Skip to content

Commit 988b7bb

Browse files
committed
Merge branch '2.x' into 3.1
2 parents b2b97be + 6a4baad commit 988b7bb

3 files changed

Lines changed: 136 additions & 11 deletions

File tree

protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufParser.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,10 @@ private JsonToken _handleRootKey(int tag) throws JacksonException
711711
}
712712
// array?
713713
if (f.repeated) {
714-
if (f.packed) {
714+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
715+
// the actual wire type, not just the schema's declared `packed` flag:
716+
// proto3 permits either encoding for repeated scalar/enum fields.
717+
if (f.isPackedInWire(wireType)) {
715718
_state = STATE_ARRAY_START_PACKED;
716719
} else {
717720
_state = STATE_ARRAY_START;
@@ -755,7 +758,10 @@ private JsonToken _handleNestedKey(int tag) throws JacksonException
755758

756759
// array?
757760
if (f.repeated) {
758-
if (f.packed) {
761+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
762+
// the actual wire type, not just the schema's declared `packed` flag:
763+
// proto3 permits either encoding for repeated scalar/enum fields.
764+
if (f.isPackedInWire(wireType)) {
759765
_state = STATE_ARRAY_START_PACKED;
760766
} else {
761767
_state = STATE_ARRAY_START;
@@ -1027,7 +1033,10 @@ public String nextName() throws JacksonException
10271033

10281034
// array?
10291035
if (_currentField.repeated) {
1030-
if (_currentField.packed) {
1036+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1037+
// the actual wire type, not just the schema's declared `packed` flag:
1038+
// proto3 permits either encoding for repeated scalar/enum fields.
1039+
if (_currentField.isPackedInWire(wireType)) {
10311040
_state = STATE_ARRAY_START_PACKED;
10321041
} else {
10331042
_state = STATE_ARRAY_START;
@@ -1064,7 +1073,10 @@ public String nextName() throws JacksonException
10641073

10651074
// array?
10661075
if (_currentField.repeated) {
1067-
if (_currentField.packed) {
1076+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1077+
// the actual wire type, not just the schema's declared `packed` flag:
1078+
// proto3 permits either encoding for repeated scalar/enum fields.
1079+
if (_currentField.isPackedInWire(wireType)) {
10681080
_state = STATE_ARRAY_START_PACKED;
10691081
} else {
10701082
_state = STATE_ARRAY_START;
@@ -1116,7 +1128,10 @@ public boolean nextName(SerializableString sstr) throws JacksonException
11161128

11171129
// array?
11181130
if (_currentField.repeated) {
1119-
if (_currentField.packed) {
1131+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1132+
// the actual wire type, not just the schema's declared `packed` flag:
1133+
// proto3 permits either encoding for repeated scalar/enum fields.
1134+
if (_currentField.isPackedInWire(wireType)) {
11201135
_state = STATE_ARRAY_START_PACKED;
11211136
} else {
11221137
_state = STATE_ARRAY_START;
@@ -1152,7 +1167,10 @@ public boolean nextName(SerializableString sstr) throws JacksonException
11521167

11531168
// array?
11541169
if (_currentField.repeated) {
1155-
if (_currentField.packed) {
1170+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1171+
// the actual wire type, not just the schema's declared `packed` flag:
1172+
// proto3 permits either encoding for repeated scalar/enum fields.
1173+
if (_currentField.isPackedInWire(wireType)) {
11561174
_state = STATE_ARRAY_START_PACKED;
11571175
} else {
11581176
_state = STATE_ARRAY_START;
@@ -1209,8 +1227,14 @@ public int nextNameMatch(PropertyNameMatcher matcher) throws JacksonException
12091227

12101228
// array?
12111229
if (_currentField.repeated) {
1212-
_state = _currentField.packed
1213-
? STATE_ARRAY_START_PACKED : STATE_ARRAY_START;
1230+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1231+
// the actual wire type, not just the schema's declared `packed` flag:
1232+
// proto3 permits either encoding for repeated scalar/enum fields.
1233+
if (_currentField.isPackedInWire(wireType)) {
1234+
_state = STATE_ARRAY_START_PACKED;
1235+
} else {
1236+
_state = STATE_ARRAY_START;
1237+
}
12141238
} else {
12151239
_state = STATE_ROOT_VALUE;
12161240
}
@@ -1247,8 +1271,14 @@ public int nextNameMatch(PropertyNameMatcher matcher) throws JacksonException
12471271

12481272
// array?
12491273
if (_currentField.repeated) {
1250-
_state = _currentField.packed
1251-
? STATE_ARRAY_START_PACKED : STATE_ARRAY_START;
1274+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1275+
// the actual wire type, not just the schema's declared `packed` flag:
1276+
// proto3 permits either encoding for repeated scalar/enum fields.
1277+
if (_currentField.isPackedInWire(wireType)) {
1278+
_state = STATE_ARRAY_START_PACKED;
1279+
} else {
1280+
_state = STATE_ARRAY_START;
1281+
}
12521282
} else {
12531283
_state = STATE_NESTED_VALUE;
12541284
}

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufField.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,34 @@ public final boolean isArray() {
226226
public final boolean isValidFor(int typeTag) {
227227
return (typeTag == wireType)
228228
// 13-Apr-2017, tatu: to fix [dataformats-binary#76]
229-
|| (packed && repeated && typeTag == WireType.LENGTH_PREFIXED);
229+
// 03-Jul-2026, tatu: [dataformats-binary#134] A repeated scalar/enum
230+
// field may arrive packed (LENGTH_PREFIXED) regardless of the schema's
231+
// declared `packed` flag -- proto3 permits either encoding on the wire,
232+
// so tolerance must key off the type, not the schema default.
233+
|| (repeated && type.isPackable() && typeTag == WireType.LENGTH_PREFIXED);
234+
}
235+
236+
/**
237+
* Accessor for deciding whether an incoming, repeated field should be read
238+
* using "packed" (single length-prefixed block) encoding.
239+
*<p>
240+
* For genuinely packable types (scalar numeric/enum/boolean) the native
241+
* unpacked wire type differs from {@code LENGTH_PREFIXED}, so the actual wire
242+
* type is unambiguous and authoritative: proto3 permits either encoding on the
243+
* wire regardless of the schema's declared {@code packed} flag.
244+
*<p>
245+
* For non-packable types (String/Bytes/Message) a single element and a
246+
* jackson-style "packed" block are <b>both</b> {@code LENGTH_PREFIXED}, so the
247+
* wire type cannot distinguish them; there we must fall back to the schema's
248+
* declared {@code packed} flag.
249+
*
250+
* @since 2.21.5 [dataformats-binary#134]
251+
*/
252+
public final boolean isPackedInWire(int typeTag) {
253+
if (type.isPackable()) {
254+
return repeated && (typeTag == WireType.LENGTH_PREFIXED);
255+
}
256+
return packed;
230257
}
231258

232259
@Override

protobuf/src/test/java/tools/jackson/dataformat/protobuf/Proto3PackedDefault134Test.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,72 @@ public void testProto3WriteDefaultsToPacked() throws Exception
117117
assertEquals(100, t.get("f").get(0).asInt());
118118
assertEquals(200, t.get("f").get(1).asInt());
119119
}
120+
121+
// [dataformats-binary#134]: even though a proto3 schema declares the field
122+
// "packed" by default, the wire is authoritative -- an unpacked proto3 stream
123+
// (legal per spec) must still decode. Decoder keys off the actual wire type.
124+
@Test
125+
public void testProto3PackedSchemaStillReadsUnpackedWire() throws Exception
126+
{
127+
final String SCHEMA_STR = "syntax = \"proto3\";\n"
128+
+ "message t {\n"
129+
+ " repeated uint32 f = 1;\n"
130+
+ "}\n";
131+
// unpacked encoding despite proto3 default being packed
132+
final byte[] pb = { 0x8, 0x64, 0x8, (byte) 0xc8, 0x1 }; // f = [100, 200], unpacked
133+
134+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(SCHEMA_STR);
135+
JsonNode t = MAPPER.readerFor(JsonNode.class).with(schema).readValue(pb);
136+
137+
assertEquals(2, t.get("f").size());
138+
assertEquals(100, t.get("f").get(0).asInt());
139+
assertEquals(200, t.get("f").get(1).asInt());
140+
}
141+
142+
// [dataformats-binary#134]: conversely, an unpacked-by-default schema (proto2,
143+
// or proto3 with `[packed=false]`) must still decode a packed wire stream.
144+
@Test
145+
public void testUnpackedSchemaStillReadsPackedWire() throws Exception
146+
{
147+
final String SCHEMA_STR = "syntax = \"proto2\";\n"
148+
+ "message t {\n"
149+
+ " repeated uint32 f = 1;\n"
150+
+ "}\n";
151+
// packed encoding despite proto2 default being unpacked
152+
final byte[] pb = { 0xa, 0x3, 0x64, (byte) 0xc8, 0x1 }; // f = [100, 200], packed
153+
154+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(SCHEMA_STR);
155+
JsonNode t = MAPPER.readerFor(JsonNode.class).with(schema).readValue(pb);
156+
157+
assertEquals(2, t.get("f").size());
158+
assertEquals(100, t.get("f").get(0).asInt());
159+
assertEquals(200, t.get("f").get(1).asInt());
160+
}
161+
162+
// [dataformats-binary#134]: mismatch tolerance must also work for a repeated
163+
// field nested inside a message (exercises `_handleNestedKey`, not just root).
164+
// Uses proto2 + explicit `[packed=true]` to declare the nested field packed
165+
// while feeding an unpacked wire stream (the old square protoparser does not
166+
// accept proto3 singular message fields, so we avoid proto3 syntax here).
167+
@Test
168+
public void testNestedRepeatedPackedSchemaReadsUnpackedWire() throws Exception
169+
{
170+
final String SCHEMA_STR = "message Outer {\n"
171+
+ " optional Inner inner = 1;\n"
172+
+ "}\n"
173+
+ "message Inner {\n"
174+
+ " repeated uint32 f = 1 [packed=true];\n"
175+
+ "}\n";
176+
// Outer.inner (field 1, length-delimited) wrapping Inner with unpacked f=[100,200]
177+
// inner payload: 0x8,0x64, 0x8,0xc8,0x1 (5 bytes)
178+
final byte[] pb = { 0xa, 0x5, 0x8, 0x64, 0x8, (byte) 0xc8, 0x1 };
179+
180+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(SCHEMA_STR, "Outer");
181+
JsonNode t = MAPPER.readerFor(JsonNode.class).with(schema).readValue(pb);
182+
183+
JsonNode arr = t.get("inner").get("f");
184+
assertEquals(2, arr.size());
185+
assertEquals(100, arr.get(0).asInt());
186+
assertEquals(200, arr.get(1).asInt());
187+
}
120188
}

0 commit comments

Comments
 (0)