Skip to content

Commit 7e99207

Browse files
committed
maintenance: make BBApp able to talk to simulator.
This allows devs to use the app without a real device and without relying on the software keystore.
1 parent a16d5e9 commit 7e99207

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed

backend/backend.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,16 @@ func (backend *Backend) OnDeviceUninit(f func(string)) {
662662
backend.onDeviceUninit = f
663663
}
664664

665+
// SetTestDevice sets a test device to be used by the USB manager instead of real USB devices.
666+
func (backend *Backend) SetTestDevice(device *bitbox02.Device) {
667+
backend.usbManager.TestDevice = device
668+
}
669+
670+
// HasTestDevice returns whether a test device is set.
671+
func (backend *Backend) HasTestDevice() bool {
672+
return backend.usbManager.TestDevice != nil
673+
}
674+
665675
// Start starts the background services. It returns a channel of events to handle by the library
666676
// client.
667677
func (backend *Backend) Start() <-chan interface{} {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package simulator
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net"
7+
"os/exec"
8+
"time"
9+
10+
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02"
11+
"github.com/BitBoxSwiss/bitbox02-api-go/api/common"
12+
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
13+
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/mocks"
14+
"github.com/BitBoxSwiss/bitbox02-api-go/communication/u2fhid"
15+
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
16+
)
17+
18+
// SimulatorID is the identifier used for the BitBox02 simulator.
19+
const SimulatorID = "Bitbox02-Simulator"
20+
21+
// NewDevice connects to a BitBox02 simulator running on localhost:simulatorPort and returns
22+
// the bitbox02.Device instance. simulatorVersion is the version string of the simulator, e.g. "9.99.0".
23+
func NewDevice(simulatorPort int, simulatorVersion string) (*bitbox02.Device, error) {
24+
var err error
25+
var conn net.Conn
26+
for range 200 {
27+
conn, err = net.Dial("tcp", fmt.Sprintf("localhost:%d", simulatorPort))
28+
if err == nil {
29+
break
30+
}
31+
time.Sleep(50 * time.Millisecond)
32+
}
33+
if err != nil {
34+
return nil, err
35+
}
36+
const bitboxCMD = 0x80 + 0x40 + 0x01
37+
38+
communication := u2fhid.NewCommunication(conn, bitboxCMD)
39+
version, err := semver.NewSemVerFromString(simulatorVersion)
40+
if err != nil {
41+
return nil, err
42+
}
43+
device := bitbox02.NewDevice(SimulatorID, version, common.ProductBitBox02Multi,
44+
&mocks.Config{}, communication,
45+
)
46+
return device, nil
47+
}
48+
49+
// IsRunning checks if a BitBox02 simulator with the given version is running.
50+
func IsRunning(version string) bool {
51+
cmd := exec.Command("pgrep", "-f", fmt.Sprintf("%s-simulator", version))
52+
if err := cmd.Run(); err != nil {
53+
return false // no matching process
54+
}
55+
return true
56+
}
57+
58+
// DeviceInfo implements usb.DeviceInfo for the simulator.
59+
// All methods return hardcoded values.
60+
type DeviceInfo struct {
61+
}
62+
63+
// IsBluetooth implements usb.DeviceInfo.
64+
func (d DeviceInfo) IsBluetooth() bool {
65+
return false
66+
}
67+
68+
// VendorID implements usb.DeviceInfo.
69+
func (d DeviceInfo) VendorID() int {
70+
return 0x03eb
71+
}
72+
73+
// ProductID implements usb.DeviceInfo.
74+
func (d DeviceInfo) ProductID() int {
75+
return 0x2403
76+
}
77+
78+
// UsagePage implements usb.DeviceInfo.
79+
func (d DeviceInfo) UsagePage() int {
80+
return 0xfffff
81+
}
82+
83+
// Interface implements usb.DeviceInfo.
84+
func (d DeviceInfo) Interface() int {
85+
return 0
86+
}
87+
88+
// Serial implements usb.DeviceInfo.
89+
func (d DeviceInfo) Serial() string {
90+
return "simulator"
91+
}
92+
93+
// Product implements usb.DeviceInfo.
94+
func (d DeviceInfo) Product() string {
95+
return bitbox02common.FirmwareDeviceProductStringBitBox02Multi
96+
}
97+
98+
// Identifier implements usb.DeviceInfo.
99+
func (d DeviceInfo) Identifier() string {
100+
return SimulatorID
101+
}
102+
103+
// Open implements usb.DeviceInfo.
104+
func (d DeviceInfo) Open() (io.ReadWriteCloser, error) {
105+
return nil, nil
106+
}

backend/devices/usb/manager.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox"
2424
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02"
25+
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02/simulator"
2526
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02bootloader"
2627
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/device"
2728
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
@@ -63,6 +64,10 @@ func isBitBox(deviceInfo DeviceInfo) bool {
6364
return deviceInfo.VendorID() == bitboxVendorID && deviceInfo.ProductID() == bitboxProductID && (deviceInfo.UsagePage() == 0xffff || deviceInfo.Interface() == 0)
6465
}
6566

