mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-04 14:35:29 +01:00
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
import {
|
|
ThpState,
|
|
decode,
|
|
encode,
|
|
encodeAck,
|
|
getExpectedResponses,
|
|
isAckExpected,
|
|
} from '../../src/protocol-thp';
|
|
import { decode as decodeV2 } from '../../src/protocol-v2';
|
|
|
|
const protobufEncoder = (..._args: any[]) => ({
|
|
messageType: 1,
|
|
message: Buffer.alloc(1),
|
|
});
|
|
|
|
const protobufDecoder = (messageName: string | number, messageData: Buffer) => ({
|
|
type: messageName.toString(),
|
|
message: {
|
|
mockProtobufData: messageData,
|
|
},
|
|
});
|
|
|
|
const thpState = new ThpState();
|
|
|
|
describe('protocol-thp', () => {
|
|
beforeEach(() => {
|
|
thpState.resetState();
|
|
});
|
|
|
|
it('encode ThpCreateChannelRequest, decode ThpCreateChannelResponse', () => {
|
|
thpState.setChannel(Buffer.from('ffff', 'hex'));
|
|
const nonce = Buffer.from('639ba57ff4e0c234', 'hex');
|
|
|
|
const encoded = encode({
|
|
messageName: 'ThpCreateChannelRequest',
|
|
data: { nonce },
|
|
protobufEncoder,
|
|
thpState,
|
|
});
|
|
expect(encoded.toString('hex')).toEqual('40ffff000c639ba57ff4e0c2348189a406');
|
|
|
|
const response = Buffer.from(
|
|
'41ffff0020639ba57ff4e0c2343c830a0454335731180220002802280328042801c0171551',
|
|
'hex',
|
|
);
|
|
const decoded = decode(decodeV2(response), protobufDecoder, thpState);
|
|
expect(decoded.type).toEqual('ThpCreateChannelResponse');
|
|
|
|
expect(decoded.message).toMatchObject({
|
|
channel: Buffer.from('3c83', 'hex'),
|
|
nonce,
|
|
handshakeHash: Buffer.from(
|
|
'a1615c6dc1c2a2df8155ea5b54d0f39c320d4908e4b1eaf8d24ab5c4b466e947',
|
|
'hex',
|
|
),
|
|
// properties asserted below
|
|
});
|
|
|
|
// @ts-expect-error
|
|
const protobuf = decoded.message.properties.mockProtobufData.toString('hex');
|
|
expect(protobuf).toEqual('0a0454335731180220002802280328042801');
|
|
});
|
|
|
|
it('encode/decode ThpAck', () => {
|
|
thpState.setChannel(Buffer.from('1234', 'hex'));
|
|
|
|
const encodeAsState1 = encodeAck(thpState); // ackByte: 0
|
|
expect(encodeAsState1.toString('hex')).toEqual('2012340004d9fcce58');
|
|
|
|
thpState.sync('recv', 'ThpAck');
|
|
const encodeAsState2 = encodeAck(thpState); // ackByte: 1
|
|
expect(encodeAsState2.toString('hex')).toEqual('2812340004e98c8599');
|
|
|
|
expect(decode(decodeV2(encodeAsState1), protobufDecoder, thpState).type).toBe('ThpAck');
|
|
expect(decode(decodeV2(encodeAsState2), protobufDecoder, thpState).type).toBe('ThpAck');
|
|
});
|
|
|
|
it('decode ThpError', () => {
|
|
thpState.setChannel(Buffer.from('1222', 'hex'));
|
|
|
|
const data = Buffer.from('42122200050270303cfa', 'hex');
|
|
const thpError = decode(decodeV2(data), protobufDecoder, thpState);
|
|
expect(thpError.type).toBe('ThpError');
|
|
expect(thpError.message).toMatchObject({
|
|
code: 'ThpUnallocatedChannel',
|
|
});
|
|
});
|
|
|
|
it('ThpState serialize/deserialize', () => {
|
|
const state1 = new ThpState();
|
|
state1.setChannel(Buffer.from('4321', 'hex'));
|
|
state1.updateSyncBit('send');
|
|
state1.updateNonce('recv');
|
|
state1.setExpectedResponses([1, 2, 3, 4]);
|
|
|
|
const serializedState = state1.serialize();
|
|
|
|
const state2 = new ThpState();
|
|
state2.deserialize(serializedState);
|
|
|
|
expect(state1.channel).toEqual(state2.channel);
|
|
expect(state1.sendBit).toEqual(state2.sendBit);
|
|
expect(state1.recvNonce).toEqual(state2.recvNonce);
|
|
expect(state1.expectedResponses).toEqual(state2.expectedResponses);
|
|
|
|
expect(state1.toString()).toMatch('{"channel":"4321"');
|
|
|
|
const s = serializedState;
|
|
const e = 'invalid state';
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize(null)).toThrow(e);
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize({})).toThrow(e);
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize({ ...s, expectedResponses: [null] })).toThrow(e);
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize({ ...s, sendBit: null })).toThrow(e);
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize({ ...s, recvBit: null })).toThrow(e);
|
|
// @ts-expect-error
|
|
expect(() => state2.deserialize({ ...s, channel: 11 })).toThrow(e);
|
|
});
|
|
|
|
it('ThpState update sync bit & nonce', () => {
|
|
const state = new ThpState();
|
|
state.updateSyncBit('send'); // set initial to 1
|
|
|
|
// rotate few times
|
|
for (let i = 0; i < 7; i++) {
|
|
// send process completed
|
|
state.updateSyncBit('send');
|
|
state.updateNonce('send');
|
|
// receive process completed
|
|
state.updateSyncBit('recv');
|
|
state.updateNonce('recv');
|
|
}
|
|
|
|
expect(state.serialize()).toMatchObject({
|
|
sendBit: 0,
|
|
recvBit: 1,
|
|
sendNonce: 7,
|
|
recvNonce: 8,
|
|
});
|
|
});
|
|
|
|
it('isAckExpected', () => {
|
|
thpState.setChannel(Buffer.from('1234', 'hex'));
|
|
|
|
// ThpCreateChannelRequest => ThpAck not expected
|
|
let msg = Buffer.from('40ffff000ceb7c85d5604bf4d7ad7bc634', 'hex');
|
|
expect(isAckExpected(msg)).toBe(false);
|
|
|
|
// ThpHandshakeInitRequest => ThpAck expected
|
|
msg = Buffer.from(
|
|
'001234002407070707070707070707070707070707a0a1a2a3a4a5a6a7a8a9b0b1b2b3b4b5d47b551c',
|
|
'hex',
|
|
);
|
|
expect(isAckExpected(msg)).toBe(true);
|
|
});
|
|
|
|
it('getExpectedResponses', () => {
|
|
thpState.setChannel(Buffer.from('1234', 'hex'));
|
|
|
|
// ThpCreateChannelRequest
|
|
expect(
|
|
getExpectedResponses(Buffer.from('40ffff000ceb7c85d5604bf4d7ad7bc634', 'hex')),
|
|
).toEqual([65]); // ThpCreateChannelResponse should start with 41ffff...
|
|
|
|
// unknown thp message type 33...
|
|
expect(getExpectedResponses(Buffer.from('33123400000', 'hex'))).toEqual([]);
|
|
});
|
|
});
|