-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ (signer-btc): Create ContinueTask & use it for SignMessage
- Loading branch information
1 parent
17aaee3
commit c3296da
Showing
8 changed files
with
291 additions
and
412 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
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
113 changes: 113 additions & 0 deletions
113
packages/signer/signer-btc/src/internal/app-binder/task/ContinueTask.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,113 @@ | ||
import { | ||
ApduResponse, | ||
CommandResultFactory, | ||
type DmkError, | ||
type InternalApi, | ||
UnknownDeviceExchangeError, | ||
} from "@ledgerhq/device-management-kit"; | ||
import { type Either, Left, Right } from "purify-ts"; | ||
|
||
import { type BtcErrorCodes } from "@internal/app-binder/command/utils/bitcoinAppErrors"; | ||
import { ContinueTask } from "@internal/app-binder/task/ContinueTask"; | ||
import { type DataStore } from "@internal/data-store/model/DataStore"; | ||
|
||
describe("ContinueTask", () => { | ||
const clientCommandInterpreter = { | ||
getClientCommandPayload: jest.fn( | ||
() => Right(Uint8Array.from([])) as Either<DmkError, Uint8Array>, | ||
), | ||
}; | ||
const api = { | ||
sendCommand: jest.fn(), | ||
}; | ||
const randomNumberOfClientCalls = Math.floor(Math.random() * 10 + 2); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it(`should call ${randomNumberOfClientCalls} times client interpreter and return success`, async () => { | ||
// given | ||
new Array(randomNumberOfClientCalls).fill(0).forEach((_) => { | ||
api.sendCommand.mockReturnValueOnce( | ||
CommandResultFactory({ | ||
data: new ApduResponse({ | ||
statusCode: Uint8Array.from([0xe0, 0x00]), | ||
data: Uint8Array.from([]), | ||
}), | ||
}), | ||
); | ||
}); | ||
api.sendCommand.mockReturnValueOnce( | ||
CommandResultFactory({ | ||
data: new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: Uint8Array.from([]), | ||
}), | ||
}), | ||
); | ||
const fromResult = CommandResultFactory<ApduResponse, BtcErrorCodes>({ | ||
data: new ApduResponse({ | ||
statusCode: Uint8Array.from([0xe0, 0x00]), | ||
data: Uint8Array.from([]), | ||
}), | ||
}); | ||
// when | ||
const task = new ContinueTask( | ||
api as unknown as InternalApi, | ||
clientCommandInterpreter, | ||
); | ||
await task.run({} as DataStore, fromResult); | ||
// then | ||
expect( | ||
clientCommandInterpreter.getClientCommandPayload, | ||
).toHaveBeenCalledTimes(randomNumberOfClientCalls + 1); | ||
}); | ||
|
||
it("should return an error if the client interpreter fails", async () => { | ||
// given | ||
const error = new UnknownDeviceExchangeError("Failed"); | ||
clientCommandInterpreter.getClientCommandPayload.mockReturnValueOnce( | ||
Left(error), | ||
); | ||
const fromResult = CommandResultFactory<ApduResponse, BtcErrorCodes>({ | ||
data: new ApduResponse({ | ||
statusCode: Uint8Array.from([0xe0, 0x00]), | ||
data: Uint8Array.from([]), | ||
}), | ||
}); | ||
// when | ||
const task = new ContinueTask( | ||
api as unknown as InternalApi, | ||
clientCommandInterpreter, | ||
); | ||
const result = await task.run({} as DataStore, fromResult); | ||
// then | ||
expect(api.sendCommand).toHaveBeenCalledTimes(0); | ||
expect(result).toStrictEqual( | ||
CommandResultFactory({ error: new UnknownDeviceExchangeError(error) }), | ||
); | ||
}); | ||
it("should return an error if send command fails", async () => { | ||
// given | ||
const error = new UnknownDeviceExchangeError("Failed"); | ||
api.sendCommand.mockReturnValueOnce(CommandResultFactory({ error })); | ||
const fromResult = CommandResultFactory<ApduResponse, BtcErrorCodes>({ | ||
data: new ApduResponse({ | ||
statusCode: Uint8Array.from([0xe0, 0x00]), | ||
data: Uint8Array.from([]), | ||
}), | ||
}); | ||
// when | ||
const task = new ContinueTask( | ||
api as unknown as InternalApi, | ||
clientCommandInterpreter, | ||
); | ||
const result = await task.run({} as DataStore, fromResult); | ||
// then | ||
expect( | ||
clientCommandInterpreter.getClientCommandPayload, | ||
).toHaveBeenCalledTimes(1); | ||
expect(result).toStrictEqual(CommandResultFactory({ error })); | ||
}); | ||
}); |
81 changes: 81 additions & 0 deletions
81
packages/signer/signer-btc/src/internal/app-binder/task/ContinueTask.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,81 @@ | ||
import { | ||
type ApduResponse, | ||
type CommandResult, | ||
CommandResultFactory, | ||
type CommandSuccessResult, | ||
type InternalApi, | ||
isSuccessCommandResult, | ||
UnknownDeviceExchangeError, | ||
} from "@ledgerhq/device-management-kit"; | ||
|
||
import { type ClientCommandContext } from "@internal/app-binder/command/client-command-handlers/ClientCommandHandlersTypes"; | ||
import { | ||
ContinueCommand, | ||
type ContinueCommandResponse, | ||
} from "@internal/app-binder/command/ContinueCommand"; | ||
import { ClientCommandInterpreter } from "@internal/app-binder/command/service/ClientCommandInterpreter"; | ||
import { type BtcErrorCodes } from "@internal/app-binder/command/utils/bitcoinAppErrors"; | ||
import { type DataStore } from "@internal/data-store/model/DataStore"; | ||
import { BtcCommandUtils } from "@internal/utils/BtcCommandUtils"; | ||
|
||
export class ContinueTask { | ||
private readonly _clientCommandInterpreter: ClientCommandInterpreter; | ||
|
||
constructor( | ||
private readonly _api: InternalApi, | ||
clientCommandInterpreter?: ClientCommandInterpreter, | ||
) { | ||
this._clientCommandInterpreter = | ||
clientCommandInterpreter || new ClientCommandInterpreter(); | ||
} | ||
|
||
async run( | ||
dataStore: DataStore, | ||
fromResult: CommandResult<ApduResponse, BtcErrorCodes>, | ||
): Promise<CommandResult<ContinueCommandResponse, BtcErrorCodes>> { | ||
let currentResponse: CommandResult<ContinueCommandResponse, BtcErrorCodes> = | ||
fromResult; | ||
const commandHandlersContext: ClientCommandContext = { | ||
dataStore, | ||
queue: [], | ||
yieldedResults: [], | ||
}; | ||
|
||
while ( | ||
this.isApduResult(currentResponse) && | ||
BtcCommandUtils.isContinueResponse(currentResponse.data) | ||
) { | ||
currentResponse = await this._clientCommandInterpreter | ||
.getClientCommandPayload( | ||
currentResponse.data.data, | ||
commandHandlersContext, | ||
) | ||
.caseOf({ | ||
Left: (error) => | ||
Promise.resolve( | ||
CommandResultFactory({ | ||
error: new UnknownDeviceExchangeError(error), | ||
}), | ||
), | ||
Right: (payload) => | ||
this._api.sendCommand( | ||
new ContinueCommand({ | ||
payload, | ||
}), | ||
), | ||
}); | ||
} | ||
return currentResponse; | ||
} | ||
private isApduResult = ( | ||
response: CommandResult<ContinueCommandResponse, BtcErrorCodes>, | ||
): response is CommandSuccessResult<ApduResponse> => { | ||
return ( | ||
isSuccessCommandResult(response) && | ||
typeof response.data === "object" && | ||
response.data !== null && | ||
"statusCode" in response.data && | ||
"data" in response.data | ||
); | ||
}; | ||
} |
Oops, something went wrong.