Skip to content

Commit fd97755

Browse files
committed
api/firmware/device: info use device status fields
Return struct containing REQ_INFO response data from device.info(). Amongst the already existing fields that were previously returned by info() as a tuple, also add the initialized status to the struct that is returned after firmware version 9.20.0. In case the device does not respond with the initialized status byte yet it will be nil. Otherwise true/false depending on the initialized status (seeded and backup created) of the device. Also use the information from device.info to set the device.status earlier to avoid showing the password video if the device is not initialized.
1 parent 788a939 commit fd97755

File tree

2 files changed

+58
-30
lines changed

2 files changed

+58
-30
lines changed

api/firmware/device.go

+57-28
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ type DeviceInfo struct {
114114
SecurechipModel string `json:"securechipModel"`
115115
}
116116

117+
// DeviceInfoREQ_INFO is the data returned from the REQ_INFO api call.
118+
type DeviceInfoREQ_INFO struct {
119+
// Device firmware version. REQ_INFO is supported since v4.3.0 which means this field will
120+
// always be at least v4.3.0.
121+
version *semver.SemVer
122+
// Device Platform/Edition e.g. "bitbox02-btconly".
123+
product common.Product
124+
// Device unlocked status, true if device is unlocked.
125+
unlocked bool
126+
// Device initialized status, true if device is seeded and backup has been stored.
127+
initialized *bool
128+
}
129+
117130
// NewDevice creates a new instance of Device.
118131
// version:
119132
//
@@ -143,26 +156,26 @@ func NewDevice(
143156
}
144157
}
145158

