Skip to content

Commit

Permalink
Add more message set tests, fix a bug (#859)
Browse files Browse the repository at this point in the history
When parsing an item we should consume the "end group" tag before
breaking. Remove the `break` statements after parsing the extension.
  • Loading branch information
osa1 authored Jul 4, 2023
1 parent 7bebbc6 commit a912f76
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 65 deletions.
14 changes: 6 additions & 8 deletions protobuf/lib/src/protobuf/message_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,15 @@ abstract class $_MessageSet extends GeneratedMessage {
[ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY]) {
// Parse items. The field for the items looks like:
//
// repeated Item items = 1;
// repeated group Item items = 1;
//
// Since message sets are compatible with proto1 items can't be packed.
outer:
while (true) {
final tag = input.readTag();
final tagNumber = getTagFieldNumber(tag);

if (tag == 0) {
break;
break; // End of input.
}

if (tagNumber != _messageSetItemsTag) {
Expand All @@ -87,7 +86,7 @@ abstract class $_MessageSet extends GeneratedMessage {
final tagNumber = getTagFieldNumber(tag);

if (tag == 0) {
break;
break; // End of input.
}

if (tagNumber == _messageSetItemTypeIdTag) {
Expand All @@ -96,20 +95,19 @@ abstract class $_MessageSet extends GeneratedMessage {
_parseExtension(typeId, message, extensionRegistry);
typeId = null;
message = null;
continue outer;
}
} else if (tagNumber == _messageSetItemMessageTag) {
message = input.readBytes();
if (typeId != null) {
_parseExtension(typeId, message, extensionRegistry);
typeId = null;
message = null;
continue outer;
}
} else {
// Skip unknown tags.
// Skip unknown tags. If we're at the end of the group consume the
// EGROUP tag.
if (!input.skipField(tag)) {
break outer; // End of group.
break; // End of group.
}
}
}
Expand Down
256 changes: 202 additions & 54 deletions protoc_plugin/test/message_set_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:typed_data';

import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart';
import 'package:test/test.dart';
Expand All @@ -10,78 +12,173 @@ import '../out/protos/google/protobuf/empty.pb.dart';
import '../out/protos/message_set.pb.dart';

void main() {
final encoded = [
0xda, 0x07, 0x0e, 0x0b, 0x10, 0xc8, 0xa6, 0x6b, 0x1a, //
0x06, 0x08, 0x7b, 0x12, 0x02, 0x68, 0x69, 0x0c
// Example message with a nested message set, encoded with the C++ library.
// `protoc --decode_raw` output:
//
// 1 {
// 1 {
// 2: 1758024
// 3 {
// 1: 123
// 2: "testing"
// 3 {
// 5: 0
// 5: 1
// 5: 2
// }
// }
// }
// 1 {
// 2: 1832098
// 3 {
// 5: 987
// 5: 654
// 5: 321
// }
// }
// }
final encodedNested = [
10, 44, 11, 16, 200, 166, 107, 26, 19, 8, 123, 18, 7, 116, 101, 115, //
116, 105, 110, 103, 26, 6, 40, 0, 40, 1, 40, 2, 12, 11, 16, 162, 233, //
111, 26, 9, 40, 219, 7, 40, 142, 5, 40, 193, 2, 12
];

test('Simple message set field', () {
final registry = ExtensionRegistry()..add(TestMessage.messageSetExtension);
final msg = TestMessage.fromBuffer(encoded, registry);
final extensionValue = msg.info
.getExtension(TestMessage.messageSetExtension) as ExtensionMessage;
expect(extensionValue.a, 123);
expect(extensionValue.b, 'hi');
expect(msg.writeToBuffer(), encoded);
// Example message with a top-level message set, encoded with the C++
// library. `protoc --decode_raw` output:
//
// 1 {
// 2: 1758024
// 3 {
// 1: 123
// }
// }
// 1 {
// 2: 1832098
// 3 {
// 5: 987
// }
// }
final encodedTopLevel = [
11, 16, 200, 166, 107, 26, 2, 8, 123, 12, 11, 16, 162, 233, 111, 26, 3, //
40, 219, 7, 12
];

test('Parse message set extensions (nested)', () {
final registry = ExtensionRegistry()
..add(TestMessage.ext1)
..add(TestMessage.ext2);
final msg = TestMessage.fromBuffer(encodedNested, registry);

final ext1Value =
msg.info.getExtension(TestMessage.ext1) as ExtensionMessage1;
expect(ext1Value.a, 123);
expect(ext1Value.b, 'testing');
expect(ext1Value.c.ints, [0, 1, 2]);

final ext2Value =
msg.info.getExtension(TestMessage.ext2) as ExtensionMessage2;
expect(ext2Value.ints, [987, 654, 321]);

expect(msg.writeToBuffer(), encodedNested);
});

test('Parse message set (top-level)', () {
final registry = ExtensionRegistry()
..add(TestMessage.ext1)
..add(TestMessage.ext2);
final msg = MessageSet.fromBuffer(encodedTopLevel, registry);

final ext1Value = msg.getExtension(TestMessage.ext1) as ExtensionMessage1;
expect(ext1Value.a, 123);
expect(ext1Value.b, ''); // not set
expect(ext1Value.c.ints, []); // not set

final ext2Value = msg.getExtension(TestMessage.ext2) as ExtensionMessage2;
expect(ext2Value.ints, [987]);

expect(msg.writeToBuffer(), encodedTopLevel);
});

test('Parse as unknown fields and serialize', () {
final msg = TestMessage.fromBuffer(encoded);
expect(msg.writeToBuffer(), encoded);
final msg = TestMessage.fromBuffer(encodedNested);
expect(msg.writeToBuffer(), encodedNested);
});

test('Reparse with extensions (nested message)', () {
final msg = TestMessage.fromBuffer(encoded);
final registry = ExtensionRegistry()..add(TestMessage.messageSetExtension);
final msg = TestMessage.fromBuffer(encodedNested);
final registry = ExtensionRegistry()
..add(TestMessage.ext1)
..add(TestMessage.ext2);
final reparsedInfo = registry.reparseMessage(msg.info);
final extensionValue = reparsedInfo
.getExtension(TestMessage.messageSetExtension) as ExtensionMessage;
expect(extensionValue.a, 123);
expect(extensionValue.b, 'hi');

final ext1Value =
reparsedInfo.getExtension(TestMessage.ext1) as ExtensionMessage1;
expect(ext1Value.a, 123);
expect(ext1Value.b, 'testing');

final ext2Value =
reparsedInfo.getExtension(TestMessage.ext2) as ExtensionMessage2;
expect(ext2Value.ints, [987, 654, 321]);

expect(reparsedInfo.unknownFields.isEmpty, true);
});

test('Reparse with extensions (top-level message)', () {
final msg = TestMessage.fromBuffer(encoded);
final registry = ExtensionRegistry()..add(TestMessage.messageSetExtension);
final msg = TestMessage.fromBuffer(encodedNested);
final registry = ExtensionRegistry()
..add(TestMessage.ext1)
..add(TestMessage.ext2);
final reparsedMsg = registry.reparseMessage(msg);
final extensionValue = reparsedMsg.info
.getExtension(TestMessage.messageSetExtension) as ExtensionMessage;
expect(extensionValue.a, 123);
expect(extensionValue.b, 'hi');

final ext1Value =
reparsedMsg.info.getExtension(TestMessage.ext1) as ExtensionMessage1;
expect(ext1Value.a, 123);
expect(ext1Value.b, 'testing');

final ext2Value =
reparsedMsg.info.getExtension(TestMessage.ext2) as ExtensionMessage2;
expect(ext2Value.ints, [987, 654, 321]);

expect(msg.unknownFields.isEmpty, true);
});

test('Ignore extra tags in items', () {
// Generate a message set encoding using unknown fields. Message set item
// will have extra tags.

final itemFieldGroup = UnknownFieldSet();
final encoded = encodeMessageSetWithExtraItemTags(Encoding.group);

final registry = ExtensionRegistry()..add(TestMessage.ext1);
final decodedMsg = MessageSet.fromBuffer(encoded, registry);
final extensionValue =
decodedMsg.getExtension(TestMessage.ext1) as ExtensionMessage1;

expect(extensionValue.a, 123456);
expect(extensionValue.b, 'test');
expect(decodedMsg.unknownFields.isEmpty, true);
});

test('Ignore extra tags in items (length delimited encoding)', () {
// Same as above, but tests length delimited encoding.

// Invalid field with tag 1.
itemFieldGroup.addField(
1, UnknownFieldSetField()..addLengthDelimited([1, 2, 3]));
final encoded = encodeMessageSetWithExtraItemTags(Encoding.lengthDelimited);

// Extension field.
itemFieldGroup.addField(
3,
UnknownFieldSetField()
..addLengthDelimited((ExtensionMessage()
..a = 123456
..b = 'test')
.writeToBuffer()));
final registry = ExtensionRegistry()..add(TestMessage.ext1);
final decodedMsg = MessageSet.fromBuffer(encoded, registry);
final extensionValue =
decodedMsg.getExtension(TestMessage.ext1) as ExtensionMessage1;

// Invalid field with tag 3.
itemFieldGroup.addField(
4, UnknownFieldSetField()..addVarint(Int64(123456)));
expect(extensionValue.a, 123456);
expect(extensionValue.b, 'test');
expect(decodedMsg.unknownFields.isEmpty, true);
});

// Type id field.
itemFieldGroup.addField(
2, UnknownFieldSetField()..addVarint(Int64(1758024)));
test('Ignore invalid tags in repeated items', () {
// Extra fields other than `repeated Item items = 1` in the outer message.

final messageSetUnknownFields = UnknownFieldSet();
messageSetUnknownFields.addField(
1, UnknownFieldSetField()..addGroup(itemFieldGroup));
2, UnknownFieldSetField()..addVarint(Int64(987)));

final messageSetEncoded = CodedBufferWriter();
messageSetUnknownFields.writeToCodedBufferWriter(messageSetEncoded);
Expand All @@ -93,17 +190,14 @@ void main() {
..lengthDelimited.add(messageSetEncoded.toBuffer())))
.writeToBuffer();

final registry = ExtensionRegistry()..add(TestMessage.messageSetExtension);
final registry = ExtensionRegistry()..add(TestMessage.ext1);
final msg = TestMessage.fromBuffer(encoded, registry);
final extensionValue = msg.info
.getExtension(TestMessage.messageSetExtension) as ExtensionMessage;
expect(extensionValue.a, 123456);
expect(extensionValue.b, 'test');
expect(msg.unknownFields.isEmpty, true);
expect(msg.info.hasExtension(TestMessage.ext1), false);
});

test('Ignore invalid tags in repeated items', () {
// Extra fields other than `repeated Item items = 1` in the outer message.
test('Encode message set as length prefixed', () {
// Message sets are generally encoded as groups, but we support length
// delimited as well.

final messageSetUnknownFields = UnknownFieldSet();
messageSetUnknownFields.addField(
Expand All @@ -119,8 +213,62 @@ void main() {
..lengthDelimited.add(messageSetEncoded.toBuffer())))
.writeToBuffer();

final registry = ExtensionRegistry()..add(TestMessage.messageSetExtension);
final registry = ExtensionRegistry()..add(TestMessage.ext1);
final msg = TestMessage.fromBuffer(encoded, registry);
expect(msg.info.hasExtension(TestMessage.messageSetExtension), false);
expect(msg.info.hasExtension(TestMessage.ext1), false);
});
}

enum Encoding {
lengthDelimited,
group,
}

/// Generate a message set encoding with one extension. Extension will have
/// extra tags.
Uint8List encodeMessageSetWithExtraItemTags(Encoding encoding) {
final itemFieldGroup = UnknownFieldSet();

// Invalid field with tag 1.
itemFieldGroup.addField(
1, UnknownFieldSetField()..addLengthDelimited([1, 2, 3]));

// Extension field.
itemFieldGroup.addField(
3,
UnknownFieldSetField()
..addLengthDelimited((ExtensionMessage1()
..a = 123456
..b = 'test')
.writeToBuffer()));

// Invalid field with tag 3.
itemFieldGroup.addField(4, UnknownFieldSetField()..addVarint(Int64(123456)));

// Type id field.
itemFieldGroup.addField(2, UnknownFieldSetField()..addVarint(Int64(1758024)));

final messageSet = Empty();
final messageSetUnknownFields = messageSet.unknownFields;

switch (encoding) {
case Encoding.lengthDelimited:
final writer = CodedBufferWriter();
itemFieldGroup.writeToCodedBufferWriter(writer);
messageSetUnknownFields.addField(
1, UnknownFieldSetField()..addLengthDelimited(writer.toBuffer()));
break;
case Encoding.group:
messageSetUnknownFields.addField(
1, UnknownFieldSetField()..addGroup(itemFieldGroup));
break;
}

messageSetUnknownFields.addField(
1, UnknownFieldSetField()..addGroup(itemFieldGroup));

final messageSetEncoded = CodedBufferWriter();
messageSetUnknownFields.writeToCodedBufferWriter(messageSetEncoded);

return messageSet.writeToBuffer();
}
13 changes: 10 additions & 3 deletions protoc_plugin/test/protos/message_set.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ message MessageSet {

message TestMessage {
extend MessageSet {
optional ExtensionMessage message_set_extension = 1758024;
optional ExtensionMessage1 ext1 = 1758024;
optional ExtensionMessage2 ext2 = 1832098;
}

optional MessageSet info = 123;
optional MessageSet info = 1;
optional int32 i = 2;
}

message ExtensionMessage {
message ExtensionMessage1 {
optional int32 a = 1;
optional string b = 2;
optional ExtensionMessage2 c = 3;
}

message ExtensionMessage2 {
repeated int32 ints = 5;
}

0 comments on commit a912f76

Please sign in to comment.