Skip to content

Commit

Permalink
simulator: implement a simulator for bitbox02 device
Browse files Browse the repository at this point in the history
HWI is thinking of updating its support policy such that supported wallets
must implement a simulator/emulator. See bitcoin-core/HWI#685.
That's why a simulator is implemented for bitbox02, supporting functionalities of
its API.

This first version of the simulator is capable of nearly every functionality of a
normal Bitbox02 device, without promising any security or production use. Its main
aim is to be able to run unit tests for features and test the API.

In addition, it will be configured to run automated tests in CI, which helps both
us and HWI integration.

Right now, the simulator has 3 different ways to communicate with a client: giving
inputs/getting output from CLI, using pipes or opening sockets. Socket is the most
convenient and reliable choice in this version. It expects the clients to open a
socket on port 15432, which is selected intentionally to avoid possible conflicts.

The simulator resides with C unit-tests since it uses same mocks, therefore it can
be built by `make unit-test`.

Lastly, Python client implemented in `py/send_message.py` is updated to support
communicating with simulator with the socket configuration mentioned above. Client
can be started up with `./py/send_message.py --simulator` command. To run the
simulator, `build-build/bin/test_simulator` command is sufficient.

Signed-off-by: asi345 <[email protected]>
  • Loading branch information
asi345 committed Jan 25, 2024
1 parent 5e1268b commit c0380b7
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 78 deletions.
Binary file added .DS_Store
Binary file not shown.
20 changes: 20 additions & 0 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"macFrameworkPath": [
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "macos-clang-x64",
"configurationProvider": "ms-vscode.makefile-tools"
}
],
"version": 4
}
6 changes: 0 additions & 6 deletions .vscode/settings.json

This file was deleted.

6 changes: 6 additions & 0 deletions py/send_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# pylint: disable=too-many-lines

import argparse
import os
import pprint
import sys
from typing import List, Any, Optional, Callable, Union, Tuple, Sequence
Expand Down Expand Up @@ -1558,7 +1559,12 @@ def run(self) -> int:

def connect_to_simulator_bitbox(debug: bool) -> int:
class Simulator(PhysicalLayer):
def __init__(self) -> None:
self.fifo_path = "./tmp/myfifo"

def write(self, data: bytes) -> None:
with open(self.fifo_path, 'w') as fifo:
fifo.write(data.hex())
raise Exception(f"TODO: write {data.hex()} to simulator")

def read(self, size: int, timeout_ms: int) -> bytes:
Expand Down
2 changes: 2 additions & 0 deletions src/usb/class/hid/hww/hid_hww.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <queue.h>
#include <string.h>
#include <usb/usb_packet.h>
#include <stdio.h>

#define HID_HWW_VERSION 0x00000001u