146-
// info uses the opInfo api endpoint to learn about the version, platform/edition, and unlock
147-
// status (true if unlocked).
148-
func (device *Device) info() (*semver.SemVer, common.Product, bool, error) {
159+
// info uses the opInfo api endpoint to learn about the version, platform/edition, unlock
160+
// status (true if unlocked), and initialized status (true if device can be unlocked/is unlocked).
161+
func (device *Device) info() (*DeviceInfoREQ_INFO, error) {
149162

150163
// CAREFUL: hwwInfo is called on the raw transport, not on device.rawQuery, which behaves
151164
// differently depending on the firmware version. Reason: the version is not
152165
// available (this call is used to get the version), so it must work for all firmware versions.
153166
response, err := device.communication.Query([]byte(hwwInfo))
154167
if err != nil {
155-
return nil, "", false, err
168+
return nil, err
156169
}
157170

158-
if len(response) < 4 {
159-
return nil, "", false, errp.New("unexpected response")
171+
if len(response) < 5 {
172+
return nil, errp.New("unexpected response")
160173
}
161174
versionStrLen, response := int(response[0]), response[1:]
162175
versionBytes, response := response[:versionStrLen], response[versionStrLen:]
163176
version, err := semver.NewSemVerFromString(string(versionBytes))
164177
if err != nil {
165-
return nil, "", false, err
178+
return nil, err
166179
}
167180
platformByte, response := response[0], response[1:]
168181
editionByte, response := response[0], response[1:]
@@ -175,24 +188,40 @@ func (device *Device) info() (*semver.SemVer, common.Product, bool, error) {
175188
}
176189
editions, ok := products[platformByte]
177190
if !ok {
178-
return nil, "", false, errp.Newf("unrecognized platform: %v", platformByte)
191+
return nil, errp.Newf("unrecognized platform: %v", platformByte)
179192
}
180193
product, ok := editions[editionByte]
181194
if !ok {
182-
return nil, "", false, errp.Newf("unrecognized platform/edition: %v/%v", platformByte, editionByte)
195+
return nil, errp.Newf("unrecognized platform/edition: %v/%v", platformByte, editionByte)
183196
}
184197

185198
var unlocked bool
186-
unlockedByte := response[0]
199+
unlockedByte, response := response[0], response[1:]
187200
switch unlockedByte {
188201
case 0x00:
189202
unlocked = false
190203
case 0x01:
191204
unlocked = true
192205
default:
193-
return nil, "", false, errp.New("unexpected reply")
206+
return nil, errp.New("unexpected reply")
207+
}
208+
209+
deviceInfo := DeviceInfoREQ_INFO{
210+
version: version,
211+
product: product,
212+
unlocked: unlocked,
194213
}
195-
return version, product, unlocked, nil
214+
215+
// Since 9.20.0 REQ_INFO responds with a byte for the initialized status.
216+
if version.AtLeast(semver.NewSemVer(9, 20, 0)) {
217+
initialized := response[0] == 0x01
218+
if response[0] != 0x00 && response[0] != 0x01 {
219+
return nil, errp.New("unexpected reply")
220+
}
221+
deviceInfo.initialized = &initialized
222+
}
223+
224+
return &deviceInfo, nil
196225
}
197226

198227
// Version returns the firmware version.
@@ -206,25 +235,13 @@ func (device *Device) Version() *semver.SemVer {
206235
// inferVersionAndProduct either sets the version and product by using OP_INFO if they were not
207236
// provided. In this case, the firmware is assumed to be >=v4.3.0, before that OP_INFO was not
208237
// available.
209-
func (device *Device) inferVersionAndProduct() error {
238+
func (device *Device) inferVersionAndProduct(version *semver.SemVer, product common.Product) {
210239
// The version has not been provided, so we try to get it from OP_INFO.
211240
if device.version == nil {
212-
version, product, _, err := device.info()
213-
if err != nil {
214-
return errp.New(
215-
"OP_INFO unavailable; need to provide version and product via the USB HID descriptor")
216-
}
217241
device.log.Info(fmt.Sprintf("OP_INFO: version=%s, product=%s", version, product))
218-
219-
// sanity check
220-
if !version.AtLeast(semver.NewSemVer(4, 3, 0)) {
221-
return errp.New("OP_INFO is not supposed to exist below v4.3.0")
222-
}
223-
224242
device.version = version
225243
device.product = &product
226244
}
227-
return nil
228245
}
229246

230247
// Init initializes the device. It changes the status to StatusRequireAppUpgrade if needed,
@@ -241,11 +258,23 @@ func (device *Device) Init() error {
241258
device.channelHashDeviceVerified = false
242259
device.sendCipher = nil
243260
device.receiveCipher = nil
244-
device.changeStatus(StatusConnected)
245261

246-
if err := device.inferVersionAndProduct(); err != nil {
247-
return err
262+
if device.version == nil || device.version.AtLeast(semver.NewSemVer(4, 3, 0)) {
263+
deviceInfo, err := device.info()
264+
if err != nil {
265+
return errp.New(
266+
"OP_INFO unavailable; need to provide version and product via the USB HID descriptor")
267+
}
268+
// Does nothing if device.version == nil
269+
device.inferVersionAndProduct(deviceInfo.version, deviceInfo.product)
270+
271+
if deviceInfo.unlocked || (deviceInfo.initialized != nil && *deviceInfo.initialized) {
272+
device.changeStatus(StatusInitialized)
273+
} else {
274+
device.changeStatus(StatusUninitialized)
275+
}
248276
}
277+
249278
if device.version.AtLeast(lowestNonSupportedFirmwareVersion) {
250279
device.changeStatus(StatusRequireAppUpgrade)
251280
return nil

api/firmware/status.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ const (
3939
// device. Use CreateBackup() to move to StatusInitialized.
4040
StatusSeeded Status = "seeded"
4141

42-
// StatusInitialized means the device is seeded and the backup was created, and the device is
43-
// unlocked. The keystore is ready to use.
42+
// StatusInitialized means the device is seeded and the backup was created.
4443
StatusInitialized Status = "initialized"
4544

4645
// StatusRequireFirmwareUpgrade means that the a firmware upgrade is required before being able

0 commit comments

Comments
 (0)