diff --git a/.changeset/pink-maps-call.md b/.changeset/pink-maps-call.md new file mode 100644 index 000000000..d659621ae --- /dev/null +++ b/.changeset/pink-maps-call.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/device-sdk-core": minor +--- + +Implement Open Application command. diff --git a/packages/core/src/api/command/os/OpenAppCommand.test.ts b/packages/core/src/api/command/os/OpenAppCommand.test.ts new file mode 100644 index 000000000..bbf8b718f --- /dev/null +++ b/packages/core/src/api/command/os/OpenAppCommand.test.ts @@ -0,0 +1,36 @@ +import { ApduResponse } from "@api/device-session/ApduResponse"; + +import { OpenAppCommand } from "./OpenAppCommand"; + +describe("OpenAppCommand", () => { + let openAppCommand: OpenAppCommand; + + beforeEach(() => { + openAppCommand = new OpenAppCommand(); + }); + + it("should return the correct APDU for opening an application", () => { + const appName = "MyApp"; + const expectedApdu = Uint8Array.from([ + 0xe0, 0xd8, 0x00, 0x00, 0x05, 0x4d, 0x79, 0x41, 0x70, 0x70, + ]); + const apdu = openAppCommand.getApdu(appName); + expect(apdu.getRawApdu()).toStrictEqual(expectedApdu); + }); + + it("should not throw error when command is successful", () => { + const apduResponse: ApduResponse = new ApduResponse({ + statusCode: new Uint8Array([0x90, 0x00]), + data: new Uint8Array([]), + }); + expect(() => openAppCommand.parseResponse(apduResponse)).not.toThrow(); + }); + + it("should throw error when command is unsuccessful", () => { + const apduResponse: ApduResponse = new ApduResponse({ + statusCode: new Uint8Array([0x6a, 0x81]), + data: new Uint8Array([]), + }); + expect(() => openAppCommand.parseResponse(apduResponse)).toThrow(); + }); +}); diff --git a/packages/core/src/api/command/os/OpenAppCommand.ts b/packages/core/src/api/command/os/OpenAppCommand.ts new file mode 100644 index 000000000..47104caf0 --- /dev/null +++ b/packages/core/src/api/command/os/OpenAppCommand.ts @@ -0,0 +1,36 @@ +import { Apdu } from "@api/apdu/model/Apdu"; +import { ApduBuilder, ApduBuilderArgs } from "@api/apdu/utils/ApduBuilder"; +import { ApduParser } from "@api/apdu/utils/ApduParser"; +import { Command } from "@api/command/Command"; +import { InvalidStatusWordError } from "@api/command/Errors"; +import { CommandUtils } from "@api/command/utils/CommandUtils"; +import { ApduResponse } from "@api/device-session/ApduResponse"; + +/** + * The command to open an application on the device. + */ +export class OpenAppCommand implements Command { + getApdu(appName: string): Apdu { + const openAppApduArgs: ApduBuilderArgs = { + cla: 0xe0, + ins: 0xd8, + p1: 0x00, + p2: 0x00, + } as const; + return new ApduBuilder(openAppApduArgs) + .addAsciiStringToData(appName) + .build(); + } + + parseResponse(apduResponse: ApduResponse): void { + const parser = new ApduParser(apduResponse); + + if (!CommandUtils.isSuccessResponse(apduResponse)) { + throw new InvalidStatusWordError( + `Unexpected status word: ${parser.encodeToHexaString( + apduResponse.statusCode, + )}`, + ); + } + } +}