mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-06 23:39:38 +01:00
feat(protocol): add protocol-v2
This commit is contained in:
48
packages/protocol/README.md
Normal file
48
packages/protocol/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# @trezor/protocol
|
||||
|
||||
Library for decoding and encoding messages from/to Trezor
|
||||
|
||||
## protocol-bridge
|
||||
|
||||
Message format:
|
||||
|
||||
```
|
||||
| 2 bytes | |
|
||||
| protobuf_message_type | protobuf_message_payload |
|
||||
```
|
||||
|
||||
## protocol-v1
|
||||
|
||||
Message format:
|
||||
|
||||
```
|
||||
| 3 bytes | 2 bytes | 2 bytes | `len` - 2 bytes |
|
||||
| magic | magic | magic | len | len | protobuf_message_type | protobuf_message_payload |
|
||||
```
|
||||
|
||||
Continuation packet format (chunks):
|
||||
|
||||
```
|
||||
| 1 byte | |
|
||||
| magic | protobuf_message_chunk |
|
||||
```
|
||||
|
||||
## protocol-v2 (TrezorHostProtocol)
|
||||
|
||||
Message format:
|
||||
|
||||
```
|
||||
| 1 byte | 2 bytes | 2 bytes | `len` including 4 bytes crc |
|
||||
| control_byte | channel | channel | len | len | thp_payload + crc |
|
||||
```
|
||||
|
||||
Continuation packet format (chunks):
|
||||
|
||||
```
|
||||
| 1 byte | 2 bytes | |
|
||||
| continuation_packet | channel | channel | payload_chunk |
|
||||
```
|
||||
|
||||
## protocol-trzd
|
||||
|
||||
Decode loaded `@trezor/protobuf` messages
|
||||
@@ -1,4 +1,5 @@
|
||||
export * as v1 from './protocol-v1';
|
||||
export * as v2 from './protocol-v2';
|
||||
export * as bridge from './protocol-bridge';
|
||||
export * as trzd from './protocol-trzd';
|
||||
export * from './errors';
|
||||
|
||||
@@ -17,6 +17,7 @@ export const decode: TransportProtocolDecode = bytes => {
|
||||
const { messageType, length } = readHeader(bytes);
|
||||
|
||||
return {
|
||||
header: Buffer.alloc(0), // bridge does not return header
|
||||
messageType,
|
||||
length,
|
||||
payload: bytes.subarray(HEADER_SIZE),
|
||||
|
||||
@@ -40,6 +40,7 @@ export const decode: TransportProtocolDecode = bytes => {
|
||||
}
|
||||
|
||||
return {
|
||||
header: bytes.subarray(0, 3),
|
||||
length,
|
||||
messageType,
|
||||
payload: bytes.subarray(HEADER_SIZE),
|
||||
|
||||
3
packages/protocol/src/protocol-v2/constants.ts
Normal file
3
packages/protocol/src/protocol-v2/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const HEADER_SIZE = 1 + 2; // 1: control_byte + 2: channel
|
||||
export const MESSAGE_LEN_SIZE = 2;
|
||||
export const MESSAGE_TYPE = 'TrezorHostProtocolMessage';
|
||||
22
packages/protocol/src/protocol-v2/decode.ts
Normal file
22
packages/protocol/src/protocol-v2/decode.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as ERRORS from '../errors';
|
||||
import { HEADER_SIZE, MESSAGE_LEN_SIZE, MESSAGE_TYPE } from './constants';
|
||||
import { getChunkHeader } from './encode';
|
||||
import { TransportProtocolDecode } from '../types';
|
||||
|
||||
// Parses raw input from Trezor and returns some information about the whole message
|
||||
export const decode: TransportProtocolDecode = bytes => {
|
||||
const buffer = Buffer.from(bytes);
|
||||
|
||||
// chunk should have at least 5 bytes. 3 bytes `header` + 2 bytes `length`
|
||||
if (buffer.byteLength < HEADER_SIZE + MESSAGE_LEN_SIZE) {
|
||||
throw new Error(ERRORS.PROTOCOL_MALFORMED);
|
||||
}
|
||||
|
||||
return {
|
||||
header: buffer.subarray(0, HEADER_SIZE),
|
||||
chunkHeader: getChunkHeader(buffer),
|
||||
length: buffer.readUint16BE(HEADER_SIZE),
|
||||
messageType: MESSAGE_TYPE, // will be decoded by `protocol-thp`
|
||||
payload: buffer.subarray(HEADER_SIZE + MESSAGE_LEN_SIZE),
|
||||
};
|
||||
};
|
||||
33
packages/protocol/src/protocol-v2/encode.ts
Normal file
33
packages/protocol/src/protocol-v2/encode.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as ERRORS from '../errors';
|
||||
import { HEADER_SIZE, MESSAGE_LEN_SIZE, MESSAGE_TYPE } from './constants';
|
||||
import { TransportProtocolEncode } from '../types';
|
||||
|
||||
export const getChunkHeader = (data: Buffer) => {
|
||||
// data should have at least 1 control_byte + 2 bytes channel
|
||||
if (data.byteLength < HEADER_SIZE) {
|
||||
throw new Error(ERRORS.PROTOCOL_MALFORMED);
|
||||
}
|
||||
|
||||
const channel = data.subarray(1, HEADER_SIZE);
|
||||
const header = Buffer.concat([Buffer.from([0x80]), channel]); // THP_CONTINUATION_PACKET
|
||||
|
||||
return header;
|
||||
};
|
||||
|
||||
// encode `protocol-thp` message
|
||||
export const encode: TransportProtocolEncode = (data, options) => {
|
||||
if (options.messageType === MESSAGE_TYPE) {
|
||||
if (!options.header || options.header.byteLength !== HEADER_SIZE) {
|
||||
throw new Error(
|
||||
`${options.messageType} unexpected header ${options.header?.toString('hex')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const length = Buffer.alloc(MESSAGE_LEN_SIZE);
|
||||
length.writeUInt16BE(data.length);
|
||||
|
||||
return Buffer.concat([options.header, length, data]);
|
||||
}
|
||||
|
||||
throw new Error(`Use protocol-thp.encode for messageType ${options.messageType}`);
|
||||
};
|
||||
3
packages/protocol/src/protocol-v2/index.ts
Normal file
3
packages/protocol/src/protocol-v2/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './decode';
|
||||
export * from './encode';
|
||||
export const name = 'v2';
|
||||
@@ -1,4 +1,5 @@
|
||||
export type TransportProtocolDecode = (bytes: Buffer) => {
|
||||
header: Buffer;
|
||||
length: number;
|
||||
messageType: number | string;
|
||||
payload: Buffer;
|
||||
@@ -6,6 +7,7 @@ export type TransportProtocolDecode = (bytes: Buffer) => {
|
||||
|
||||
export interface TransportProtocolEncodeOptions {
|
||||
messageType: number | string;
|
||||
header?: Buffer;
|
||||
}
|
||||
|
||||
export type TransportProtocolEncode = (
|
||||
@@ -14,7 +16,7 @@ export type TransportProtocolEncode = (
|
||||
) => Buffer;
|
||||
|
||||
export interface TransportProtocol {
|
||||
name: 'bridge' | 'v1';
|
||||
name: 'bridge' | 'v1' | 'v2';
|
||||
encode: TransportProtocolEncode;
|
||||
decode: TransportProtocolDecode;
|
||||
getChunkHeader: (data: Buffer) => Buffer;
|
||||
|
||||
53
packages/protocol/tests/protocol-v2.test.ts
Normal file
53
packages/protocol/tests/protocol-v2.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { decode, encode, getChunkHeader } from '../src/protocol-v2';
|
||||
|
||||
describe('protocol-v2', () => {
|
||||
it('encode/decode TrezorHostProtocolMessage', () => {
|
||||
// ThpCreateNewSession message
|
||||
const data = Buffer.from(
|
||||
'04123800230003e80a0870617373313233341000a0a1a2a3a4a5a6a7a8a9b0b1b2b3b4b504db712b',
|
||||
'hex',
|
||||
);
|
||||
|
||||
const decoded = decode(data);
|
||||
expect(decoded.messageType).toEqual('TrezorHostProtocolMessage');
|
||||
expect(decoded.header).toEqual(data.subarray(0, 3));
|
||||
expect(decoded.length).toEqual(35);
|
||||
expect(decoded.payload).toEqual(data.subarray(5, 5 + 35));
|
||||
|
||||
const encoded = encode(decoded.payload, decoded);
|
||||
expect(encoded).toEqual(data);
|
||||
});
|
||||
|
||||
it('encode with error', () => {
|
||||
expect(() => encode(Buffer.alloc(0), { messageType: 1 })).toThrow(
|
||||
'Use protocol-thp.encode',
|
||||
);
|
||||
expect(() => encode(Buffer.alloc(0), { messageType: 'TrezorHostProtocolMessage' })).toThrow(
|
||||
'unexpected header undefined',
|
||||
);
|
||||
expect(() =>
|
||||
encode(Buffer.alloc(0), {
|
||||
messageType: 'TrezorHostProtocolMessage',
|
||||
header: Buffer.alloc(1),
|
||||
}),
|
||||
).toThrow('unexpected header 00');
|
||||
expect(() =>
|
||||
encode(Buffer.alloc(0), {
|
||||
messageType: 'TrezorHostProtocolMessage',
|
||||
header: Buffer.alloc(4),
|
||||
}),
|
||||
).toThrow('unexpected header 00000000');
|
||||
});
|
||||
|
||||
it('decode with error', () => {
|
||||
expect(() => decode(Buffer.alloc(0))).toThrow('Malformed protocol format');
|
||||
});
|
||||
|
||||
it('getChunkHeader', () => {
|
||||
expect(getChunkHeader(Buffer.from('0412380000', 'hex'))).toEqual(
|
||||
Buffer.from('801238', 'hex'),
|
||||
);
|
||||
// with error
|
||||
expect(() => getChunkHeader(Buffer.alloc(0))).toThrow('Malformed protocol format');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user