Skip to content

Commit f9ed173

Browse files
committed
Merge branch '3.2' into 3.x
2 parents 028d6b8 + 518cf6f commit f9ed173

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
@@ -718,7 +718,10 @@ private JsonToken _handleRootKey(int tag) throws JacksonException
718718
}
719719
// array?
720720
if (f.repeated) {
721-
if (f.packed) {
721+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
722+
// the actual wire type, not just the schema's declared `packed` flag:
723+
// proto3 permits either encoding for repeated scalar/enum fields.
724+
if (f.isPackedInWire(wireType)) {
722725
_state = STATE_ARRAY_START_PACKED;
723726
} else {
724727
_state = STATE_ARRAY_START;
@@ -762,7 +765,10 @@ private JsonToken _handleNestedKey(int tag) throws JacksonException
762765

763766
// array?
764767
if (f.repeated) {
765-
if (f.packed) {
768+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
769+
// the actual wire type, not just the schema's declared `packed` flag:
770+
// proto3 permits either encoding for repeated scalar/enum fields.
771+
if (f.isPackedInWire(wireType)) {
766772
_state = STATE_ARRAY_START_PACKED;
767773
} else {
768774
_state = STATE_ARRAY_START;
@@ -1034,7 +1040,10 @@ public String nextName() throws JacksonException
10341040

10351041
// array?
10361042
if (_currentField.repeated) {
1037-
if (_currentField.packed) {
1043+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1044+
// the actual wire type, not just the schema's declared `packed` flag:
1045+
// proto3 permits either encoding for repeated scalar/enum fields.
1046+
if (_currentField.isPackedInWire(wireType)) {
10381047
_state = STATE_ARRAY_START_PACKED;
10391048
} else {
10401049
_state = STATE_ARRAY_START;
@@ -1071,7 +1080,10 @@ public String nextName() throws JacksonException
10711080

10721081
// array?
10731082
if (_currentField.repeated) {
1074-
if (_currentField.packed) {
1083+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1084+
// the actual wire type, not just the schema's declared `packed` flag:
1085+
// proto3 permits either encoding for repeated scalar/enum fields.
1086+
if (_currentField.isPackedInWire(wireType)) {
10751087
_state = STATE_ARRAY_START_PACKED;
10761088
} else {
10771089
_state = STATE_ARRAY_START;
@@ -1123,7 +1135,10 @@ public boolean nextName(SerializableString sstr) throws JacksonException
11231135

11241136
// array?
11251137
if (_currentField.repeated) {
1126-
if (_currentField.packed) {
1138+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1139+
// the actual wire type, not just the schema's declared `packed` flag:
1140+
// proto3 permits either encoding for repeated scalar/enum fields.
1141+
if (_currentField.isPackedInWire(wireType)) {
11271142
_state = STATE_ARRAY_START_PACKED;
11281143
} else {
11291144
_state = STATE_ARRAY_START;
@@ -1159,7 +1174,10 @@ public boolean nextName(SerializableString sstr) throws JacksonException
11591174

11601175
// array?
11611176
if (_currentField.repeated) {
1162-
if (_currentField.packed) {
1177+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1178+
// the actual wire type, not just the schema's declared `packed` flag:
1179+
// proto3 permits either encoding for repeated scalar/enum fields.
1180+
if (_currentField.isPackedInWire(wireType)) {
11631181
_state = STATE_ARRAY_START_PACKED;
11641182
} else {
11651183
_state = STATE_ARRAY_START;
@@ -1216,8 +1234,14 @@ public int nextNameMatch(PropertyNameMatcher matcher) throws JacksonException
12161234

12171235
// array?
12181236
if (_currentField.repeated) {
1219-
_state = _currentField.packed
1220-
? STATE_ARRAY_START_PACKED : STATE_ARRAY_START;
1237+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1238+
// the actual wire type, not just the schema's declared `packed` flag:
1239+
// proto3 permits either encoding for repeated scalar/enum fields.
1240+
if (_currentField.isPackedInWire(wireType)) {
1241+
_state = STATE_ARRAY_START_PACKED;
1242+
} else {
1243+
_state = STATE_ARRAY_START;
1244+
}
12211245
} else {
12221246
_state = STATE_ROOT_VALUE;
12231247
}
@@ -1254,8 +1278,14 @@ public int nextNameMatch(PropertyNameMatcher matcher) throws JacksonException
12541278

12551279
// array?
12561280
if (_currentField.repeated) {
1257-
_state = _currentField.packed
1258-
? STATE_ARRAY_START_PACKED : STATE_ARRAY_START;
1281+
// 03-Jul-2026, tatu: [dataformats-binary#134] Decide packed-vs-unpacked from
1282+
// the actual wire type, not just the schema's declared `packed` flag:
1283+
// proto3 permits either encoding for repeated scalar/enum fields.
1284+
if (_currentField.isPackedInWire(wireType)) {
1285+
_state = STATE_ARRAY_START_PACKED;
1286+
} else {
1287+
_state = STATE_ARRAY_START;
1288+
}
12591289
} else {
12601290
_state = STATE_NESTED_VALUE;
12611291
}

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)