Files
trezor-suite/packages/protobuf/tests/encode-decode-basic.test.ts

306 lines
9.5 KiB
TypeScript

import * as ProtoBuf from 'protobufjs/light';
import { decode } from '../src/decode';
import { encode } from '../src/encode';
const messages = {
nested: {
messages: {
nested: {
String: {
fields: {
field: {
rule: 'required',
type: 'string',
id: 1,
},
},
},
Uint32: {
fields: {
field: {
rule: 'required',
type: 'uint32',
id: 2,
},
},
},
Uint64: {
fields: {
field: {
rule: 'required',
type: 'uint64',
id: 3,
},
},
},
Bool: {
fields: {
field: {
rule: 'required',
type: 'bool',
id: 4,
},
},
},
Sint32: {
fields: {
field: {
rule: 'required',
type: 'sint32',
id: 5,
},
},
},
Sint64: {
fields: {
field: {
rule: 'required',
type: 'sint64',
id: 6,
},
},
},
Bytes: {
fields: {
field: {
rule: 'required',
type: 'bytes',
id: 7,
},
},
},
// complex and real life examples
ComplexFieldOfOptionals: {
fields: {
bool: {
rule: 'optional',
type: 'bool',
id: 8,
},
number: {
rule: 'optional',
type: 'uint32',
id: 9,
},
},
},
Repeated: {
fields: {
bool: {
rule: 'repeated',
type: 'bool',
id: 8,
},
},
},
Defaults: {
fields: {
string: {
type: 'string',
id: 8,
options: {
defaults: 'hello world',
},
},
},
},
},
},
},
};
const basicFixtures = [
{
name: 'String',
params: { field: 'foo' },
encoded: '0a03666f6f',
},
{
name: 'Uint32',
params: { field: 4294967295 },
encoded: '10ffffffff0f',
},
{
name: 'Uint64',
params: { field: 1844674407370955 },
encoded: '18cba19cd68bb7a303',
},
{
name: 'Uint64',
params: { field: '166054873161269248' }, // over Number.MAX_SAFE_INTEGER is sent as string
encoded: '1880808080f0c0fca602',
},
{
name: 'Sint64',
params: { field: '-166054873161269248' }, // over Number.MAX_SAFE_INTEGER is sent as string
encoded: '30ffffffffdf81f9cd04',
},
{
name: 'Bool',
params: { field: true },
encoded: '2001',
},
{
name: 'Bool',
params: { field: false },
encoded: '2000',
},
{
name: 'Sint32',
params: { field: -4294967 },
encoded: '28eda48c04',
},
{
name: 'Sint64',
params: { field: -1844674407370955 },
encoded: '3095c3b8ac97eec606',
},
{
name: 'Bytes',
params: {
field: '851fc9542342321af63ecbba7d3ece545f2a42bad01ba32cff5535b18e54b6d3106e10b6a4525993d185a1443d9a125186960e028eabfdd8d76cf70a3a7e3100',
},
encoded:
'3a40851fc9542342321af63ecbba7d3ece545f2a42bad01ba32cff5535b18e54b6d3106e10b6a4525993d185a1443d9a125186960e028eabfdd8d76cf70a3a7e3100',
},
];
// note: difference in bool encoding. if type === bool && field = optional && not message of only one field, bool is encoded as ""
const advancedFixtures = [
{
name: 'ComplexFieldOfOptionals',
in: { number: 1 },
encoded: '4801',
out: { bool: null, number: 1 },
},
{
name: 'Repeated',
in: { bool: [true, false, true, false] },
encoded: '420401000100',
out: { bool: [true, false, true, false] },
},
{
name: 'Defaults',
in: { string: '' },
encoded: '4200',
out: { string: '' },
},
];
describe('basic concepts', () => {
const Messages = ProtoBuf.Root.fromJSON(messages);
describe('primitives encode/decode', () => {
basicFixtures.forEach(f => {
describe(f.name, () => {
const Message = Messages.lookupType(`messages.${f.name}`);
test(f.name, () => {
// serialize new way - this is to confirm new lib won't break old behavior
const encoded = encode(Message, f.params);
expect(encoded.toString('hex')).toEqual(f.encoded);
// deserialize new way - this is to confirm new lib won't break old behavior
const decoded = decode(Message, encoded);
expect(decoded).toEqual(f.params);
});
});
});
});
describe('advanced', () => {
advancedFixtures.forEach(f => {
describe(f.name, () => {
const Message = Messages.lookupType(`messages.${f.name}`);
test(f.name, () => {
// serialize new way - this is to confirm new lib won't break old behavior
const encoded = encode(Message, f.in);
expect(encoded.toString('hex')).toEqual(f.encoded);
// deserialize new way - this is to confirm new lib won't break old behavior
const decoded = decode(Message, encoded);
expect(decoded).toEqual(f.out);
});
});
});
test('Different protobuf between receiving ends', () => {
const customMessages = (fields?: any) => ({
nested: {
messages: {
nested: {
ButtonRequest: {
fields: {
code: {
type: 'string',
id: 1,
},
pages: {
type: 'uint32',
id: 2,
},
...fields,
},
},
},
},
},
});
const SenderMessages = ProtoBuf.Root.fromJSON(customMessages());
const senderEncoded = encode(SenderMessages.lookupType('messages.ButtonRequest'), {
type: 'foo',
pages: 123,
});
// now change field type from uint32 to string
const ReceiverMessages = ProtoBuf.Root.fromJSON(
customMessages({ pages: { type: 'string', id: 2 } }),
);
expect(() => {
decode(ReceiverMessages.lookupType('messages.ButtonRequest'), senderEncoded);
}).toThrow();
});
test('message with repeated enum field', () => {
const protobufRoot = ProtoBuf.Root.fromJSON({
nested: {
RepeatedEnum: {
values: {
RepeatedEnum_1: 1,
RepeatedEnum_2: 2,
RepeatedEnum_3: 3,
},
},
MessageWithRepeatedEnum: {
fields: {
repeated_enum: {
rule: 'repeated',
type: 'RepeatedEnum',
id: 1,
options: {
packed: false,
},
},
},
},
},
});
const m = protobufRoot.lookupType('MessageWithRepeatedEnum');
const encoded = encode(m, { repeated_enum: [3, 'RepeatedEnum_2'] });
const decoded = decode(m, encoded);
expect(decoded.repeated_enum).toEqual(['RepeatedEnum_3', 'RepeatedEnum_2']);
});
});
});