-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ (core) [DSDK-260]: Add sendCommand use case + GetOsVersion command (#…
…62)
- Loading branch information
Showing
16 changed files
with
501 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@ledgerhq/device-sdk-core": minor | ||
--- | ||
|
||
Add SendCommand use case + GetOsVersion command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import { Apdu } from "@api/apdu/model/Apdu"; | ||
import { DeviceModelId } from "@internal/device-model/model/DeviceModel"; | ||
import { ApduResponse } from "@internal/device-session/model/ApduResponse"; | ||
|
||
export interface Command<Params, T> { | ||
getApdu(params?: Params): Apdu; | ||
parseResponse(responseApdu: Apdu): T; | ||
parseResponse(responseApdu: ApduResponse, deviceModelId: DeviceModelId): T; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Container } from "inversify"; | ||
|
||
import { SendCommandUseCase } from "@api/command/use-case/SendCommandUseCase"; | ||
import { deviceSessionModuleFactory } from "@internal/device-session/di/deviceSessionModule"; | ||
import { loggerModuleFactory } from "@internal/logger-publisher/di/loggerModule"; | ||
import { StubUseCase } from "@root/src/di.stub"; | ||
|
||
import { commandModuleFactory } from "./commandModule"; | ||
import { commandTypes } from "./commandTypes"; | ||
|
||
describe("commandModuleFactory", () => { | ||
describe("Default", () => { | ||
let container: Container; | ||
let mod: ReturnType<typeof commandModuleFactory>; | ||
beforeEach(() => { | ||
mod = commandModuleFactory(); | ||
container = new Container(); | ||
container.load(mod, deviceSessionModuleFactory(), loggerModuleFactory()); | ||
}); | ||
|
||
it("should return the config module", () => { | ||
expect(mod).toBeDefined(); | ||
}); | ||
|
||
it("should return non-stubbed sendCommand usecase", () => { | ||
const sendCommandUseCase = container.get<SendCommandUseCase>( | ||
commandTypes.SendCommandUseCase, | ||
); | ||
expect(sendCommandUseCase).toBeInstanceOf(SendCommandUseCase); | ||
}); | ||
}); | ||
|
||
describe("Stubbed", () => { | ||
let container: Container; | ||
let mod: ReturnType<typeof commandModuleFactory>; | ||
beforeEach(() => { | ||
mod = commandModuleFactory({ stub: true }); | ||
container = new Container(); | ||
container.load(mod); | ||
}); | ||
|
||
it("should return the config module", () => { | ||
expect(mod).toBeDefined(); | ||
}); | ||
|
||
it("should return stubbed sendCommand usecase", () => { | ||
const sendCommandUseCase = container.get(commandTypes.SendCommandUseCase); | ||
expect(sendCommandUseCase).toBeInstanceOf(StubUseCase); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { ContainerModule } from "inversify"; | ||
|
||
import { SendCommandUseCase } from "@api/command/use-case/SendCommandUseCase"; | ||
import { StubUseCase } from "@root/src/di.stub"; | ||
|
||
import { commandTypes } from "./commandTypes"; | ||
|
||
type CommandModuleArgs = Partial<{ | ||
stub: boolean; | ||
}>; | ||
|
||
export const commandModuleFactory = ({ | ||
stub = false, | ||
}: CommandModuleArgs = {}) => | ||
new ContainerModule( | ||
( | ||
bind, | ||
_unbind, | ||
_isBound, | ||
rebind, | ||
_unbindAsync, | ||
_onActivation, | ||
_onDeactivation, | ||
) => { | ||
bind(commandTypes.SendCommandUseCase).to(SendCommandUseCase); | ||
if (stub) { | ||
rebind(commandTypes.SendCommandUseCase).to(StubUseCase); | ||
} | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const commandTypes = { | ||
SendCommandUseCase: Symbol.for("SendCommandUseCase"), | ||
}; |
136 changes: 136 additions & 0 deletions
136
packages/core/src/api/command/os/GetOsVersionCommand.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { Command } from "@api/command/Command"; | ||
import { DeviceModelId } from "@api/types"; | ||
import { ApduResponse } from "@internal/device-session/model/ApduResponse"; | ||
|
||
import { | ||
GetOsVersionCommand, | ||
GetOsVersionResponse, | ||
} from "./GetOsVersionCommand"; | ||
|
||
const GET_OS_VERSION_APDU = Uint8Array.from([0xe0, 0x01, 0x00, 0x00, 0x00]); | ||
|
||
const LNX_RESPONSE_DATA_GOOD = Uint8Array.from([ | ||
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, | ||
0x00, 0x01, 0x00, 0x01, 0x00, 0x90, 0x00, | ||
]); | ||
const LNX_RESPONSE_GOOD = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: LNX_RESPONSE_DATA_GOOD, | ||
}); | ||
|
||
const LNSP_REPONSE_DATA_GOOD = Uint8Array.from([ | ||
0x33, 0x10, 0x00, 0x04, 0x05, 0x31, 0x2e, 0x31, 0x2e, 0x31, 0x04, 0xe6, 0x00, | ||
0x00, 0x00, 0x04, 0x34, 0x2e, 0x30, 0x33, 0x04, 0x33, 0x2e, 0x31, 0x32, 0x01, | ||
0x00, 0x01, 0x00, 0x90, 0x00, | ||
]); | ||
const LNSP_RESPONSE_GOOD = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: LNSP_REPONSE_DATA_GOOD, | ||
}); | ||
|
||
const STAX_RESPONSE_DATA_GOOD = Uint8Array.from([ | ||
0x33, 0x20, 0x00, 0x04, 0x05, 0x31, 0x2e, 0x33, 0x2e, 0x30, 0x04, 0xe6, 0x00, | ||
0x00, 0x00, 0x04, 0x35, 0x2e, 0x32, 0x34, 0x04, 0x30, 0x2e, 0x34, 0x38, 0x01, | ||
0x00, 0x01, 0x00, 0x90, 0x00, | ||
]); | ||
const STAX_RESPONSE_GOOD = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: STAX_RESPONSE_DATA_GOOD, | ||
}); | ||
|
||
describe("GetOsVersionCommand", () => { | ||
let command: Command<void, GetOsVersionResponse>; | ||
|
||
beforeEach(() => { | ||
command = new GetOsVersionCommand(); | ||
}); | ||
|
||
describe("getApdu", () => { | ||
it("should return the GetOsVersion apdu", () => { | ||
const apdu = command.getApdu(); | ||
expect(apdu.getRawApdu()).toStrictEqual(GET_OS_VERSION_APDU); | ||
}); | ||
}); | ||
|
||
describe("parseResponse", () => { | ||
describe("Nano X", () => { | ||
it("should parse the LNX response", () => { | ||
const parsed = command.parseResponse( | ||
LNX_RESPONSE_GOOD, | ||
DeviceModelId.NANO_X, | ||
); | ||
|
||
const expected = { | ||
targetId: "33000004", | ||
seVersion: "2.2.3", | ||
seFlags: 3858759680, | ||
mcuSephVersion: "2.30", | ||
mcuBootloaderVersion: "1.16", | ||
hwVersion: "00", | ||
langId: "00", | ||
recoverState: "00", | ||
}; | ||
|
||
expect(parsed).toStrictEqual(expected); | ||
}); | ||
}); | ||
|
||
describe("Nano S Plus", () => { | ||
it("should parse the LNSP response", () => { | ||
const parsed = command.parseResponse( | ||
LNSP_RESPONSE_GOOD, | ||
DeviceModelId.NANO_SP, | ||
); | ||
|
||
const expected = { | ||
targetId: "33100004", | ||
seVersion: "1.1.1", | ||
seFlags: 3858759680, | ||
mcuSephVersion: "4.03", | ||
mcuBootloaderVersion: "3.12", | ||
hwVersion: "00", | ||
langId: "00", | ||
recoverState: "00", | ||
}; | ||
|
||
expect(parsed).toStrictEqual(expected); | ||
}); | ||
}); | ||
|
||
describe("Stax", () => { | ||
it("should parse the STAX response", () => { | ||
const parsed = command.parseResponse( | ||
STAX_RESPONSE_GOOD, | ||
DeviceModelId.STAX, | ||
); | ||
|
||
const expected = { | ||
targetId: "33200004", | ||
seVersion: "1.3.0", | ||
seFlags: 3858759680, | ||
mcuSephVersion: "5.24", | ||
mcuBootloaderVersion: "0.48", | ||
hwVersion: "00", | ||
langId: "00", | ||
recoverState: "00", | ||
}; | ||
|
||
expect(parsed).toStrictEqual(expected); | ||
}); | ||
}); | ||
|
||
describe("Error handling", () => { | ||
it("should throw an error if the response is not successful", () => { | ||
const response = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x6e, 0x80]), | ||
data: Uint8Array.from([]), | ||
}); | ||
|
||
expect(() => | ||
command.parseResponse(response, DeviceModelId.NANO_X), | ||
).toThrow("Unexpected status word: 6e80"); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Apdu } from "@api/apdu/model/Apdu"; | ||
import { ApduBuilder } from "@api/apdu/utils/ApduBuilder"; | ||
import { ApduParser } from "@api/apdu/utils/ApduParser"; | ||
import { Command } from "@api/command/Command"; | ||
import { CommandUtils } from "@api/command/utils/CommandUtils"; | ||
import { DeviceModelId } from "@api/types"; | ||
import { ApduResponse } from "@internal/device-session/model/ApduResponse"; | ||
|
||
export type GetOsVersionResponse = { | ||
targetId: string; | ||
seVersion: string; | ||
seFlags: number; | ||
mcuSephVersion: string; | ||
mcuBootloaderVersion: string; | ||
hwVersion: string; | ||
langId: string; | ||
recoverState: string; | ||
}; | ||
|
||
export class GetOsVersionCommand | ||
implements Command<void, GetOsVersionResponse> | ||
{ | ||
getApdu = (): Apdu => | ||
new ApduBuilder({ | ||
cla: 0xe0, | ||
ins: 0x01, | ||
p1: 0x00, | ||
p2: 0x00, | ||
}).build(); | ||
|
||
parseResponse(responseApdu: ApduResponse, deviceModelId: DeviceModelId) { | ||
const parser = new ApduParser(responseApdu); | ||
if (!CommandUtils.isSuccessResponse(responseApdu)) { | ||
// [ASK] How de we handle unsuccessful responses? | ||
throw new Error( | ||
`Unexpected status word: ${parser.encodeToHexaString(responseApdu.statusCode)}`, | ||
); | ||
} | ||
|
||
const targetId = parser.encodeToHexaString(parser.extractFieldByLength(4)); | ||
const seVersion = parser.encodeToString(parser.extractFieldLVEncoded()); | ||
const seFlags = parseInt( | ||
parser.encodeToHexaString(parser.extractFieldLVEncoded()).toString(), | ||
16, | ||
); | ||
const mcuSephVersion = parser.encodeToString( | ||
parser.extractFieldLVEncoded(), | ||
); | ||
const mcuBootloaderVersion = parser.encodeToString( | ||
parser.extractFieldLVEncoded(), | ||
); | ||
|
||
let hwVersion = "00"; | ||
if (deviceModelId === DeviceModelId.NANO_X) { | ||
hwVersion = parser.encodeToHexaString(parser.extractFieldLVEncoded()); | ||
} | ||
|
||
const langId = parser.encodeToHexaString(parser.extractFieldLVEncoded()); | ||
const recoverState = parser.encodeToHexaString( | ||
parser.extractFieldLVEncoded(), | ||
); | ||
|
||
return { | ||
targetId, | ||
seVersion, | ||
seFlags, | ||
mcuSephVersion, | ||
mcuBootloaderVersion, | ||
hwVersion, | ||
langId, | ||
recoverState: recoverState ? recoverState : "0", | ||
}; | ||
} | ||
} |
Oops, something went wrong.