Skip to content

Commit b950877

Browse files
fix: handling of map entries with omitted key or value (#1348)
According to [1], map encoding must be compatible with a repeated message using indices 1 and 2 for key and value. In particular this implies that both key and value may be omitted when they are equal to the default value - which some protobuf implementations like protobuf-c actually do. The comments in the added testcase are based on the output of protobuf-inspector [2]. [1] https://developers.google.com/protocol-buffers/docs/proto3#backwards-compatibility [2] https://github.com/jmendeth/protobuf-inspector Based-on-patch-by: Shrimpz <[email protected]> Co-authored-by: Alexander Fenster <[email protected]>
1 parent 7a25398 commit b950877

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

src/decoder.js

+37-15
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function decoder(mtype) {
1919
var gen = util.codegen(["r", "l"], mtype.name + "$decode")
2020
("if(!(r instanceof Reader))")
2121
("r=Reader.create(r)")
22-
("var c=l===undefined?r.len:r.pos+l,m=new this.ctor" + (mtype.fieldsArray.filter(function(field) { return field.map; }).length ? ",k" : ""))
22+
("var c=l===undefined?r.len:r.pos+l,m=new this.ctor" + (mtype.fieldsArray.filter(function(field) { return field.map; }).length ? ",k,value" : ""))
2323
("while(r.pos<c){")
2424
("var t=r.uint32()");
2525
if (mtype.group) gen
@@ -37,22 +37,44 @@ function decoder(mtype) {
3737

3838
// Map fields
3939
if (field.map) { gen
40-
("r.skip().pos++") // assumes id 1 + key wireType
4140
("if(%s===util.emptyObject)", ref)
4241
("%s={}", ref)
43-
("k=r.%s()", field.keyType)
44-
("r.pos++"); // assumes id 2 + value wireType
45-
if (types.long[field.keyType] !== undefined) {
46-
if (types.basic[type] === undefined) gen
47-
("%s[typeof k===\"object\"?util.longToHash(k):k]=types[%i].decode(r,r.uint32())", ref, i); // can't be groups
48-
else gen
49-
("%s[typeof k===\"object\"?util.longToHash(k):k]=r.%s()", ref, type);
50-
} else {
51-
if (types.basic[type] === undefined) gen
52-
("%s[k]=types[%i].decode(r,r.uint32())", ref, i); // can't be groups
53-
else gen
54-
("%s[k]=r.%s()", ref, type);
55-
}
42+
("var c2 = r.uint32()+r.pos");
43+
44+
if (types.defaults[field.keyType] !== undefined) gen
45+
("k=%j", types.defaults[field.keyType]);
46+
else gen
47+
("k=null");
48+
49+
if (types.defaults[type] !== undefined) gen
50+
("value=%j", types.defaults[type]);
51+
else gen
52+
("value=null");
53+
54+
gen
55+
("while(r.pos<c2){")
56+
("var tag2=r.uint32()")
57+
("switch(tag2>>>3){")
58+
("case 1: k=r.%s(); break", field.keyType)
59+
("case 2:");
60+
61+
if (types.basic[type] === undefined) gen
62+
("value=types[%i].decode(r,r.uint32())", i); // can't be groups
63+
else gen
64+
("value=r.%s()", type);
65+
66+
gen
67+
("break")
68+
("default:")
69+
("r.skipType(tag2&7)")
70+
("break")
71+
("}")
72+
("}");
73+
74+
if (types.long[field.keyType] !== undefined) gen
75+
("%s[typeof k===\"object\"?util.longToHash(k):k]=value", ref);
76+
else gen
77+
("%s[k]=value", ref);
5678

5779
// Repeated fields
5880
} else if (field.repeated) { gen

tests/comp_maps.js

+44
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,50 @@ tape.test("maps", function(test) {
9292
test.end();
9393
});
9494

95+
test.test(test.name + " - omitted fields", function(test) {
96+
97+
var mapRoot = protobuf.Root.fromJSON({
98+
nested: {
99+
MapMessage: {
100+
fields: {
101+
value: {
102+
keyType: "int32",
103+
type: "string",
104+
id: 1
105+
}
106+
}
107+
}
108+
}
109+
});
110+
111+
var MapMessage = mapRoot.lookup("MapMessage");
112+
113+
var value = {
114+
value: {
115+
0: ''
116+
}
117+
};
118+
var dec;
119+
120+
// 1 <chunk> = message(1 <varint> = 0, 2 <chunk> = empty chunk)
121+
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x04, 0x08, 0x00, 0x12, 0x00));
122+
test.deepEqual(dec, value, "should correct decode the buffer without omitted fields");
123+
124+
// 1 <chunk> = message(1 <varint> = 0)
125+
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x02, 0x08, 0x00));
126+
test.deepEqual(dec, value, "should correct decode the buffer with omitted value");
127+
128+
// 1 <chunk> = message(2 <chunk> = empty chunk)
129+
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x02, 0x12, 0x00));
130+
test.deepEqual(dec, value, "should correct decode the buffer with omitted key");
131+
132+
// 1 <chunk> = empty chunk
133+
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x00));
134+
test.deepEqual(dec, value, "should correct decode the buffer with both key and value omitted");
135+
136+
test.end();
137+
});
138+
95139
test.end();
96140
});
97141

0 commit comments

Comments
 (0)