67+
func isSimulator(deviceInfo DeviceInfo) bool {
68+
return deviceInfo.Identifier() == simulator.SimulatorID
69+
}
70+
6671
func isBitBox02(deviceInfo DeviceInfo) bool {
6772
return (deviceInfo.Product() == bitbox02common.FirmwareDeviceProductStringBitBox02Multi ||
6873
deviceInfo.Product() == bitbox02common.FirmwareDeviceProductStringBitBox02BTCOnly ||
@@ -99,6 +104,9 @@ type Manager struct {
99104
socksProxy socksproxy.SocksProxy
100105

101106
log *logrus.Entry
107+
108+
// TestDevice is used to inject a device for testing purposes.
109+
TestDevice *bitbox02.Device
102110
}
103111

104112
// NewManager creates a new Manager. onRegister is called when a device has been
@@ -320,6 +328,8 @@ func (manager *Manager) listen() {
320328
manager.log.WithError(err).Error("Failed to register bitbox")
321329
continue
322330
}
331+
case isSimulator(deviceInfo):
332+
device = manager.TestDevice
323333
case isBitBox02(deviceInfo):
324334
var err error
325335
device, err = manager.makeBitBox02(deviceInfo)

cmd/servewallet/main.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import (
2222
"os/exec"
2323
"runtime"
2424
"strings"
25+
"time"
2526

2627
backendPkg "github.com/BitBoxSwiss/bitbox-wallet-app/backend"
2728
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/arguments"
2829
btctypes "github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/types"
30+
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02/simulator"
2931
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/usb"
3032
backendHandlers "github.com/BitBoxSwiss/bitbox-wallet-app/backend/handlers"
3133
"github.com/BitBoxSwiss/bitbox-wallet-app/util/config"
@@ -69,6 +71,10 @@ func (webdevEnvironment) NotifyUser(text string) {
6971

7072
// DeviceInfos implements backend.Environment.
7173
func (webdevEnvironment) DeviceInfos() []usb.DeviceInfo {
74+
if backend.HasTestDevice() {
75+
// We are in "test device" mode.
76+
return []usb.DeviceInfo{simulator.DeviceInfo{}}
77+
}
7278
return usb.DeviceInfos()
7379
}
7480

@@ -130,6 +136,41 @@ func (webdevEnvironment) DetectDarkTheme() bool {
130136
return false
131137
}
132138

139+
func handleBitboxSimulator(log *logrus.Entry, simulatorPort int, simulatorVersion string) error {
140+
portIsSet := simulatorPort != -1
141+
versionIsSet := simulatorVersion != ""
142+
143+
if portIsSet != versionIsSet {
144+
return fmt.Errorf("flags -simulatorPort and -simulatorVersion must be set together or not at all")
145+
}
146+
147+
if !portIsSet {
148+
return nil
149+
}
150+
go func() {
151+
for {
152+
if !simulator.IsRunning(simulatorVersion) {
153+
// Simulator not running. Unset test device.
154+
backend.SetTestDevice(nil)
155+
continue
156+
}
157+
// If the testdevice is already set, do nothing.
158+
if backend.HasTestDevice() {
159+
continue
160+
}
161+
162+
// Otherwise, connect to the simulator.
163+
device, err := simulator.NewDevice(simulatorPort, simulatorVersion)
164+
if err != nil {
165+
log.WithError(err).Fatal("Could not create new device from running simulator")
166+
}
167+
backend.SetTestDevice(device)
168+
time.Sleep(time.Millisecond * 50)
169+
}
170+
}()
171+
return nil
172+
}
173+
133174
func main() {
134175
config.SetAppDir("appfolder.dev")
135176

@@ -138,6 +179,8 @@ func main() {
138179
devservers := flag.Bool("devservers", true, "switch to dev servers")
139180
gapLimitsReceive := flag.Uint("gapLimitReceive", 0, "gap limit for receive addresses")
140181
gapLimitsChange := flag.Uint("gapLimitChange", 0, "gap limit for change addresses")
182+
simulatorPort := flag.Int("simulatorPort", -1, "port for the BitBox02 simulator")
183+
simulatorVersion := flag.String("simulatorVersion", "", "version of the BitBox02 simulator")
141184
flag.Parse()
142185

143186
var gapLimits *btctypes.GapLimits
@@ -161,6 +204,7 @@ func main() {
161204
log.Info("--------------- Started application --------------")
162205
// since we are in dev-mode, we can drop the authorization token
163206
connectionData := backendHandlers.NewConnectionData(-1, "")
207+
164208
newBackend, err := backendPkg.NewBackend(
165209
arguments.NewArguments(
166210
config.AppDir(),
@@ -177,6 +221,11 @@ func main() {
177221
handlers := backendHandlers.NewHandlers(backend, connectionData)
178222
log.WithFields(logrus.Fields{"address": address, "port": port}).Info("Listening for HTTP")
179223
fmt.Printf("Listening on: http://localhost:%d\n", port)
224+
225+
if err := handleBitboxSimulator(log, *simulatorPort, *simulatorVersion); err != nil {
226+
log.WithError(err).Fatal("Invalid simulator flags")
227+
}
228+
180229
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", address, port), handlers.Router); err != nil {
181230
log.WithFields(logrus.Fields{"address": address, "port": port, "error": err.Error()}).Fatal("Failed to listen for HTTP")
182231
}

0 commit comments

Comments
 (0)