Skip to content

Commit 9b0f85f

Browse files
✨ (dmk): Add API calls and models for secure channel
1 parent 02ec232 commit 9b0f85f

21 files changed

+426
-52
lines changed

.changeset/rare-elephants-breathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ledgerhq/device-management-kit": patch
3+
---
4+
5+
Add manager api calls for secure channel

packages/device-management-kit/src/api/command/os/GetOsVersionCommand.test.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import { DeviceModelId } from "@api/device/DeviceModel";
66
import { ApduResponse } from "@api/device-session/ApduResponse";
77

8+
import { getOsVersionCommandResponseStubBuilder } from "./__mocks__/GetOsVersionCommand";
89
import { GetOsVersionCommand } from "./GetOsVersionCommand";
910

1011
const GET_OS_VERSION_APDU = Uint8Array.from([0xe0, 0x01, 0x00, 0x00, 0x00]);
@@ -62,16 +63,7 @@ describe("GetOsVersionCommand", () => {
6263
);
6364

6465
const expected = CommandResultFactory({
65-
data: {
66-
targetId: "33000004",
67-
seVersion: "2.2.3",
68-
seFlags: 3858759680,
69-
mcuSephVersion: "2.30",
70-
mcuBootloaderVersion: "1.16",
71-
hwVersion: "00",
72-
langId: "00",
73-
recoverState: "00",
74-
},
66+
data: getOsVersionCommandResponseStubBuilder(DeviceModelId.NANO_X),
7567
});
7668

7769
expect(parsed).toStrictEqual(expected);
@@ -86,16 +78,7 @@ describe("GetOsVersionCommand", () => {
8678
);
8779

8880
const expected = CommandResultFactory({
89-
data: {
90-
targetId: "33100004",
91-
seVersion: "1.1.1",
92-
seFlags: 3858759680,
93-
mcuSephVersion: "4.03",
94-
mcuBootloaderVersion: "3.12",
95-
hwVersion: "00",
96-
langId: "00",
97-
recoverState: "00",
98-
},
81+
data: getOsVersionCommandResponseStubBuilder(DeviceModelId.NANO_SP),
9982
});
10083

10184
expect(parsed).toStrictEqual(expected);
@@ -110,16 +93,7 @@ describe("GetOsVersionCommand", () => {
11093
);
11194

11295
const expected = CommandResultFactory({
113-
data: {
114-
targetId: "33200004",
115-
seVersion: "1.3.0",
116-
seFlags: 3858759680,
117-
mcuSephVersion: "5.24",
118-
mcuBootloaderVersion: "0.48",
119-
hwVersion: "00",
120-
langId: "00",
121-
recoverState: "00",
122-
},
96+
data: getOsVersionCommandResponseStubBuilder(DeviceModelId.STAX),
12397
});
12498

12599
expect(parsed).toStrictEqual(expected);

packages/device-management-kit/src/api/command/os/GetOsVersionCommand.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type GetOsVersionResponse = {
1818
/**
1919
* Target identifier.
2020
*/
21-
readonly targetId: string;
21+
readonly targetId: number;
2222

2323
/**
2424
* Version of BOLOS on the secure element (SE).
@@ -91,7 +91,10 @@ export class GetOsVersionCommand implements Command<GetOsVersionResponse> {
9191
}
9292
const parser = new ApduParser(apduResponse);
9393

94-
const targetId = parser.encodeToHexaString(parser.extractFieldByLength(4));
94+
const targetId = parseInt(
95+
parser.encodeToHexaString(parser.extractFieldByLength(4)),
96+
16,
97+
);
9598
const seVersion = parser.encodeToString(parser.extractFieldLVEncoded());
9699
const seFlags = parseInt(
97100
parser.encodeToHexaString(parser.extractFieldLVEncoded()).toString(),
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { DeviceModelId } from "@api/device/DeviceModel";
2+
import { type GetOsVersionResponse } from "@api/index";
3+
4+
export const getOsVersionCommandResponseStubBuilder = (
5+
deviceModelId: DeviceModelId = DeviceModelId.NANO_SP,
6+
props: Partial<GetOsVersionResponse> = {},
7+
): GetOsVersionResponse =>
8+
({
9+
[DeviceModelId.NANO_SP]: {
10+
targetId: 856686596,
11+
seVersion: "1.1.1",
12+
seFlags: 3858759680,
13+
mcuSephVersion: "4.03",
14+
mcuBootloaderVersion: "3.12",
15+
hwVersion: "00",
16+
langId: "00",
17+
recoverState: "00",
18+
...props,
19+
},
20+
[DeviceModelId.NANO_S]: {
21+
targetId: 858783748,
22+
seVersion: "1.1.1",
23+
seFlags: 3858759680,
24+
mcuSephVersion: "6.4.0",
25+
mcuBootloaderVersion: "5.4.0",
26+
hwVersion: "00",
27+
langId: "00",
28+
recoverState: "00",
29+
...props,
30+
},
31+
[DeviceModelId.NANO_X]: {
32+
targetId: 855638020,
33+
seVersion: "2.2.3",
34+
seFlags: 3858759680,
35+
mcuSephVersion: "2.30",
36+
mcuBootloaderVersion: "1.16",
37+
hwVersion: "00",
38+
langId: "00",
39+
recoverState: "00",
40+
},
41+
[DeviceModelId.STAX]: {
42+
targetId: 857735172,
43+
seVersion: "1.3.0",
44+
seFlags: 3858759680,
45+
mcuSephVersion: "5.24",
46+
mcuBootloaderVersion: "0.48",
47+
hwVersion: "00",
48+
langId: "00",
49+
recoverState: "00",
50+
...props,
51+
},
52+
[DeviceModelId.FLEX]: {
53+
targetId: 858783748,
54+
seVersion: "1.1.1",
55+
seFlags: 3858759680,
56+
mcuSephVersion: "6.4.0",
57+
mcuBootloaderVersion: "5.4.0",
58+
hwVersion: "00",
59+
langId: "00",
60+
recoverState: "00",
61+
...props,
62+
},
63+
})[deviceModelId];

packages/device-management-kit/src/api/device-action/__test-utils__/data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AppType } from "@internal/manager-api/model/ManagerApiType";
1+
import { AppType } from "@internal/manager-api/model/Application";
22

33
export const BTC_APP = {
44
appEntryLength: 77,

packages/device-management-kit/src/api/device-action/os/ListAppsWithMetadata/ListAppsWithMetadataDeviceAction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
XStateDeviceAction,
2020
} from "@api/device-action/xstate-utils/XStateDeviceAction";
2121
import { type DeviceSessionState } from "@api/device-session/DeviceSessionState";
22+
import { type Application } from "@internal/manager-api/model/Application";
2223
import { type HttpFetchApiError } from "@internal/manager-api/model/Errors";
23-
import { type Application } from "@internal/manager-api/model/ManagerApiType";
2424

2525
import {
2626
type ListAppsWithMetadataDAError,

packages/device-management-kit/src/api/device-action/os/ListAppsWithMetadata/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
type ListAppsDAInput,
99
type ListAppsDAIntermediateValue,
1010
} from "@api/device-action/os/ListApps/types";
11+
import { type Application } from "@internal/manager-api/model/Application";
1112
import { type HttpFetchApiError } from "@internal/manager-api/model/Errors";
12-
import { type Application } from "@internal/manager-api/model/ManagerApiType";
1313

1414
export type ListAppsWithMetadataDAOutput = Array<Application | null>;
1515
export type ListAppsWithMetadataDAInput = ListAppsDAInput;

packages/device-management-kit/src/api/device-session/DeviceSessionState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type GetAppAndVersionResponse } from "@api/command/os/GetAppAndVersionCommand";
22
import { type BatteryStatusFlags } from "@api/command/os/GetBatteryStatusCommand";
33
import { type DeviceStatus } from "@api/device/DeviceStatus";
4-
import { type Application } from "@internal/manager-api/model/ManagerApiType";
4+
import { type Application } from "@internal/manager-api/model/Application";
55

66
/**
77
* The battery status of a device.

packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ export class DeviceSession {
107107
rawApdu,
108108
options.triggersDisconnection,
109109
);
110-
console.log("errrorOrResponse", rawApdu);
111110

112111
return errorOrResponse.ifRight((response: ApduResponse) => {
113112
if (CommandUtils.isLockedDeviceResponse(response)) {

packages/device-management-kit/src/internal/manager-api/data/AxiosManagerApiDataSource.test.ts

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ import {
1111
DEFAULT_MANAGER_API_BASE_URL,
1212
DEFAULT_MOCK_SERVER_BASE_URL,
1313
} from "@internal/manager-api//model/Const";
14+
import { deviceVersionMockBuilder } from "@internal/manager-api/data/__mocks__/GetDeviceVersion";
15+
import { firmwareVersionMockBuilder } from "@internal/manager-api/data/__mocks__/GetFirmwareVersion";
1416
import { HttpFetchApiError } from "@internal/manager-api/model/Errors";
1517

1618
import { AxiosManagerApiDataSource } from "./AxiosManagerApiDataSource";
1719

1820
jest.mock("axios");
1921

2022
describe("AxiosManagerApiDataSource", () => {
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
});
2126
describe("getAppsByHash", () => {
2227
describe("success cases", () => {
2328
it("with BTC app, should return the metadata", async () => {
@@ -78,7 +83,8 @@ describe("AxiosManagerApiDataSource", () => {
7883
});
7984

8085
describe("error cases", () => {
81-
it("should throw an error if the request fails", async () => {
86+
it("should throw an error if the request fails", () => {
87+
// given
8288
const api = new AxiosManagerApiDataSource({
8389
managerApiUrl: DEFAULT_MANAGER_API_BASE_URL,
8490
mockUrl: DEFAULT_MOCK_SERVER_BASE_URL,
@@ -88,12 +94,82 @@ describe("AxiosManagerApiDataSource", () => {
8894

8995
const hashes = [BTC_APP.appFullHash];
9096

91-
try {
92-
await api.getAppsByHash(hashes);
93-
} catch (error) {
94-
expect(error).toEqual(Left(new HttpFetchApiError(err)));
95-
}
97+
// when
98+
const response = api.getAppsByHash(hashes);
99+
100+
// then
101+
expect(response).resolves.toEqual(Left(new HttpFetchApiError(err)));
102+
});
103+
});
104+
});
105+
106+
describe("getDeviceVersion", () => {
107+
it("should return a complete device version", () => {
108+
// given
109+
const api = new AxiosManagerApiDataSource({
110+
managerApiUrl: DEFAULT_MANAGER_API_BASE_URL,
111+
mockUrl: DEFAULT_MOCK_SERVER_BASE_URL,
112+
});
113+
const mockedDeviceVersion = deviceVersionMockBuilder();
114+
jest
115+
.spyOn(axios, "get")
116+
.mockResolvedValueOnce({ data: mockedDeviceVersion });
117+
118+
// when
119+
const response = api.getDeviceVersion("targetId", 42);
120+
121+
// then
122+
expect(response).resolves.toEqual(Right(mockedDeviceVersion));
123+
});
124+
it("should return an error if the request fails", () => {
125+
// given
126+
const api = new AxiosManagerApiDataSource({
127+
managerApiUrl: DEFAULT_MANAGER_API_BASE_URL,
128+
mockUrl: DEFAULT_MOCK_SERVER_BASE_URL,
129+
});
130+
const error = new Error("fetch error");
131+
jest.spyOn(axios, "get").mockRejectedValue(error);
132+
133+
// when
134+
const response = api.getDeviceVersion("targetId", 42);
135+
136+
// then
137+
expect(response).resolves.toEqual(Left(new HttpFetchApiError(error)));
138+
});
139+
});
140+
141+
describe("getFirmwareVersion", () => {
142+
it("should return a complete firmware version", () => {
143+
// given
144+
const api = new AxiosManagerApiDataSource({
145+
managerApiUrl: DEFAULT_MANAGER_API_BASE_URL,
146+
mockUrl: DEFAULT_MOCK_SERVER_BASE_URL,
96147
});
148+
const mockedFirmwareVersion = firmwareVersionMockBuilder();
149+
jest
150+
.spyOn(axios, "get")
151+
.mockResolvedValueOnce({ data: mockedFirmwareVersion });
152+
153+
// when
154+
const response = api.getFirmwareVersion("versionName", 42, 21);
155+
156+
// then
157+
expect(response).resolves.toEqual(Right(mockedFirmwareVersion));
158+
});
159+
it("should return an error if the request fails", () => {
160+
// given
161+
const api = new AxiosManagerApiDataSource({
162+
managerApiUrl: DEFAULT_MANAGER_API_BASE_URL,
163+
mockUrl: DEFAULT_MOCK_SERVER_BASE_URL,
164+
});
165+
const error = new Error("fetch error");
166+
jest.spyOn(axios, "get").mockRejectedValue(error);
167+
168+
// when
169+
const response = api.getFirmwareVersion("versionName", 42, 21);
170+
171+
// then
172+
expect(response).resolves.toEqual(Left(new HttpFetchApiError(error)));
97173
});
98174
});
99175
});

packages/device-management-kit/src/internal/manager-api/data/AxiosManagerApiDataSource.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,58 @@ import { EitherAsync } from "purify-ts";
44

55
import { type DmkConfig } from "@api/DmkConfig";
66
import { managerApiTypes } from "@internal/manager-api/di/managerApiTypes";
7-
import { HttpFetchApiError } from "@internal/manager-api/model/Errors";
87
import {
9-
Application,
8+
type Application,
109
AppType,
11-
} from "@internal/manager-api/model/ManagerApiType";
10+
} from "@internal/manager-api/model/Application";
11+
import { type DeviceVersion } from "@internal/manager-api/model/Device";
12+
import { HttpFetchApiError } from "@internal/manager-api/model/Errors";
13+
import { type FinalFirmware } from "@internal/manager-api/model/Firmware";
1214

1315
import { ManagerApiDataSource } from "./ManagerApiDataSource";
1416
import { ApplicationDto, AppTypeDto } from "./ManagerApiDto";
1517

1618
@injectable()
1719
export class AxiosManagerApiDataSource implements ManagerApiDataSource {
1820
private readonly baseUrl: string;
21+
1922
constructor(@inject(managerApiTypes.DmkConfig) config: DmkConfig) {
2023
this.baseUrl = config.managerApiUrl;
2124
}
2225

26+
getDeviceVersion(
27+
targetId: string,
28+
provider: number,
29+
): EitherAsync<HttpFetchApiError, DeviceVersion> {
30+
return EitherAsync(() =>
31+
axios.get<DeviceVersion>(`${this.baseUrl}/get_device_version`, {
32+
params: {
33+
target_id: targetId,
34+
provider,
35+
},
36+
}),
37+
)
38+
.map((res) => res.data)
39+
.mapLeft((error) => new HttpFetchApiError(error));
40+
}
41+
getFirmwareVersion(
42+
version: string,
43+
deviceId: number,
44+
provider: number,
45+
): EitherAsync<HttpFetchApiError, FinalFirmware> {
46+
return EitherAsync(() =>
47+
axios.get<FinalFirmware>(`${this.baseUrl}/get_firmware_version`, {
48+
params: {
49+
device_version: deviceId,
50+
version_name: version,
51+
provider,
52+
},
53+
}),
54+
)
55+
.map((res) => res.data)
56+
.mapLeft((error) => new HttpFetchApiError(error));
57+
}
58+
2359
private mapAppTypeDtoToAppType(appType: AppTypeDto): AppType {
2460
switch (appType) {
2561
case AppTypeDto.currency:
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { type EitherAsync } from "purify-ts";
22

3+
import { type Application } from "@internal/manager-api/model/Application";
4+
import { type DeviceVersion } from "@internal/manager-api/model/Device";
35
import { type HttpFetchApiError } from "@internal/manager-api/model/Errors";
4-
import { type Application } from "@internal/manager-api/model/ManagerApiType";
6+
import { type FinalFirmware } from "@internal/manager-api/model/Firmware";
57

68
export interface ManagerApiDataSource {
79
getAppsByHash(
810
hashes: string[],
911
): EitherAsync<HttpFetchApiError, Array<Application | null>>;
12+
getDeviceVersion(
13+
targetId: string,
14+
provider: number,
15+
): EitherAsync<HttpFetchApiError, DeviceVersion>;
16+
getFirmwareVersion(
17+
version: string,
18+
deviceId: number,
19+
provider: number,
20+
): EitherAsync<HttpFetchApiError, FinalFirmware>;
1021
}

0 commit comments

Comments
 (0)