Expand Down Expand Up @@ -81,6 +82,7 @@ static void _send_next(void)
if (data != NULL) {
_send_busy = true;
hid_write(&_func_data, data, USB_HID_REPORT_OUT_SIZE);
printf("hid_write: %s\n", data);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/usb/usb_frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,14 @@ int32_t usb_frame_process(const USB_FRAME* frame, State* state)
{
// USB initialization frames contain a command that begins with 0x80
if ((frame->type & FRAME_TYPE_MASK) == FRAME_TYPE_INIT) {
printf("CMD_INIT, %d and %u\n", frame->type & FRAME_TYPE_MASK, frame->type);
return _cmd_init(frame, state);
}
if ((frame->type & FRAME_TYPE_MASK) == FRAME_TYPE_CONT) {
printf("CMD_CONTINUE, %d and %u\n", frame->type & FRAME_TYPE_MASK, frame->type);
printf("cid: %u, type: %u, cmd: %u\n", frame->cid, frame->type, frame->init.cmd);
return _cmd_continue(frame, state);
}
printf("FRAME_ERR_INVALID_CMD obtained\n");
return FRAME_ERR_INVALID_CMD;
}
7 changes: 7 additions & 0 deletions src/usb/usb_packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define ERR_NONE 0

Expand Down Expand Up @@ -67,23 +68,28 @@ bool usb_packet_process(const USB_FRAME* frame)
switch (usb_frame_process(frame, &_in_state)) {
case FRAME_ERR_IGNORE:
// Ignore this frame, i.e. no response.
printf("usb_packet_process: FRAME_ERR_IGNORE\n");
break;
case FRAME_ERR_INVALID_SEQ:
// Reset the state becuase this error indicates that there is a host application bug
_reset_state();
_queue_err(FRAME_ERR_INVALID_SEQ, frame->cid);
printf("usb_packet_process: FRAME_ERR_INVALID_SEQ\n");
break;
case FRAME_ERR_CHANNEL_BUSY:
// We don't reset the state because this error doesn't indicate something wrong with the
// "current" connection.
_queue_err(FRAME_ERR_CHANNEL_BUSY, frame->cid);
printf("usb_packet_process: FRAME_ERR_CHANNEL_BUSY\n");
break;
case FRAME_ERR_INVALID_LEN:
// Reset the state becuase this error indicates that there is a host application bug
_reset_state();
_queue_err(FRAME_ERR_INVALID_LEN, frame->cid);
printf("usb_packet_process: FRAME_ERR_INVALID_LEN\n");
break;
case ERR_NONE:
printf("usb_packet_process: ERR_NONE\n");
if (_need_more_data()) {
// Do not send a message yet
return true;
Expand All @@ -100,6 +106,7 @@ bool usb_packet_process(const USB_FRAME* frame)
break;
default:
// other errors
printf("usb_packet_process: default\n");
_reset_state();
_queue_err(FRAME_ERR_OTHER, frame->cid);
break;
Expand Down
Binary file added test/.DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions test/unit-test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ set(TEST_LIST
"-Wl,--wrap=screen_process"
ugui
""
simulator
""
)

find_package(CMocka REQUIRED)
Expand Down
Binary file removed test/unit-test/simulator
Binary file not shown.
72 changes: 0 additions & 72 deletions test/unit-test/simulator.c

This file was deleted.

152 changes: 152 additions & 0 deletions test/unit-test/test_simulator.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2023 Shift Cryptosecurity AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/*
infinite loop reading stdin line by line
each line is e.g. a hex encoded string containing the USB message that is sent by the host (e.g. the USB msgs that is sent by the bitbox-api-rs client library)
process the usb message
print the response usb messages to stdout, again one message per line, hex encoded
somehow expose a usb connection to this c program. There read the messages from stdin and write the responses to stdout.
When read the message, call the message handling function of the firmware so that it takes care of everything and returns the output
*/

// https://github.com/Coldcard/firmware/blob/master/unix/simulator.py#L296
// Coldcard implements in python and uses `serial` library to access to USB serial ports
// https://pyserial.readthedocs.io/en/latest/pyserial.html

// For C, there is usbip and gadget api libraries
// usbip allows remote/local USB devices to communicate over IP
// gadget api allows to emulate USB devices, but its protocol seems complex

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "usb/usb_processing.h"

#define USB_DESC_HID_EP_SIZE 0x40
#define USB_REPORT_SIZE USB_DESC_HID_EP_SIZE
#define USB_HID_REPORT_IN_SIZE USB_REPORT_SIZE
#define USB_HID_REPORT_OUT_SIZE USB_REPORT_SIZE

static uint8_t _out_report[USB_HID_REPORT_OUT_SIZE];
const char* fifo_path = "./tmp/myfifo";
int fd, num_read;

int hex_to_uint8(char high, char low, uint8_t* num) {
*num = 0;

if (high >= '0' && high <= '9')
{
*num += (high - '0') << 4;
}
else if (high >= 'A' && high <= 'F')
{
*num += (high - 'A' + 10) << 4;
}
else if (high >= 'a' && high <= 'f')
{
*num += (high - 'a' + 10) << 4;
}
else
{
return 1;
}

if (low >= '0' && low <= '9')
{
*num += (low - '0');
}
else if (low >= 'A' && low <= 'F')
{
*num += (low - 'A' + 10);
}
else if (low >= 'a' && low <= 'f')
{
*num += (low - 'a' + 10);
}
else
{
return 1;
}

return 0;
}

char* get_usb_message(char* buffer, uint8_t* input) {
char* res = fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n\r")] = 0;
for (int i = 0; i < 512; i+=2) {
hex_to_uint8(buffer[i], buffer[i+1], &input[i/2]);
printf("%02x ", input[i/2]);
}
return res;
}

int get_usb_message2(char* buffer, uint8_t* input) {
num_read = read(fd, buffer, sizeof(buffer));
buffer[strcspn(buffer, "\n\r")] = 0;
for (int i = 0; i < 512; i+=2) {
hex_to_uint8(buffer[i], buffer[i+1], &input[i/2]);
printf("%02x ", input[i/2]);
}
return num_read;
}

void simulate_firmware_execution(uint8_t* input) {
//u2f_packet_process

//usb_processing_process
//chain of call: firmware_main_loop -> usb_processing_process -> _usb_consume_incoming_packets ->
// _usb_execute_packet
//usb_processing_process(usb_processing_hww());
/* First, process all the incoming USB traffic. */
//usb_processing_process(usb_processing_hww());
/*
* If USB has generated events at the application level,
* process them now.
*/
//hww_process();

//usb_packet_process but it puts the packet into queue
//chain of call: usb_packet_process -> usb_processing_enqueue -> _build_packet
memcpy(_out_report, input, sizeof(uint8_t) * sizeof(_out_report));
usb_packet_process((const USB_FRAME*)_out_report);
}

int main(void) {
char buffer[1024];
uint8_t input[1024];

printf("opening fifo\n");

fd = open(fifo_path, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
printf("opened fifo\n");

while (get_usb_message2(buffer, input)) {
printf("got input\n");
if (strcmp(buffer, "") == 0) {
break;
}
simulate_firmware_execution(input);
}

close(fd);
return 0;
}

0 comments on commit c0380b7

Please sign in to comment.