From 49f512c5ab0cc3fe30b5f5a065ce2ffcf576fec7 Mon Sep 17 00:00:00 2001 From: asi345 Date: Wed, 8 Nov 2023 16:43:41 +0100 Subject: [PATCH] simulator: implement a simulator for bitbox02 device HWI is thinking of updating its support policy such that supported wallets must implement a simulator/emulator. See https://github.com/bitcoin-core/HWI/issues/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 --- .gitignore | 2 + py/send_message.py | 69 ++++++++ src/rust/bitbox02-rust/src/workflow.rs | 1 + .../bitbox02-rust/src/workflow/confirm.rs | 2 + .../bitbox02-rust/src/workflow/mnemonic.rs | 2 +- .../src/workflow/mnemonic_c_unit_tests.rs | 48 ++++++ src/rust/bitbox02-rust/src/workflow/status.rs | 2 + .../src/workflow/trinary_input_string.rs | 2 + src/rust/bitbox02/src/lib.rs | 8 + src/rust/bitbox02/src/ui.rs | 1 + src/rust/bitbox02/src/ui/types.rs | 8 +- .../bitbox02/src/ui/ui_stub_c_unit_tests.rs | 163 ++++++++++++++++++ test/unit-test/CMakeLists.txt | 6 +- test/unit-test/framework/mock_securechip.c | 6 +- test/unit-test/test_simulator.c | 155 +++++++++++++++++ 15 files changed, 467 insertions(+), 8 deletions(-) create mode 100644 src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs create mode 100644 src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs create mode 100644 test/unit-test/test_simulator.c diff --git a/.gitignore b/.gitignore index fd916a87ba..c17fdbcfaa 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ .ccls-cache compile_commands.json compile_flags.txt +.vscode +.DS_Store # gnu global /src/GPATH diff --git a/py/send_message.py b/py/send_message.py index a56f66487c..47fa429ce9 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -18,6 +18,7 @@ # pylint: disable=too-many-lines import argparse +import socket import pprint import sys from typing import List, Any, Optional, Callable, Union, Tuple, Sequence @@ -41,6 +42,7 @@ FirmwareVersionOutdatedException, u2fhid, bitbox_api_protocol, + PhysicalLayer, ) import u2f @@ -1556,6 +1558,65 @@ def run(self) -> int: return 0 +def connect_to_simulator_bitbox(debug: bool) -> int: + """ + Connects and runs the main menu on host computer, + simulating a BitBox02 connected over USB. + """ + + class Simulator(PhysicalLayer): + """ + Simulator class handles the communication + with the firmware simulator + """ + + def __init__(self) -> None: + self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + port = 15423 + self.client_socket.bind(("", port)) + self.client_socket.listen(50) + print(f"Waiting for connection on port {port}") + self.connection, addr = self.client_socket.accept() + print(f"Connected to {addr}") + + def write(self, data: bytes) -> None: + self.connection.send(data[1:]) + if debug: + print(f"Written to the simulator:\n{data.hex()[2:]}") + + def read(self, size: int, timeout_ms: int) -> bytes: + res = self.connection.recv(64) + if debug: + print(f"Read from the simulator:\n{res.hex()}") + return res + + def __del__(self) -> None: + print("Simulator quit") + if self.connection: + self.connection.shutdown(socket.SHUT_RDWR) + self.connection.close() + + simulator = Simulator() + + device_info: devices.DeviceInfo = { + "serial_number": "v9.16.0", + "path": b"", + "product_string": "BitBox02BTC", + } + noise_config = bitbox_api_protocol.BitBoxNoiseConfig() + bitbox_connection = bitbox02.BitBox02( + transport=u2fhid.U2FHid(simulator), + device_info=device_info, + noise_config=noise_config, + ) + try: + bitbox_connection.check_min_version() + except FirmwareVersionOutdatedException as exc: + print("WARNING: ", exc) + + return SendMessage(bitbox_connection, debug).run() + + def connect_to_usb_bitbox(debug: bool, use_cache: bool) -> int: """ Connects and runs the main menu on a BitBox02 connected @@ -1643,6 +1704,11 @@ def main() -> int: parser = argparse.ArgumentParser(description="Tool for communicating with bitbox device") parser.add_argument("--debug", action="store_true", help="Print messages sent and received") parser.add_argument("--u2f", action="store_true", help="Use u2f menu instead") + parser.add_argument( + "--simulator", + action="store_true", + help="Connect to the BitBox02 simulator instead of a real BitBox02", + ) parser.add_argument( "--no-cache", action="store_true", help="Don't use cached or store noise keys" ) @@ -1663,6 +1729,9 @@ def main() -> int: return u2fapp.run() return 1 + if args.simulator: + return connect_to_simulator_bitbox(args.debug) + return connect_to_usb_bitbox(args.debug, not args.no_cache) diff --git a/src/rust/bitbox02-rust/src/workflow.rs b/src/rust/bitbox02-rust/src/workflow.rs index 352ca4ad58..edfbfa103f 100644 --- a/src/rust/bitbox02-rust/src/workflow.rs +++ b/src/rust/bitbox02-rust/src/workflow.rs @@ -15,6 +15,7 @@ pub mod cancel; pub mod confirm; pub mod menu; +#[cfg_attr(feature = "c-unit-testing", path = "workflow/mnemonic_c_unit_tests.rs")] pub mod mnemonic; pub mod pairing; pub mod password; diff --git a/src/rust/bitbox02-rust/src/workflow/confirm.rs b/src/rust/bitbox02-rust/src/workflow/confirm.rs index 19dc14701e..ef3ae196c5 100644 --- a/src/rust/bitbox02-rust/src/workflow/confirm.rs +++ b/src/rust/bitbox02-rust/src/workflow/confirm.rs @@ -31,5 +31,7 @@ pub async fn confirm(params: &Params<'_>) -> Result<(), UserAbort> { }; }); component.screen_stack_push(); + #[cfg(feature = "c-unit-testing")] + bitbox02::print_stdout(&format!("CONFIRM SCREEN START\nTITLE: {}\nBODY: {}\nCONFIRM SCREEN END\n", params.title, params.body)); option_no_screensaver(&result).await } diff --git a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs index 075b28384a..0b003f1c8d 100644 --- a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs +++ b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs @@ -502,4 +502,4 @@ mod tests { &bruteforce_lastword(&mnemonic) ); } -} +} \ No newline at end of file diff --git a/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs b/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs new file mode 100644 index 0000000000..10af0b1b8f --- /dev/null +++ b/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs @@ -0,0 +1,48 @@ +// Copyright 2024 Shift Crypto 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. + +pub use super::cancel::Error as CancelError; +use super::confirm; + +use alloc::string::String; +use alloc::string::ToString; + +pub async fn show_and_confirm_mnemonic(words: &[&str]) -> Result<(), CancelError> { + let _ = confirm::confirm(&confirm::Params { + title: "", + body: "Please confirm\neach word", + accept_only: true, + accept_is_nextarrow: true, + ..Default::default() + }) + .await; + + for word in words.iter() { + bitbox02::println_stdout(word); + } + bitbox02::println_stdout("Words confirmed"); + + Ok(()) +} + +pub async fn get() -> Result, CancelError> { + let words = "boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide"; + bitbox02::println_stdout("Restored from recovery words below:"); + bitbox02::println_stdout(words); + + Ok(zeroize::Zeroizing::new( + words + .to_string() + )) +} \ No newline at end of file diff --git a/src/rust/bitbox02-rust/src/workflow/status.rs b/src/rust/bitbox02-rust/src/workflow/status.rs index d5a38ec30f..4bac5b8e91 100644 --- a/src/rust/bitbox02-rust/src/workflow/status.rs +++ b/src/rust/bitbox02-rust/src/workflow/status.rs @@ -21,5 +21,7 @@ pub async fn status(title: &str, status_success: bool) { *result.borrow_mut() = Some(()); }); component.screen_stack_push(); + #[cfg(feature = "c-unit-testing")] + bitbox02::print_stdout(&format!("STATUS SCREEN START\nTITLE: {}\nSTATUS SCREEN END\n", title)); option_no_screensaver(&result).await } diff --git a/src/rust/bitbox02-rust/src/workflow/trinary_input_string.rs b/src/rust/bitbox02-rust/src/workflow/trinary_input_string.rs index 62f8d6f259..9b300d5f89 100644 --- a/src/rust/bitbox02-rust/src/workflow/trinary_input_string.rs +++ b/src/rust/bitbox02-rust/src/workflow/trinary_input_string.rs @@ -49,6 +49,8 @@ pub async fn enter( bitbox02::ui::trinary_input_string_set_input(&mut component, preset); } component.screen_stack_push(); + #[cfg(feature = "c-unit-testing")] + bitbox02::print_stdout(&format!("ENTER SCREEN START\nTITLE: {}\nENTER SCREEN END\n", params.title)); option(&result) .await .or(Err(super::cancel::Error::Cancelled)) diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs index e5e05280d1..5c46f21429 100644 --- a/src/rust/bitbox02/src/lib.rs +++ b/src/rust/bitbox02/src/lib.rs @@ -183,6 +183,14 @@ pub fn print_stdout(msg: &str) { } } +#[cfg(any(feature = "testing", feature = "c-unit-testing"))] +pub fn println_stdout(msg: &str) { + unsafe { + bitbox02_sys::printf(crate::util::str_to_cstr_vec(msg).unwrap().as_ptr()); + bitbox02_sys::printf(crate::util::str_to_cstr_vec("\n").unwrap().as_ptr()); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/rust/bitbox02/src/ui.rs b/src/rust/bitbox02/src/ui.rs index ba3af064d0..5d9241d7c8 100644 --- a/src/rust/bitbox02/src/ui.rs +++ b/src/rust/bitbox02/src/ui.rs @@ -16,6 +16,7 @@ mod types; #[cfg_attr(feature = "testing", path = "ui/ui_stub.rs")] +#[cfg_attr(not(feature = "testing"), cfg_attr(feature = "c-unit-testing", path = "ui/ui_stub_c_unit_tests.rs"))] // We don't actually use ui::ui anywhere, we re-export below. #[allow(clippy::module_inception)] mod ui; diff --git a/src/rust/bitbox02/src/ui/types.rs b/src/rust/bitbox02/src/ui/types.rs index c51c56231d..d4972f21eb 100644 --- a/src/rust/bitbox02/src/ui/types.rs +++ b/src/rust/bitbox02/src/ui/types.rs @@ -21,7 +21,7 @@ use util::Survive; pub use bitbox02_sys::trinary_choice_t as TrinaryChoice; // Taking the constant straight from C, as it's excluding the null terminator. -#[cfg_attr(feature = "testing", allow(dead_code))] +#[cfg_attr(any(feature = "testing", feature = "c-unit-testing"), allow(dead_code))] pub(crate) const MAX_LABEL_SIZE: usize = bitbox02_sys::MAX_LABEL_SIZE as _; #[derive(Default)] @@ -33,7 +33,7 @@ pub enum Font { } impl Font { - #[cfg_attr(feature = "testing", allow(dead_code))] + #[cfg_attr(any(feature = "testing", feature = "c-unit-testing"), allow(dead_code))] pub(crate) fn as_ptr(&self) -> *const bitbox02_sys::UG_FONT { match self { Font::Default => core::ptr::null() as *const _, @@ -65,7 +65,7 @@ pub struct ConfirmParams<'a> { } impl<'a> ConfirmParams<'a> { - #[cfg_attr(feature = "testing", allow(dead_code))] + #[cfg_attr(any(feature = "testing", feature = "c-unit-testing"), allow(dead_code))] /// `title_scratch` and `body_scratch` exist to keep the data /// alive for as long as the C params live. pub(crate) fn to_c_params( @@ -110,7 +110,7 @@ pub struct TrinaryInputStringParams<'a> { } impl<'a> TrinaryInputStringParams<'a> { - #[cfg_attr(feature = "testing", allow(dead_code))] + #[cfg_attr(any(feature = "testing", feature = "c-unit-testing"), allow(dead_code))] pub(crate) fn to_c_params( &self, title_scratch: &'a mut Vec, diff --git a/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs new file mode 100644 index 0000000000..19100db51f --- /dev/null +++ b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs @@ -0,0 +1,163 @@ +// Copyright 2024 Shift Crypto 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. + +//! Stubs for the Bitbox02 simulator and also C unit-tests. + +pub use super::types::{ + AcceptRejectCb, ConfirmParams, ContinueCancelCb, Font, MenuParams, SelectWordCb, TrinaryChoice, + TrinaryChoiceCb, TrinaryInputStringParams, +}; + +use crate::input::SafeInputString; + +use core::marker::PhantomData; + +extern crate alloc; + +pub struct Component<'a> { + is_pushed: bool, + _p: PhantomData<&'a ()>, +} + +impl<'a> Component<'a> { + pub fn screen_stack_push(&mut self) { + if self.is_pushed { + panic!("component pushed twice"); + } + self.is_pushed = true; + } +} + +impl<'a> Drop for Component<'a> { + fn drop(&mut self) { + if !self.is_pushed { + panic!("component not pushed"); + } + } +} + +pub fn trinary_input_string_create<'a, F>( + _params: &TrinaryInputStringParams, + mut confirm_callback: F, + _cancel_callback: Option>, +) -> Component<'a> +where + F: FnMut(SafeInputString) + 'a, +{ + confirm_callback(SafeInputString::new()); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn confirm_create<'a, F>(_params: &ConfirmParams, mut result_callback: F) -> Component<'a> +where + F: FnMut(bool) + 'a, +{ + result_callback(true); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn screen_process() {} + +pub fn status_create<'a, F>(_text: &str, _status_success: bool, mut callback: F) -> Component<'a> +where + F: FnMut() + 'a, +{ + callback(); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn sdcard_create<'a, F>(_insert: bool, mut continue_callback: F) -> Component<'a> +where + F: FnMut() + 'a, +{ + continue_callback(); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn menu_create(_params: MenuParams<'_>) -> Component<'_> { + panic!("not implemented"); +} + +pub fn trinary_choice_create<'a>( + _message: &'a str, + _label_left: &'a str, + _label_middle: &'a str, + _label_right: &'a str, + _chosen_callback: TrinaryChoiceCb, +) -> Component<'a> { + panic!("not implemented") +} + +pub fn confirm_transaction_address_create<'a, 'b>( + _amount: &'a str, + _address: &'a str, + mut callback: AcceptRejectCb<'b>, +) -> Component<'b> { + callback(true); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn confirm_transaction_fee_create<'a, 'b>( + _amount: &'a str, + _fee: &'a str, + _longtouch: bool, + mut callback: AcceptRejectCb<'b>, +) -> Component<'b> { + callback(true); + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn trinary_input_string_set_input(_component: &mut Component, _word: &str) { + panic!("not implemented") +} + +pub fn with_lock_animation(f: F) { + f() +} + +pub fn screen_stack_pop_all() {} + +pub fn progress_create<'a>(_title: &str) -> Component<'a> { + Component { + is_pushed: false, + _p: PhantomData, + } +} + +pub fn progress_set(_component: &mut Component, _progress: f32) {} + +pub fn empty_create<'a>() -> Component<'a> { + Component { + is_pushed: false, + _p: PhantomData, + } +} \ No newline at end of file diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index 8f79f0516e..1144125115 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -240,6 +240,8 @@ set(TEST_LIST "-Wl,--wrap=screen_process" ugui "" + simulator + "" ) find_package(CMocka REQUIRED) @@ -263,7 +265,9 @@ foreach(I RANGE 0 ${TEST_LIST_LEN} 2) ${CMOCKA_LIBRARIES} ${TEST_LINK_ARGS} ) - add_test(NAME test_${TEST_NAME} COMMAND ${EXE}) + if(NOT ${TEST_NAME} STREQUAL "simulator") + add_test(NAME test_${TEST_NAME} COMMAND ${EXE}) + endif() endforeach() diff --git a/test/unit-test/framework/mock_securechip.c b/test/unit-test/framework/mock_securechip.c index 957c42d840..8709b261a0 100644 --- a/test/unit-test/framework/mock_securechip.c +++ b/test/unit-test/framework/mock_securechip.c @@ -86,10 +86,12 @@ bool securechip_attestation_sign(const uint8_t* msg, uint8_t* signature_out) bool securechip_monotonic_increments_remaining(uint32_t* remaining_out) { - return false; + *remaining_out = 1; + return true; } bool securechip_model(securechip_model_t* model_out) { - return false; + *model_out = SECURECHIP_ATECC608B; + return true; } diff --git a/test/unit-test/test_simulator.c b/test/unit-test/test_simulator.c new file mode 100644 index 0000000000..346f88f06c --- /dev/null +++ b/test/unit-test/test_simulator.c @@ -0,0 +1,155 @@ +// Copyright 2023-2024 Shift Crypto 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. + +#include "hww.h" +#include "memory/bitbox02_smarteeprom.h" +#include "usb/usb_packet.c" +#include "usb/usb_processing.c" +#include "usb/usb_processing.h" +#include "workflow/idle_workflow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define BUFFER_SIZE 1024 +#define SOCKET + +static uint8_t _out_report[USB_HID_REPORT_OUT_SIZE]; +char out_buffer[2 * USB_HID_REPORT_OUT_SIZE]; +int data_len; +int sockfd; + +int get_usb_message_socket(char* buffer, uint8_t* input) +{ + int num_read = read(sockfd, buffer, USB_HID_REPORT_OUT_SIZE); + for (int i = 0; i < USB_HID_REPORT_OUT_SIZE; i++) { + input[i] = (uint8_t)buffer[i]; + } + return num_read; +} + +void send_usb_message_socket(void) +{ + struct queue* q = queue_hww_queue(); + const uint8_t* data = queue_pull(q); + if (data != NULL) { + data_len = 256 * (int)data[5] + (int)data[6]; + for (int i = 0; i < USB_HID_REPORT_OUT_SIZE; i++) { + out_buffer[i] = (char)data[i]; + } + if (!write(sockfd, out_buffer, USB_HID_REPORT_OUT_SIZE)) { + perror("ERROR, could not write to socket"); + exit(1); + } + } +} + +void simulate_firmware_execution(uint8_t* input) +{ + memcpy(_out_report, input, sizeof(_out_report)); + usb_packet_process((const USB_FRAME*)_out_report); + rust_workflow_spin(); + rust_async_usb_spin(); + usb_processing_process(usb_processing_hww()); +} + +int main(void) +{ + char buffer[BUFFER_SIZE]; + uint8_t input[BUFFER_SIZE]; + int (*get_message)(char*, uint8_t*); + void (*send_message)(void); + bool memory_success, sd_success; + int temp_len; + + // Establish socket connection with client + int portno; + struct sockaddr_in serv_addr; + struct hostent* server; + portno = 15423; + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + perror("ERROR opening socket"); + return 1; + } + server = gethostbyname("host.docker.internal"); + if (server == NULL) { + fprintf(stderr, "ERROR, no such host\n"); + return 1; + } + memset((char*)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + memcpy((char*)&serv_addr.sin_addr.s_addr, (char*)server->h_addr_list[0], server->h_length); + serv_addr.sin_port = htons(portno); + if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + perror("ERROR, could not connect to client"); + return 1; + } + + get_message = get_usb_message_socket; + send_message = send_usb_message_socket; + + // BitBox02 simulation initializaition + usb_processing_init(); + usb_processing_set_send(usb_processing_hww(), send_message); + printf("USB setup success\n"); + + hww_setup(); + printf("HWW setup success\n"); + + sd_success = sd_format(); + printf("Sd card setup %s\n", sd_success ? "success" : "failed"); + if (!sd_success) { + perror("ERROR, sd card setup failed"); + return 1; + } + + mock_memory_factoryreset(); + memory_interface_functions_t ifs = { + .random_32_bytes = random_32_bytes_mcu, + }; + memory_success = memory_setup(&ifs); + printf("Memory setup %s\n", memory_success ? "success" : "failed"); + if (!memory_success) { + perror("ERROR, memory setup failed"); + return 1; + } + + smarteeprom_bb02_config(); + bitbox02_smarteeprom_init(); + idle_workflow_blocking(); + + while (1) { + if (!get_message(buffer, input)) break; + simulate_firmware_execution(input); + + temp_len = data_len - (USB_HID_REPORT_OUT_SIZE - 7); + while (temp_len > 0) { + usb_processing_process(usb_processing_hww()); + temp_len -= (USB_HID_REPORT_OUT_SIZE - 5); + } + } + close(sockfd); + return 0; +} \ No newline at end of file