From f2ffe2c53f375f3384546815fabbc2513e32cd9e Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Mon, 25 Mar 2024 09:45:22 +0100 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8=20(core):=20Add=20new=20Receive?= =?UTF-8?q?r=20Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device-session/data/ApduResponseConst.ts | 1 + .../device-session/model/ApduResponse.ts | 22 ++ .../internal/device-session/model/Frame.ts | 11 + .../device-session/model/FrameHeader.ts | 15 +- .../service/DefaultReceiverService.test.ts | 219 ++++++++++++++++++ .../service/DefaultReceiverService.ts | 137 +++++++++++ .../device-session/service/ReceiverService.ts | 7 + .../device-session/utils/FramerUtils.test.ts | 30 +++ .../device-session/utils/FramerUtils.ts | 9 + 9 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/internal/device-session/data/ApduResponseConst.ts create mode 100644 packages/core/src/internal/device-session/model/ApduResponse.ts create mode 100644 packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts create mode 100644 packages/core/src/internal/device-session/service/DefaultReceiverService.ts create mode 100644 packages/core/src/internal/device-session/service/ReceiverService.ts diff --git a/packages/core/src/internal/device-session/data/ApduResponseConst.ts b/packages/core/src/internal/device-session/data/ApduResponseConst.ts new file mode 100644 index 000000000..5496d8162 --- /dev/null +++ b/packages/core/src/internal/device-session/data/ApduResponseConst.ts @@ -0,0 +1 @@ +export const APDU_RESPONSE_STATUS_CODE_SIZE = 2; diff --git a/packages/core/src/internal/device-session/model/ApduResponse.ts b/packages/core/src/internal/device-session/model/ApduResponse.ts new file mode 100644 index 000000000..63d617ff0 --- /dev/null +++ b/packages/core/src/internal/device-session/model/ApduResponse.ts @@ -0,0 +1,22 @@ +type ApduResponseConstructorArgs = { + statusCode: Uint8Array; + data: Uint8Array; +}; + +export class ApduResponse { + protected _statusCode: Uint8Array; + protected _data: Uint8Array; + + constructor({ statusCode, data }: ApduResponseConstructorArgs) { + this._statusCode = statusCode; + this._data = data; + } + + public getStatusCode() { + return this._statusCode; + } + + public getData() { + return this._data; + } +} diff --git a/packages/core/src/internal/device-session/model/Frame.ts b/packages/core/src/internal/device-session/model/Frame.ts index e327168b4..353de03b1 100644 --- a/packages/core/src/internal/device-session/model/Frame.ts +++ b/packages/core/src/internal/device-session/model/Frame.ts @@ -8,10 +8,12 @@ type FrameConstructorArgs = { export class Frame { protected _header: FrameHeader; protected _data: Uint8Array; + constructor({ header, data }: FrameConstructorArgs) { this._header = header; this._data = data; } + toString(): string { return JSON.stringify( { @@ -22,6 +24,7 @@ export class Frame { 2, ); } + getRawData(): Uint8Array { const headerRaw = this._header.getRawData(); const raw = new Uint8Array(headerRaw.length + this._data.length); @@ -30,4 +33,12 @@ export class Frame { raw.set(this._data, headerRaw.length); return raw; } + + getHeader(): FrameHeader { + return this._header; + } + + getData(): Uint8Array { + return this._data; + } } diff --git a/packages/core/src/internal/device-session/model/FrameHeader.ts b/packages/core/src/internal/device-session/model/FrameHeader.ts index a386a9923..12aa40471 100644 --- a/packages/core/src/internal/device-session/model/FrameHeader.ts +++ b/packages/core/src/internal/device-session/model/FrameHeader.ts @@ -1,4 +1,4 @@ -import { Maybe } from "purify-ts"; +import { Maybe, Nothing } from "purify-ts"; type FrameHeaderConstructorArgs = { uuid: string; @@ -31,6 +31,19 @@ export class FrameHeader { this._length = length; this._channel = channel; } + getDataSize(): Maybe { + return this._dataSize.caseOf({ + Just: (value) => + Maybe.of( + value.reduce( + (acc, val, index) => + acc + val * Math.pow(0x100, value.length - 1 - index), + 0, + ), + ), + Nothing: () => Nothing, + }); + } setDataSize(dataSize: Maybe): FrameHeader { this._dataSize = dataSize; return this; diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts new file mode 100644 index 000000000..5333e33d3 --- /dev/null +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts @@ -0,0 +1,219 @@ +import * as uuid from "uuid"; +jest.mock("uuid"); + +import { Just, Nothing } from "purify-ts"; + +import { ApduResponse } from "@internal/device-session/model/ApduResponse"; +import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; + +import { DefaultReceiverService } from "./DefaultReceiverService"; +import { ReceiverService } from "./ReceiverService"; + +const loggerService = new DefaultLoggerPublisherService([], "frame"); + +const RESPONSE_GET_VERSION = new Uint8Array([ + 0xaa, 0xaa, 0x05, 0x00, 0x00, 0x00, 0x21, 0x33, 0x00, 0x00, 0x04, 0x05, 0x32, + 0x2e, 0x32, 0x2e, 0x33, 0x04, 0xe6, 0x00, 0x00, 0x00, 0x04, 0x32, 0x2e, 0x33, + 0x30, 0x04, 0x31, 0x2e, 0x31, 0x36, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x90, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]); + +const RESPONSE_LOCKED_DEVICE = new Uint8Array([ + 0xaa, 0xaa, 0x05, 0x00, 0x00, 0x00, 0x02, 0x55, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]); + +const RESPONSE_LIST_APPS = [ + new Uint8Array([ + 0xaa, 0xaa, 0x05, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x4d, 0x00, 0x13, 0xca, + 0x50, 0xfa, 0xa1, 0x91, 0x40, 0x9a, 0x6b, 0xfa, 0x6c, 0x0f, 0xbb, 0xb2, + 0xe7, 0xc4, 0xa9, 0xcf, 0xe5, 0x57, 0x41, 0x00, 0x5d, 0xbd, 0x84, 0xab, + 0x9a, 0xbd, 0x66, 0xc7, 0x6c, 0x90, 0xdd, 0x08, 0x79, 0x0d, 0x08, 0x47, + 0xb9, 0x3a, 0x8f, 0xa7, 0x6f, 0x60, 0x33, 0xae, 0xd3, 0x25, 0xd7, 0xb1, + 0xe5, 0x7c, 0xeb, 0xd7, + ]), + new Uint8Array([ + 0xaa, 0xaa, 0x05, 0x00, 0x01, 0x4b, 0x2e, 0x2c, 0x9f, 0xb4, 0x46, 0x78, + 0xde, 0x05, 0x5f, 0x9e, 0x80, 0x0a, 0x07, 0x42, 0x69, 0x74, 0x63, 0x6f, + 0x69, 0x6e, 0x4e, 0x00, 0x15, 0xca, 0x40, 0x06, 0x03, 0x28, 0xf8, 0x8f, + 0xc6, 0xd6, 0x42, 0x98, 0xd0, 0x49, 0x00, 0xc7, 0x04, 0x98, 0x19, 0x1b, + 0x6c, 0xeb, 0xed, 0xd8, 0xcb, 0x84, 0x5d, 0xf5, 0x4b, 0xe3, 0xbd, 0xbb, + 0x25, 0x7a, 0x3f, 0x6f, + ]), + new Uint8Array([ + 0xaa, 0xaa, 0x05, 0x00, 0x02, 0x68, 0x8f, 0x54, 0xef, 0x7f, 0xaa, 0xc4, + 0x22, 0xaa, 0x54, 0xe7, 0xb8, 0x0a, 0xc8, 0xa3, 0x2f, 0x96, 0xe5, 0x5e, + 0x43, 0x2d, 0xf3, 0xa3, 0x45, 0x8d, 0x8e, 0xaa, 0xf1, 0x4e, 0xd1, 0x1e, + 0x08, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x90, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]), +]; + +describe("DefaultReceiverService", () => { + let service: ReceiverService; + + beforeAll(() => { + jest.spyOn(uuid, "v4").mockReturnValue("42"); + }); + + describe("[USB] With padding and channel", () => { + beforeEach(() => { + service = new DefaultReceiverService( + { channel: Just(new Uint8Array([0xaa, 0xaa])) }, + () => loggerService, + ); + }); + + it("should return a response directly when a frame is complete", () => { + // given + const frame = RESPONSE_GET_VERSION; + + // when + const apdu = service.handleFrame(frame); + + // then + expect(apdu.isNothing()).toBeFalsy(); + expect(apdu.extract()).toEqual( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ); + }); + + it("should return a response on a third frame when the two first are not complete", () => { + // given + const firstFrame = RESPONSE_LIST_APPS[0]!; + const secondFrame = RESPONSE_LIST_APPS[1]!; + const thirdFrame = RESPONSE_LIST_APPS[2]!; + + // when + const firstResponse = service.handleFrame(firstFrame); + const secondResponse = service.handleFrame(secondFrame); + const thirdResponse = service.handleFrame(thirdFrame); + + // then + expect(firstResponse.isNothing()).toBeTruthy(); + expect(secondResponse.isNothing()).toBeTruthy(); + expect(thirdResponse.isNothing()).toBeFalsy(); + expect(thirdResponse.extract()?.getStatusCode()).toEqual( + new Uint8Array([0x90, 0x00]), + ); + expect(Array.from(thirdResponse.extract()?.getData() ?? [])).toEqual([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]); + }); + + it("should return two response directly when each frame is complete", () => { + // given + const firstFrame = RESPONSE_LOCKED_DEVICE; + const secondFrame = RESPONSE_GET_VERSION; + + // when + const firstResponse = service.handleFrame(firstFrame); + const secondResponse = service.handleFrame(secondFrame); + + // then + expect(firstResponse.isNothing()).toBeFalsy(); + expect(secondResponse.isNothing()).toBeFalsy(); + + expect(firstResponse.extract()).toEqual( + new ApduResponse({ + data: new Uint8Array([]), + statusCode: new Uint8Array([0x55, 0x15]), + }), + ); + expect(secondResponse.extract()).toEqual( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ); + }); + }); + + describe("[BLE] Without padding nor channel", () => { + beforeEach(() => { + service = new DefaultReceiverService({}, () => loggerService); + { channel: Nothing }, + () => loggerService, + ); + }); + + it("should return a response directly when a frame is complete", () => { + // given + const frame = RESPONSE_GET_VERSION.slice(2, 40); + + // when + const apdu = service.handleFrame(frame); + + // then + expect(apdu.isNothing()).toBeFalsy(); + expect(apdu.extract()).toEqual( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ); + }); + + it("should return a response on a third frame when the two first are not complete", () => { + // given + const firstFrame = RESPONSE_LIST_APPS[0]!.slice(2); + const secondFrame = RESPONSE_LIST_APPS[1]!.slice(2); + const thirdFrame = RESPONSE_LIST_APPS[2]!.slice(2, 47); + + // when + const firstResponse = service.handleFrame(firstFrame); + const secondResponse = service.handleFrame(secondFrame); + const thirdResponse = service.handleFrame(thirdFrame); + + // then + expect(firstResponse.isNothing()).toBeTruthy(); + expect(secondResponse.isNothing()).toBeTruthy(); + expect(thirdResponse.isNothing()).toBeFalsy(); + expect(thirdResponse.extract()?.getStatusCode()).toEqual( + new Uint8Array([0x90, 0x00]), + ); + expect(Array.from(thirdResponse.extract()?.getData() ?? [])).toEqual([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]); + }); + + it("should return two response directly when each frame is complete", () => { + // given + const firstFrame = RESPONSE_LOCKED_DEVICE.slice(2, 9); + const secondFrame = RESPONSE_GET_VERSION.slice(2, 40); + + // when + const firstResponse = service.handleFrame(firstFrame); + const secondResponse = service.handleFrame(secondFrame); + + // then + expect(firstResponse.isNothing()).toBeFalsy(); + expect(secondResponse.isNothing()).toBeFalsy(); + + expect(firstResponse.extract()).toEqual( + new ApduResponse({ + data: new Uint8Array([]), + statusCode: new Uint8Array([0x55, 0x15]), + }), + ); + expect(secondResponse.extract()).toEqual( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ); + }); + }); +}); diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts new file mode 100644 index 000000000..4e4a5aa0b --- /dev/null +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -0,0 +1,137 @@ +import { inject, injectable } from "inversify"; +import { Just, Maybe, Nothing } from "purify-ts"; +import { v4 } from "uuid"; + +import { APDU_RESPONSE_STATUS_CODE_SIZE } from "@internal/device-session/data/ApduResponseConst"; +import { + APDU_DATA_SIZE, + CHANNEL_SIZE, + HEAD_TAG_SIZE, + INDEX_SIZE, +} from "@internal/device-session/data/FramerConst"; +import { ApduResponse } from "@internal/device-session/model/ApduResponse"; +import { Frame } from "@internal/device-session/model/Frame"; +import { FrameHeader } from "@internal/device-session/model/FrameHeader"; +import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; +import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; +import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; + +import { ReceiverService } from "./ReceiverService"; + +type DefaultReceiverConstructorArgs = { + channel?: Maybe; +}; + +@injectable() +export class DefaultReceiverService implements ReceiverService { + private _channel: Maybe; + private _logger: LoggerPublisherService; + private _pendingFrames: Frame[]; + + constructor( + { channel = Maybe.zero() }: DefaultReceiverConstructorArgs, + @inject(loggerTypes.LoggerPublisherServiceFactory) + loggerModuleFactory: (tag: string) => LoggerPublisherService, + ) { + this._channel = channel; + this._logger = loggerModuleFactory("receiver"); + this._pendingFrames = []; + } + + public handleFrame(apdu: Uint8Array): Maybe { + const frame = this.parseApdu(apdu); + this._logger.debug("handle frame", { data: { frame } }); + this._pendingFrames.push(frame); + + const dataSize = this._pendingFrames[0]!.getHeader() + .getDataSize() + .caseOf({ + Just: (value) => value, + Nothing: () => { + this._logger.error("unable to get size"); + throw new Error(); + }, + }); + + if (this.isComplete(dataSize)) { + const concatenatedFramesData = FramerUtils.getFirstBytesFrom( + this.concatFrames(this._pendingFrames), + dataSize, + ); + const data = FramerUtils.getFirstBytesFrom( + concatenatedFramesData, + concatenatedFramesData.length - APDU_RESPONSE_STATUS_CODE_SIZE, + ); + const statusCode = FramerUtils.getLastBytesFrom( + concatenatedFramesData, + APDU_RESPONSE_STATUS_CODE_SIZE, + ); + + this._pendingFrames = []; + + return Just( + new ApduResponse({ + data: data, + statusCode, + }), + ); + } + + this._logger.debug("frame is not complete, waiting for more"); + return Maybe.empty(); + } + + private parseApdu(apdu: Uint8Array): Frame { + const channelSize = this._channel.caseOf({ + Just: () => CHANNEL_SIZE, + Nothing: () => 0, + }); + + const headTag = apdu.slice(channelSize, channelSize + HEAD_TAG_SIZE); + const index = apdu.slice( + channelSize + HEAD_TAG_SIZE, + channelSize + HEAD_TAG_SIZE + INDEX_SIZE, + ); + + const isFirstIndex = index.reduce((curr, val) => curr + val, 0) === 0; + const dataSizeIndex = channelSize + HEAD_TAG_SIZE + INDEX_SIZE; + const dataSizeLength = isFirstIndex ? APDU_DATA_SIZE : 0; + const dataSize = isFirstIndex + ? Just(apdu.slice(dataSizeIndex, dataSizeIndex + dataSizeLength)) + : Nothing; + + const dataIndex = dataSizeIndex + dataSizeLength; + const data = apdu.slice(dataIndex); + + const frame = new Frame({ + header: new FrameHeader({ + uuid: v4(), + channel: this._channel, + dataSize, + headTag, + index, + length: channelSize + HEAD_TAG_SIZE + INDEX_SIZE + dataSizeLength, + }), + data, + }); + + return frame; + } + + private isComplete(dataSize: number): boolean { + const totalReceiveLength = this._pendingFrames.reduce( + (prev: number, curr: Frame) => prev + curr.getData().length, + 0, + ); + + return totalReceiveLength >= dataSize; + } + + private concatFrames(frames: Frame[]): Uint8Array { + return frames.reduce( + (prev: Uint8Array, curr: Frame) => + Uint8Array.from([...prev, ...curr.getData()]), + new Uint8Array(0), + ); + } +} diff --git a/packages/core/src/internal/device-session/service/ReceiverService.ts b/packages/core/src/internal/device-session/service/ReceiverService.ts new file mode 100644 index 000000000..ff9fc9ffc --- /dev/null +++ b/packages/core/src/internal/device-session/service/ReceiverService.ts @@ -0,0 +1,7 @@ +import { Maybe } from "purify-ts"; + +import { ApduResponse } from "@internal/device-session/model/ApduResponse"; + +export interface ReceiverService { + handleFrame(apdu: Uint8Array): Maybe; +} diff --git a/packages/core/src/internal/device-session/utils/FramerUtils.test.ts b/packages/core/src/internal/device-session/utils/FramerUtils.test.ts index 8517d9c4c..fc8cf3300 100644 --- a/packages/core/src/internal/device-session/utils/FramerUtils.test.ts +++ b/packages/core/src/internal/device-session/utils/FramerUtils.test.ts @@ -30,4 +30,34 @@ describe("FramerUtils", () => { expect(result).toEqual(new Uint8Array([])); }); }); + + describe("getLastBytesFrom", () => { + it("should return 2 same bytes of Uint8Array", () => { + // Arrange + const array = new Uint8Array([0x67, 0x89]); + const size = 2; + // Act + const result = FramerUtils.getFirstBytesFrom(array, size); + // Assert + expect(result).toEqual(new Uint8Array([0x67, 0x89])); + }); + it("should return 2 first bytes of Uint8Array", () => { + // Arrange + const array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const size = 2; + // Act + const result = FramerUtils.getFirstBytesFrom(array, size); + // Assert + expect(result).toEqual(new Uint8Array([1, 2])); + }); + it("should return empty Uint8Array", () => { + // Arrange + const array = new Uint8Array([]); + const size = 2; + // Act + const result = FramerUtils.getLastBytesFrom(array, size); + // Assert + expect(result).toEqual(new Uint8Array([])); + }); + }); }); diff --git a/packages/core/src/internal/device-session/utils/FramerUtils.ts b/packages/core/src/internal/device-session/utils/FramerUtils.ts index 6d55b6105..2feebe05e 100644 --- a/packages/core/src/internal/device-session/utils/FramerUtils.ts +++ b/packages/core/src/internal/device-session/utils/FramerUtils.ts @@ -7,4 +7,13 @@ export const FramerUtils = { getLastBytesFrom(array: Uint8Array, size: number): Uint8Array { return new Uint8Array(array.slice(-size)); }, + + /* + * Get first bytes of Uint8Array + * + * @param Uint8Array + */ + getFirstBytesFrom(array: Uint8Array, size: number): Uint8Array { + return new Uint8Array(array.slice(0, size)); + }, }; From f056596b7f2f06a2bcb02030341e32c1e2764afa Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Mon, 25 Mar 2024 11:00:57 +0100 Subject: [PATCH 02/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(core):=20Return=20e?= =?UTF-8?q?ither=20error=20or=20maybe=20apdu=20response?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/device-session/model/Errors.ts | 9 + .../service/DefaultReceiverService.test.ts | 168 +++++++++++------- .../service/DefaultReceiverService.ts | 39 ++-- .../device-session/service/ReceiverService.ts | 5 +- 4 files changed, 137 insertions(+), 84 deletions(-) diff --git a/packages/core/src/internal/device-session/model/Errors.ts b/packages/core/src/internal/device-session/model/Errors.ts index 33b4b6b05..e2e00db9c 100644 --- a/packages/core/src/internal/device-session/model/Errors.ts +++ b/packages/core/src/internal/device-session/model/Errors.ts @@ -19,6 +19,15 @@ export class FramerApduError implements SdkError { } } +export class ReceiverApduError implements SdkError { + readonly _tag = "ReceiverApduError"; + originalError?: Error; + + constructor() { + this.originalError = new Error("Unable to parse apdu"); + } +} + export class DeviceSessionNotFound implements SdkError { readonly _tag = "DeviceSessionNotFound"; originalError?: Error; diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts index 5333e33d3..642c10ce4 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts @@ -1,9 +1,10 @@ import * as uuid from "uuid"; jest.mock("uuid"); -import { Just, Nothing } from "purify-ts"; +import { Just, Left, Nothing, Right } from "purify-ts"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; +import { ReceiverApduError } from "@internal/device-session/model/Errors"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; import { DefaultReceiverService } from "./DefaultReceiverService"; @@ -61,6 +62,26 @@ describe("DefaultReceiverService", () => { jest.spyOn(uuid, "v4").mockReturnValue("42"); }); + describe("without dataSize", () => { + beforeEach(() => { + service = new DefaultReceiverService( + { channel: Just(new Uint8Array([0xaa, 0xaa])) }, + () => loggerService, + ); + }); + + it("should return a left error when the first frame has no dataSize", () => { + //given + const frame = RESPONSE_LIST_APPS[1]!; + + //when + const apdu = service.handleFrame(frame); + + //then + expect(apdu).toEqual(Left(new ReceiverApduError())); + }); + }); + describe("[USB] With padding and channel", () => { beforeEach(() => { service = new DefaultReceiverService( @@ -77,12 +98,15 @@ describe("DefaultReceiverService", () => { const apdu = service.handleFrame(frame); // then - expect(apdu.isNothing()).toBeFalsy(); - expect(apdu.extract()).toEqual( - new ApduResponse({ - data: RESPONSE_GET_VERSION.slice(7, 38), - statusCode: new Uint8Array([0x90, 0x00]), - }), + expect(apdu).toEqual( + Right( + Just( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); }); @@ -98,17 +122,22 @@ describe("DefaultReceiverService", () => { const thirdResponse = service.handleFrame(thirdFrame); // then - expect(firstResponse.isNothing()).toBeTruthy(); - expect(secondResponse.isNothing()).toBeTruthy(); - expect(thirdResponse.isNothing()).toBeFalsy(); - expect(thirdResponse.extract()?.getStatusCode()).toEqual( - new Uint8Array([0x90, 0x00]), + expect(firstResponse).toEqual(Right(Nothing)); + expect(secondResponse).toEqual(Right(Nothing)); + expect(thirdResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: new Uint8Array([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); - expect(Array.from(thirdResponse.extract()?.getData() ?? [])).toEqual([ - ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), - ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), - ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), - ]); }); it("should return two response directly when each frame is complete", () => { @@ -121,20 +150,25 @@ describe("DefaultReceiverService", () => { const secondResponse = service.handleFrame(secondFrame); // then - expect(firstResponse.isNothing()).toBeFalsy(); - expect(secondResponse.isNothing()).toBeFalsy(); - - expect(firstResponse.extract()).toEqual( - new ApduResponse({ - data: new Uint8Array([]), - statusCode: new Uint8Array([0x55, 0x15]), - }), + expect(firstResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: new Uint8Array([]), + statusCode: new Uint8Array([0x55, 0x15]), + }), + ), + ), ); - expect(secondResponse.extract()).toEqual( - new ApduResponse({ - data: RESPONSE_GET_VERSION.slice(7, 38), - statusCode: new Uint8Array([0x90, 0x00]), - }), + expect(secondResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); }); }); @@ -142,9 +176,6 @@ describe("DefaultReceiverService", () => { describe("[BLE] Without padding nor channel", () => { beforeEach(() => { service = new DefaultReceiverService({}, () => loggerService); - { channel: Nothing }, - () => loggerService, - ); }); it("should return a response directly when a frame is complete", () => { @@ -155,12 +186,15 @@ describe("DefaultReceiverService", () => { const apdu = service.handleFrame(frame); // then - expect(apdu.isNothing()).toBeFalsy(); - expect(apdu.extract()).toEqual( - new ApduResponse({ - data: RESPONSE_GET_VERSION.slice(7, 38), - statusCode: new Uint8Array([0x90, 0x00]), - }), + expect(apdu).toEqual( + Right( + Just( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); }); @@ -176,17 +210,22 @@ describe("DefaultReceiverService", () => { const thirdResponse = service.handleFrame(thirdFrame); // then - expect(firstResponse.isNothing()).toBeTruthy(); - expect(secondResponse.isNothing()).toBeTruthy(); - expect(thirdResponse.isNothing()).toBeFalsy(); - expect(thirdResponse.extract()?.getStatusCode()).toEqual( - new Uint8Array([0x90, 0x00]), + expect(firstResponse).toEqual(Right(Nothing)); + expect(secondResponse).toEqual(Right(Nothing)); + expect(thirdResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: new Uint8Array([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); - expect(Array.from(thirdResponse.extract()?.getData() ?? [])).toEqual([ - ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), - ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), - ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), - ]); }); it("should return two response directly when each frame is complete", () => { @@ -199,20 +238,25 @@ describe("DefaultReceiverService", () => { const secondResponse = service.handleFrame(secondFrame); // then - expect(firstResponse.isNothing()).toBeFalsy(); - expect(secondResponse.isNothing()).toBeFalsy(); - - expect(firstResponse.extract()).toEqual( - new ApduResponse({ - data: new Uint8Array([]), - statusCode: new Uint8Array([0x55, 0x15]), - }), + expect(firstResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: new Uint8Array([]), + statusCode: new Uint8Array([0x55, 0x15]), + }), + ), + ), ); - expect(secondResponse.extract()).toEqual( - new ApduResponse({ - data: RESPONSE_GET_VERSION.slice(7, 38), - statusCode: new Uint8Array([0x90, 0x00]), - }), + expect(secondResponse).toEqual( + Right( + Just( + new ApduResponse({ + data: RESPONSE_GET_VERSION.slice(7, 38), + statusCode: new Uint8Array([0x90, 0x00]), + }), + ), + ), ); }); }); diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts index 4e4a5aa0b..042615936 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -1,5 +1,5 @@ import { inject, injectable } from "inversify"; -import { Just, Maybe, Nothing } from "purify-ts"; +import { Either, Just, Left, Maybe, Nothing, Right } from "purify-ts"; import { v4 } from "uuid"; import { APDU_RESPONSE_STATUS_CODE_SIZE } from "@internal/device-session/data/ApduResponseConst"; @@ -10,6 +10,7 @@ import { INDEX_SIZE, } from "@internal/device-session/data/FramerConst"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; +import { ReceiverApduError } from "@internal/device-session/model/Errors"; import { Frame } from "@internal/device-session/model/Frame"; import { FrameHeader } from "@internal/device-session/model/FrameHeader"; import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; @@ -38,25 +39,21 @@ export class DefaultReceiverService implements ReceiverService { this._pendingFrames = []; } - public handleFrame(apdu: Uint8Array): Maybe { + public handleFrame( + apdu: Uint8Array, + ): Either> { const frame = this.parseApdu(apdu); this._logger.debug("handle frame", { data: { frame } }); this._pendingFrames.push(frame); - const dataSize = this._pendingFrames[0]!.getHeader() - .getDataSize() - .caseOf({ - Just: (value) => value, - Nothing: () => { - this._logger.error("unable to get size"); - throw new Error(); - }, - }); - - if (this.isComplete(dataSize)) { + const dataSize = this._pendingFrames[0]!.getHeader().getDataSize(); + + if (dataSize.isNothing()) return Left(new ReceiverApduError()); + + if (dataSize.isJust() && this.isComplete(dataSize.extract())) { const concatenatedFramesData = FramerUtils.getFirstBytesFrom( this.concatFrames(this._pendingFrames), - dataSize, + dataSize.extract(), ); const data = FramerUtils.getFirstBytesFrom( concatenatedFramesData, @@ -69,16 +66,18 @@ export class DefaultReceiverService implements ReceiverService { this._pendingFrames = []; - return Just( - new ApduResponse({ - data: data, - statusCode, - }), + return Right( + Just( + new ApduResponse({ + data: data, + statusCode, + }), + ), ); } this._logger.debug("frame is not complete, waiting for more"); - return Maybe.empty(); + return Right(Nothing); } private parseApdu(apdu: Uint8Array): Frame { diff --git a/packages/core/src/internal/device-session/service/ReceiverService.ts b/packages/core/src/internal/device-session/service/ReceiverService.ts index ff9fc9ffc..dd496b446 100644 --- a/packages/core/src/internal/device-session/service/ReceiverService.ts +++ b/packages/core/src/internal/device-session/service/ReceiverService.ts @@ -1,7 +1,8 @@ -import { Maybe } from "purify-ts"; +import { Either, Maybe } from "purify-ts"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; +import { ReceiverApduError } from "@internal/device-session/model/Errors"; export interface ReceiverService { - handleFrame(apdu: Uint8Array): Maybe; + handleFrame(apdu: Uint8Array): Either>; } From 254091c6a321428116ee9d049b1c2e7b1eec3d6e Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Tue, 26 Mar 2024 13:44:25 +0100 Subject: [PATCH 03/11] =?UTF-8?q?=F0=9F=A9=B9=20(core):=20Handle=20non=20p?= =?UTF-8?q?roperly=20formatted=20apdu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DefaultReceiverService.test.ts | 50 +++++++++++++++++++ .../service/DefaultReceiverService.ts | 17 +++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts index 642c10ce4..c9fb25ad1 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts @@ -171,6 +171,31 @@ describe("DefaultReceiverService", () => { ), ); }); + + it("should return an error if the frame is not complete", () => { + //given + //frame with channelId, headTag, and partial Index only + const frame = RESPONSE_LIST_APPS[0]!.slice(0, 4); + + //when + const apdu = service.handleFrame(frame); + + //then + expect(apdu).toEqual(Left(new ReceiverApduError())); + }); + + it("should return an error if the frame is not complete", () => { + //given + //frame with channelId, headTag, and Index and partial dataSize only + const frame = RESPONSE_LIST_APPS[0]!.slice(0, 6); + console.log(frame); + + //when + const apdu = service.handleFrame(frame); + + //then + expect(apdu).toEqual(Left(new ReceiverApduError())); + }); }); describe("[BLE] Without padding nor channel", () => { @@ -259,5 +284,30 @@ describe("DefaultReceiverService", () => { ), ); }); + + it("should return an error if the frame is not complete", () => { + //given + //frame with channelId, headTag, and partial Index only + const frame = RESPONSE_LIST_APPS[0]!.slice(2, 4); + + //when + const apdu = service.handleFrame(frame); + + //then + expect(apdu).toEqual(Left(new ReceiverApduError())); + }); + + it("should return an error if the frame is not complete", () => { + //given + //frame with channelId, headTag, and Index and partial dataSize only + const frame = RESPONSE_LIST_APPS[0]!.slice(2, 6); + console.log(frame); + + //when + const apdu = service.handleFrame(frame); + + //then + expect(apdu).toEqual(Left(new ReceiverApduError())); + }); }); }); diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts index 042615936..f42dbfb77 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -43,8 +43,11 @@ export class DefaultReceiverService implements ReceiverService { apdu: Uint8Array, ): Either> { const frame = this.parseApdu(apdu); + + if (frame.isLeft()) return frame; + this._logger.debug("handle frame", { data: { frame } }); - this._pendingFrames.push(frame); + this._pendingFrames.push(frame.extract() as Frame); const dataSize = this._pendingFrames[0]!.getHeader().getDataSize(); @@ -80,7 +83,7 @@ export class DefaultReceiverService implements ReceiverService { return Right(Nothing); } - private parseApdu(apdu: Uint8Array): Frame { + private parseApdu(apdu: Uint8Array): Either { const channelSize = this._channel.caseOf({ Just: () => CHANNEL_SIZE, Nothing: () => 0, @@ -95,6 +98,14 @@ export class DefaultReceiverService implements ReceiverService { const isFirstIndex = index.reduce((curr, val) => curr + val, 0) === 0; const dataSizeIndex = channelSize + HEAD_TAG_SIZE + INDEX_SIZE; const dataSizeLength = isFirstIndex ? APDU_DATA_SIZE : 0; + + if ( + apdu.length < + channelSize + HEAD_TAG_SIZE + INDEX_SIZE + dataSizeLength + ) { + return Left(new ReceiverApduError()); + } + const dataSize = isFirstIndex ? Just(apdu.slice(dataSizeIndex, dataSizeIndex + dataSizeLength)) : Nothing; @@ -114,7 +125,7 @@ export class DefaultReceiverService implements ReceiverService { data, }); - return frame; + return Right(frame); } private isComplete(dataSize: number): boolean { From 8fce1cb2db033bbfdc11da5a03003e47870df029 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Wed, 27 Mar 2024 17:27:59 +0100 Subject: [PATCH 04/11] =?UTF-8?q?=F0=9F=8E=A8=20(core):=20Rename=20parseAp?= =?UTF-8?q?du=20to=20apduToFrame?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/device-session/service/DefaultReceiverService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts index f42dbfb77..6d6946630 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -42,7 +42,7 @@ export class DefaultReceiverService implements ReceiverService { public handleFrame( apdu: Uint8Array, ): Either> { - const frame = this.parseApdu(apdu); + const frame = this.apduToFrame(apdu); if (frame.isLeft()) return frame; @@ -83,7 +83,7 @@ export class DefaultReceiverService implements ReceiverService { return Right(Nothing); } - private parseApdu(apdu: Uint8Array): Either { + private apduToFrame(apdu: Uint8Array): Either { const channelSize = this._channel.caseOf({ Just: () => CHANNEL_SIZE, Nothing: () => 0, From 69a8c9647955c1aeb2ec8804bb0850174cca5fad Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Wed, 27 Mar 2024 17:29:52 +0100 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=A9=B9=20(core):=20Return=20specifi?= =?UTF-8?q?c=20error=20when=20unable=20to=20parse=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/device-session/model/Errors.ts | 6 ++--- .../service/DefaultReceiverService.test.ts | 26 ++++++++++++------- .../service/DefaultReceiverService.ts | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/core/src/internal/device-session/model/Errors.ts b/packages/core/src/internal/device-session/model/Errors.ts index e2e00db9c..835ac143c 100644 --- a/packages/core/src/internal/device-session/model/Errors.ts +++ b/packages/core/src/internal/device-session/model/Errors.ts @@ -21,10 +21,10 @@ export class FramerApduError implements SdkError { export class ReceiverApduError implements SdkError { readonly _tag = "ReceiverApduError"; - originalError?: Error; + originalError: Error; - constructor() { - this.originalError = new Error("Unable to parse apdu"); + constructor(message?: string) { + this.originalError = new Error(message ?? "Unable to parse apdu"); } } diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts index c9fb25ad1..eaa1d170f 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts @@ -172,7 +172,7 @@ describe("DefaultReceiverService", () => { ); }); - it("should return an error if the frame is not complete", () => { + it("should return an error if the frame is without index", () => { //given //frame with channelId, headTag, and partial Index only const frame = RESPONSE_LIST_APPS[0]!.slice(0, 4); @@ -181,20 +181,23 @@ describe("DefaultReceiverService", () => { const apdu = service.handleFrame(frame); //then - expect(apdu).toEqual(Left(new ReceiverApduError())); + expect(apdu).toEqual( + Left(new ReceiverApduError("Unable to parse header from apdu")), + ); }); - it("should return an error if the frame is not complete", () => { + it("should return an error if the frame is without datasize", () => { //given //frame with channelId, headTag, and Index and partial dataSize only const frame = RESPONSE_LIST_APPS[0]!.slice(0, 6); - console.log(frame); //when const apdu = service.handleFrame(frame); //then - expect(apdu).toEqual(Left(new ReceiverApduError())); + expect(apdu).toEqual( + Left(new ReceiverApduError("Unable to parse header from apdu")), + ); }); }); @@ -285,7 +288,7 @@ describe("DefaultReceiverService", () => { ); }); - it("should return an error if the frame is not complete", () => { + it("should return an error if the frame is without index", () => { //given //frame with channelId, headTag, and partial Index only const frame = RESPONSE_LIST_APPS[0]!.slice(2, 4); @@ -294,20 +297,23 @@ describe("DefaultReceiverService", () => { const apdu = service.handleFrame(frame); //then - expect(apdu).toEqual(Left(new ReceiverApduError())); + expect(apdu).toEqual( + Left(new ReceiverApduError("Unable to parse header from apdu")), + ); }); - it("should return an error if the frame is not complete", () => { + it("should return an error if the frame is without datasize", () => { //given //frame with channelId, headTag, and Index and partial dataSize only const frame = RESPONSE_LIST_APPS[0]!.slice(2, 6); - console.log(frame); //when const apdu = service.handleFrame(frame); //then - expect(apdu).toEqual(Left(new ReceiverApduError())); + expect(apdu).toEqual( + Left(new ReceiverApduError("Unable to parse header from apdu")), + ); }); }); }); diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts index 6d6946630..e88dd84c7 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -103,7 +103,7 @@ export class DefaultReceiverService implements ReceiverService { apdu.length < channelSize + HEAD_TAG_SIZE + INDEX_SIZE + dataSizeLength ) { - return Left(new ReceiverApduError()); + return Left(new ReceiverApduError("Unable to parse header from apdu")); } const dataSize = isFirstIndex From 439ff22a036ab7664aebf02fd01c2e8f886c0b9e Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Wed, 27 Mar 2024 17:30:32 +0100 Subject: [PATCH 06/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(core):=20Update=20T?= =?UTF-8?q?U=20with=20better=20purify=20syntax?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DefaultReceiverService.test.ts | 110 ++++++++++-------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts index eaa1d170f..f9e4b0e8d 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts @@ -1,7 +1,7 @@ import * as uuid from "uuid"; jest.mock("uuid"); -import { Just, Left, Nothing, Right } from "purify-ts"; +import { Just, Left, Maybe, Nothing, Right } from "purify-ts"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; @@ -111,33 +111,37 @@ describe("DefaultReceiverService", () => { }); it("should return a response on a third frame when the two first are not complete", () => { - // given - const firstFrame = RESPONSE_LIST_APPS[0]!; - const secondFrame = RESPONSE_LIST_APPS[1]!; - const thirdFrame = RESPONSE_LIST_APPS[2]!; + //given + const apdus = [...RESPONSE_LIST_APPS]; - // when - const firstResponse = service.handleFrame(firstFrame); - const secondResponse = service.handleFrame(secondFrame); - const thirdResponse = service.handleFrame(thirdFrame); + //when + let response: Maybe = Nothing; + const responses: Maybe[] = []; + while (response.isNothing()) { + const apdu = apdus.shift()!; + const either = service.handleFrame(apdu); + + either.map((value) => { + response = value; + responses.push(response); + }); + } - // then - expect(firstResponse).toEqual(Right(Nothing)); - expect(secondResponse).toEqual(Right(Nothing)); - expect(thirdResponse).toEqual( - Right( - Just( - new ApduResponse({ - data: new Uint8Array([ - ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), - ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), - ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), - ]), - statusCode: new Uint8Array([0x90, 0x00]), - }), - ), + //then + expect(responses).toEqual([ + Nothing, + Nothing, + Just( + new ApduResponse({ + data: new Uint8Array([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]), + statusCode: new Uint8Array([0x90, 0x00]), + }), ), - ); + ]); }); it("should return two response directly when each frame is complete", () => { @@ -227,33 +231,41 @@ describe("DefaultReceiverService", () => { }); it("should return a response on a third frame when the two first are not complete", () => { - // given - const firstFrame = RESPONSE_LIST_APPS[0]!.slice(2); - const secondFrame = RESPONSE_LIST_APPS[1]!.slice(2); - const thirdFrame = RESPONSE_LIST_APPS[2]!.slice(2, 47); + //given + const apdus = [ + RESPONSE_LIST_APPS[0]!.slice(2), + RESPONSE_LIST_APPS[1]!.slice(2), + RESPONSE_LIST_APPS[2]!.slice(2, 47), + ]; - // when - const firstResponse = service.handleFrame(firstFrame); - const secondResponse = service.handleFrame(secondFrame); - const thirdResponse = service.handleFrame(thirdFrame); + //when + let response: Maybe = Nothing; + const responses: Maybe[] = []; + while (response.isNothing()) { + const apdu = apdus.shift()!; + const either = service.handleFrame(apdu); + + either.map((value) => { + response = value; + responses.push(response); + }); + } - // then - expect(firstResponse).toEqual(Right(Nothing)); - expect(secondResponse).toEqual(Right(Nothing)); - expect(thirdResponse).toEqual( - Right( - Just( - new ApduResponse({ - data: new Uint8Array([ - ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), - ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), - ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), - ]), - statusCode: new Uint8Array([0x90, 0x00]), - }), - ), + //then + expect(responses).toEqual([ + Nothing, + Nothing, + Just( + new ApduResponse({ + data: new Uint8Array([ + ...Array.from(RESPONSE_LIST_APPS[0]!.slice(7)), + ...Array.from(RESPONSE_LIST_APPS[1]!.slice(5)), + ...Array.from(RESPONSE_LIST_APPS[2]!).slice(5, 45), + ]), + statusCode: new Uint8Array([0x90, 0x00]), + }), ), - ); + ]); }); it("should return two response directly when each frame is complete", () => { From 80053bddfe69d33340355462fd3ef95c4e2ec2a8 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 28 Mar 2024 10:03:10 +0100 Subject: [PATCH 07/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(core):=20Remove=20a?= =?UTF-8?q?ll=20extract=20from=20receiver=20default=20implem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Only apduToFrame can throw ReceiverApduError - Split handleFrame with getCompleteFrame to handle only maybe case --- .../service/DefaultReceiverService.ts | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts index e88dd84c7..3d8f46fd9 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultReceiverService.ts @@ -39,24 +39,45 @@ export class DefaultReceiverService implements ReceiverService { this._pendingFrames = []; } + /* + * Return + * - A complete ApduResponse + * - Or a Nothing if not all the data has been received + * - Or a ReceiverApduError if the apdu is not formatted correctly + * + * @param Uint8Array + */ public handleFrame( apdu: Uint8Array, ): Either> { const frame = this.apduToFrame(apdu); - if (frame.isLeft()) return frame; + return frame.map((value) => { + this._logger.debug("handle frame", { data: { frame } }); + this._pendingFrames.push(value); - this._logger.debug("handle frame", { data: { frame } }); - this._pendingFrames.push(frame.extract() as Frame); - - const dataSize = this._pendingFrames[0]!.getHeader().getDataSize(); + const dataSize = this._pendingFrames[0]!.getHeader().getDataSize(); + return this.getCompleteFrame(dataSize); + }); + } - if (dataSize.isNothing()) return Left(new ReceiverApduError()); + /* + * Return + * - A complete ApduResponse + * - Or a Nothing if not all the data has been received + * + * @param Maybe + */ + private getCompleteFrame(dataSize: Maybe): Maybe { + return dataSize.chain((value) => { + if (!this.isComplete(value)) { + this._logger.debug("frame is not complete, waiting for more"); + return Nothing; + } - if (dataSize.isJust() && this.isComplete(dataSize.extract())) { const concatenatedFramesData = FramerUtils.getFirstBytesFrom( this.concatFrames(this._pendingFrames), - dataSize.extract(), + value, ); const data = FramerUtils.getFirstBytesFrom( concatenatedFramesData, @@ -69,18 +90,13 @@ export class DefaultReceiverService implements ReceiverService { this._pendingFrames = []; - return Right( - Just( - new ApduResponse({ - data: data, - statusCode, - }), - ), + return Just( + new ApduResponse({ + data: data, + statusCode, + }), ); - } - - this._logger.debug("frame is not complete, waiting for more"); - return Right(Nothing); + }); } private apduToFrame(apdu: Uint8Array): Either { @@ -96,6 +112,11 @@ export class DefaultReceiverService implements ReceiverService { ); const isFirstIndex = index.reduce((curr, val) => curr + val, 0) === 0; + + if (!isFirstIndex && this._pendingFrames.length === 0) { + return Left(new ReceiverApduError()); + } + const dataSizeIndex = channelSize + HEAD_TAG_SIZE + INDEX_SIZE; const dataSizeLength = isFirstIndex ? APDU_DATA_SIZE : 0; From 30da7e4a5c85ada02d7ada9c9b43fe2f24d9ab67 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 28 Mar 2024 10:08:40 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=8E=A8=20(core):=20Rename=20Receive?= =?UTF-8?q?rService=20to=20ApduReceiverService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ReceiverService.ts => ApduReceiverService.ts} | 2 +- ....test.ts => DefaultApduReceiverService.test.ts} | 14 +++++++------- ...verService.ts => DefaultApduReceiverService.ts} | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename packages/core/src/internal/device-session/service/{ReceiverService.ts => ApduReceiverService.ts} (87%) rename packages/core/src/internal/device-session/service/{DefaultReceiverService.test.ts => DefaultApduReceiverService.test.ts} (96%) rename packages/core/src/internal/device-session/service/{DefaultReceiverService.ts => DefaultApduReceiverService.ts} (97%) diff --git a/packages/core/src/internal/device-session/service/ReceiverService.ts b/packages/core/src/internal/device-session/service/ApduReceiverService.ts similarity index 87% rename from packages/core/src/internal/device-session/service/ReceiverService.ts rename to packages/core/src/internal/device-session/service/ApduReceiverService.ts index dd496b446..42ba0d86e 100644 --- a/packages/core/src/internal/device-session/service/ReceiverService.ts +++ b/packages/core/src/internal/device-session/service/ApduReceiverService.ts @@ -3,6 +3,6 @@ import { Either, Maybe } from "purify-ts"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; -export interface ReceiverService { +export interface ApduReceiverService { handleFrame(apdu: Uint8Array): Either>; } diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.test.ts similarity index 96% rename from packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts rename to packages/core/src/internal/device-session/service/DefaultApduReceiverService.test.ts index f9e4b0e8d..d8a603273 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.test.ts +++ b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.test.ts @@ -7,8 +7,8 @@ import { ApduResponse } from "@internal/device-session/model/ApduResponse"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; import { DefaultLoggerPublisherService } from "@internal/logger-publisher/service/DefaultLoggerPublisherService"; -import { DefaultReceiverService } from "./DefaultReceiverService"; -import { ReceiverService } from "./ReceiverService"; +import { ApduReceiverService } from "./ApduReceiverService"; +import { DefaultApduReceiverService } from "./DefaultApduReceiverService"; const loggerService = new DefaultLoggerPublisherService([], "frame"); @@ -55,8 +55,8 @@ const RESPONSE_LIST_APPS = [ ]), ]; -describe("DefaultReceiverService", () => { - let service: ReceiverService; +describe("DefaultApduReceiverService", () => { + let service: ApduReceiverService; beforeAll(() => { jest.spyOn(uuid, "v4").mockReturnValue("42"); @@ -64,7 +64,7 @@ describe("DefaultReceiverService", () => { describe("without dataSize", () => { beforeEach(() => { - service = new DefaultReceiverService( + service = new DefaultApduReceiverService( { channel: Just(new Uint8Array([0xaa, 0xaa])) }, () => loggerService, ); @@ -84,7 +84,7 @@ describe("DefaultReceiverService", () => { describe("[USB] With padding and channel", () => { beforeEach(() => { - service = new DefaultReceiverService( + service = new DefaultApduReceiverService( { channel: Just(new Uint8Array([0xaa, 0xaa])) }, () => loggerService, ); @@ -207,7 +207,7 @@ describe("DefaultReceiverService", () => { describe("[BLE] Without padding nor channel", () => { beforeEach(() => { - service = new DefaultReceiverService({}, () => loggerService); + service = new DefaultApduReceiverService({}, () => loggerService); }); it("should return a response directly when a frame is complete", () => { diff --git a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts similarity index 97% rename from packages/core/src/internal/device-session/service/DefaultReceiverService.ts rename to packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts index 3d8f46fd9..5d14fa77a 100644 --- a/packages/core/src/internal/device-session/service/DefaultReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts @@ -17,14 +17,14 @@ import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; import { loggerTypes } from "@internal/logger-publisher/di/loggerTypes"; import { LoggerPublisherService } from "@internal/logger-publisher/service/LoggerPublisherService"; -import { ReceiverService } from "./ReceiverService"; +import { ApduReceiverService } from "./ApduReceiverService"; type DefaultReceiverConstructorArgs = { channel?: Maybe; }; @injectable() -export class DefaultReceiverService implements ReceiverService { +export class DefaultApduReceiverService implements ApduReceiverService { private _channel: Maybe; private _logger: LoggerPublisherService; private _pendingFrames: Frame[]; From d3732da38d33ce8c5c60f0edeebafb4febf216e9 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 28 Mar 2024 10:16:22 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=8E=A8=20(core):=20Change=20size=20?= =?UTF-8?q?suffix=20to=20length=20for=20apdu=20part?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device-session/data/ApduResponseConst.ts | 2 +- .../device-session/data/FramerConst.ts | 8 ++--- .../device-session/model/FrameHeader.ts | 14 ++++---- .../service/DefaultApduReceiverService.ts | 32 +++++++++---------- .../service/DefaultFramerService.ts | 18 +++++------ 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/core/src/internal/device-session/data/ApduResponseConst.ts b/packages/core/src/internal/device-session/data/ApduResponseConst.ts index 5496d8162..0b8428a0d 100644 --- a/packages/core/src/internal/device-session/data/ApduResponseConst.ts +++ b/packages/core/src/internal/device-session/data/ApduResponseConst.ts @@ -1 +1 @@ -export const APDU_RESPONSE_STATUS_CODE_SIZE = 2; +export const APDU_RESPONSE_STATUS_CODE_LENGTH = 2; diff --git a/packages/core/src/internal/device-session/data/FramerConst.ts b/packages/core/src/internal/device-session/data/FramerConst.ts index c95f66951..17588db3f 100644 --- a/packages/core/src/internal/device-session/data/FramerConst.ts +++ b/packages/core/src/internal/device-session/data/FramerConst.ts @@ -1,5 +1,5 @@ export const HEAD_TAG = 0x05; -export const HEAD_TAG_SIZE = 1; -export const CHANNEL_SIZE = 2; -export const INDEX_SIZE = 2; -export const APDU_DATA_SIZE = 2; +export const HEAD_TAG_LENGTH = 1; +export const CHANNEL_LENGTH = 2; +export const INDEX_LENGTH = 2; +export const APDU_DATA_LENGTH_LENGTH = 2; diff --git a/packages/core/src/internal/device-session/model/FrameHeader.ts b/packages/core/src/internal/device-session/model/FrameHeader.ts index 12aa40471..30539358a 100644 --- a/packages/core/src/internal/device-session/model/FrameHeader.ts +++ b/packages/core/src/internal/device-session/model/FrameHeader.ts @@ -15,7 +15,7 @@ export class FrameHeader { protected _headTag: Uint8Array; protected _index: Uint8Array; protected _length: number; - protected _dataSize: Maybe; + protected _dataLength: Maybe; constructor({ uuid, dataSize, @@ -25,14 +25,14 @@ export class FrameHeader { channel, }: FrameHeaderConstructorArgs) { this._uuid = uuid; - this._dataSize = dataSize; + this._dataLength = dataSize; this._index = index; this._headTag = headTag; this._length = length; this._channel = channel; } - getDataSize(): Maybe { - return this._dataSize.caseOf({ + getDataLength(): Maybe { + return this._dataLength.caseOf({ Just: (value) => Maybe.of( value.reduce( @@ -45,7 +45,7 @@ export class FrameHeader { }); } setDataSize(dataSize: Maybe): FrameHeader { - this._dataSize = dataSize; + this._dataLength = dataSize; return this; } getLength(): number { @@ -54,7 +54,7 @@ export class FrameHeader { toString(): string { return JSON.stringify({ uuid: this._uuid.toString(), - dataSize: this._dataSize.extract()?.toString(), + dataSize: this._dataLength.extract()?.toString(), index: this._index.toString(), headTag: this._headTag.toString(), length: this._length.toString(), @@ -69,7 +69,7 @@ export class FrameHeader { }), ...this._headTag, ...this._index, - ...this._dataSize.caseOf({ + ...this._dataLength.caseOf({ Just: (dataSize) => [...dataSize], Nothing: () => [], }), diff --git a/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts index 5d14fa77a..abe0115f9 100644 --- a/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts @@ -2,12 +2,12 @@ import { inject, injectable } from "inversify"; import { Either, Just, Left, Maybe, Nothing, Right } from "purify-ts"; import { v4 } from "uuid"; -import { APDU_RESPONSE_STATUS_CODE_SIZE } from "@internal/device-session/data/ApduResponseConst"; +import { APDU_RESPONSE_STATUS_CODE_LENGTH } from "@internal/device-session/data/ApduResponseConst"; import { - APDU_DATA_SIZE, - CHANNEL_SIZE, - HEAD_TAG_SIZE, - INDEX_SIZE, + APDU_DATA_LENGTH_LENGTH, + CHANNEL_LENGTH, + HEAD_TAG_LENGTH, + INDEX_LENGTH, } from "@internal/device-session/data/FramerConst"; import { ApduResponse } from "@internal/device-session/model/ApduResponse"; import { ReceiverApduError } from "@internal/device-session/model/Errors"; @@ -56,7 +56,7 @@ export class DefaultApduReceiverService implements ApduReceiverService { this._logger.debug("handle frame", { data: { frame } }); this._pendingFrames.push(value); - const dataSize = this._pendingFrames[0]!.getHeader().getDataSize(); + const dataSize = this._pendingFrames[0]!.getHeader().getDataLength(); return this.getCompleteFrame(dataSize); }); } @@ -81,11 +81,11 @@ export class DefaultApduReceiverService implements ApduReceiverService { ); const data = FramerUtils.getFirstBytesFrom( concatenatedFramesData, - concatenatedFramesData.length - APDU_RESPONSE_STATUS_CODE_SIZE, + concatenatedFramesData.length - APDU_RESPONSE_STATUS_CODE_LENGTH, ); const statusCode = FramerUtils.getLastBytesFrom( concatenatedFramesData, - APDU_RESPONSE_STATUS_CODE_SIZE, + APDU_RESPONSE_STATUS_CODE_LENGTH, ); this._pendingFrames = []; @@ -101,14 +101,14 @@ export class DefaultApduReceiverService implements ApduReceiverService { private apduToFrame(apdu: Uint8Array): Either { const channelSize = this._channel.caseOf({ - Just: () => CHANNEL_SIZE, + Just: () => CHANNEL_LENGTH, Nothing: () => 0, }); - const headTag = apdu.slice(channelSize, channelSize + HEAD_TAG_SIZE); + const headTag = apdu.slice(channelSize, channelSize + HEAD_TAG_LENGTH); const index = apdu.slice( - channelSize + HEAD_TAG_SIZE, - channelSize + HEAD_TAG_SIZE + INDEX_SIZE, + channelSize + HEAD_TAG_LENGTH, + channelSize + HEAD_TAG_LENGTH + INDEX_LENGTH, ); const isFirstIndex = index.reduce((curr, val) => curr + val, 0) === 0; @@ -117,12 +117,12 @@ export class DefaultApduReceiverService implements ApduReceiverService { return Left(new ReceiverApduError()); } - const dataSizeIndex = channelSize + HEAD_TAG_SIZE + INDEX_SIZE; - const dataSizeLength = isFirstIndex ? APDU_DATA_SIZE : 0; + const dataSizeIndex = channelSize + HEAD_TAG_LENGTH + INDEX_LENGTH; + const dataSizeLength = isFirstIndex ? APDU_DATA_LENGTH_LENGTH : 0; if ( apdu.length < - channelSize + HEAD_TAG_SIZE + INDEX_SIZE + dataSizeLength + channelSize + HEAD_TAG_LENGTH + INDEX_LENGTH + dataSizeLength ) { return Left(new ReceiverApduError("Unable to parse header from apdu")); } @@ -141,7 +141,7 @@ export class DefaultApduReceiverService implements ApduReceiverService { dataSize, headTag, index, - length: channelSize + HEAD_TAG_SIZE + INDEX_SIZE + dataSizeLength, + length: channelSize + HEAD_TAG_LENGTH + INDEX_LENGTH + dataSizeLength, }), data, }); diff --git a/packages/core/src/internal/device-session/service/DefaultFramerService.ts b/packages/core/src/internal/device-session/service/DefaultFramerService.ts index 201b73352..feb7ec894 100644 --- a/packages/core/src/internal/device-session/service/DefaultFramerService.ts +++ b/packages/core/src/internal/device-session/service/DefaultFramerService.ts @@ -3,11 +3,11 @@ import { Either, Left, Maybe, Right } from "purify-ts"; import { v4 } from "uuid"; import { - APDU_DATA_SIZE, - CHANNEL_SIZE, + APDU_DATA_LENGTH_LENGTH, + CHANNEL_LENGTH, HEAD_TAG, - HEAD_TAG_SIZE, - INDEX_SIZE, + HEAD_TAG_LENGTH, + INDEX_LENGTH, } from "@internal/device-session/data/FramerConst"; import { FramerApduError, @@ -128,7 +128,7 @@ export class DefaultFramerService implements FramerService { const header = new FrameHeader({ uuid: v4(), channel: this._channel.map((channel) => - FramerUtils.getLastBytesFrom(channel, CHANNEL_SIZE), + FramerUtils.getLastBytesFrom(channel, CHANNEL_LENGTH), ), headTag: new Uint8Array([HEAD_TAG]), index: new Uint8Array([Math.floor(frameIndex / 0xff), frameIndex & 0xff]), @@ -170,12 +170,12 @@ export class DefaultFramerService implements FramerService { private getFrameHeaderSizeFromIndex(frameIndex: number): number { return ( this._channel.caseOf({ - Just: () => CHANNEL_SIZE, + Just: () => CHANNEL_LENGTH, Nothing: () => 0, }) + - INDEX_SIZE + - HEAD_TAG_SIZE + - (frameIndex === 0 ? APDU_DATA_SIZE : 0) + INDEX_LENGTH + + HEAD_TAG_LENGTH + + (frameIndex === 0 ? APDU_DATA_LENGTH_LENGTH : 0) ); } } From 140b71add5bccae6df8d0df2256751a928be8eae Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 28 Mar 2024 10:36:03 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(core):=20Create=20h?= =?UTF-8?q?elper=20bytesToNumber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device-session/model/FrameHeader.ts | 16 ++++------------ .../device-session/utils/FramerUtils.test.ts | 19 +++++++++++++++++++ .../device-session/utils/FramerUtils.ts | 13 +++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/core/src/internal/device-session/model/FrameHeader.ts b/packages/core/src/internal/device-session/model/FrameHeader.ts index 30539358a..8cd260a79 100644 --- a/packages/core/src/internal/device-session/model/FrameHeader.ts +++ b/packages/core/src/internal/device-session/model/FrameHeader.ts @@ -1,4 +1,6 @@ -import { Maybe, Nothing } from "purify-ts"; +import { Maybe } from "purify-ts"; + +import { FramerUtils } from "@internal/device-session/utils/FramerUtils"; type FrameHeaderConstructorArgs = { uuid: string; @@ -32,17 +34,7 @@ export class FrameHeader { this._channel = channel; } getDataLength(): Maybe { - return this._dataLength.caseOf({ - Just: (value) => - Maybe.of( - value.reduce( - (acc, val, index) => - acc + val * Math.pow(0x100, value.length - 1 - index), - 0, - ), - ), - Nothing: () => Nothing, - }); + return this._dataLength.map((value) => FramerUtils.bytesToNumber(value)); } setDataSize(dataSize: Maybe): FrameHeader { this._dataLength = dataSize; diff --git a/packages/core/src/internal/device-session/utils/FramerUtils.test.ts b/packages/core/src/internal/device-session/utils/FramerUtils.test.ts index fc8cf3300..e71eb307d 100644 --- a/packages/core/src/internal/device-session/utils/FramerUtils.test.ts +++ b/packages/core/src/internal/device-session/utils/FramerUtils.test.ts @@ -60,4 +60,23 @@ describe("FramerUtils", () => { expect(result).toEqual(new Uint8Array([])); }); }); + + describe("bytesToNumber", () => { + it("should return a correct number", () => { + // Arrange + const array = new Uint8Array([0x67, 0x89]); + // Act + const result = FramerUtils.bytesToNumber(array); + // Assert + expect(result).toEqual(26505); + }); + it("should return 0 when array is empty", () => { + // Arrange + const array = new Uint8Array([]); + // Act + const result = FramerUtils.bytesToNumber(array); + // Assert + expect(result).toEqual(0); + }); + }); }); diff --git a/packages/core/src/internal/device-session/utils/FramerUtils.ts b/packages/core/src/internal/device-session/utils/FramerUtils.ts index 2feebe05e..b8bacb434 100644 --- a/packages/core/src/internal/device-session/utils/FramerUtils.ts +++ b/packages/core/src/internal/device-session/utils/FramerUtils.ts @@ -16,4 +16,17 @@ export const FramerUtils = { getFirstBytesFrom(array: Uint8Array, size: number): Uint8Array { return new Uint8Array(array.slice(0, size)); }, + + /* + * Get number from Uint8Array + * + * @param Uint8Array + */ + bytesToNumber(array: Uint8Array) { + return array.reduce( + (acc, val, index) => + acc + val * Math.pow(0x100, array.length - 1 - index), + 0, + ); + }, }; From 31d7aca4d511455e01784c5833b3ceaeab7af59e Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 28 Mar 2024 10:40:01 +0100 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=92=A1=20(core):=20Add=20comments?= =?UTF-8?q?=20in=20DefaultApduReceiverService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DefaultApduReceiverService.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts index abe0115f9..8cd95ead5 100644 --- a/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts +++ b/packages/core/src/internal/device-session/service/DefaultApduReceiverService.ts @@ -99,6 +99,12 @@ export class DefaultApduReceiverService implements ApduReceiverService { }); } + /* + * Parse an Uint8Array to a Frame + * Return an error if the frame is not formatted correctly + * + * @param Uint8Array + */ private apduToFrame(apdu: Uint8Array): Either { const channelSize = this._channel.caseOf({ Just: () => CHANNEL_LENGTH, @@ -149,6 +155,11 @@ export class DefaultApduReceiverService implements ApduReceiverService { return Right(frame); } + /* + * Return true if all the datas has been received + * + * @param number + */ private isComplete(dataSize: number): boolean { const totalReceiveLength = this._pendingFrames.reduce( (prev: number, curr: Frame) => prev + curr.getData().length,