1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-16 17:42:02 +00:00

embed/bootloader: add information about firmware (version, vendor, etc.) to Features message

This commit is contained in:
Pavol Rusnak 2018-02-06 17:06:43 +01:00
parent 548b8cb25b
commit 1c5beb1c12
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
9 changed files with 93 additions and 48 deletions

View File

@ -150,6 +150,9 @@ static secbool bootloader_usb_loop(const vendor_header * const vhdr, const image
return sectrue; // jump to firmware return sectrue; // jump to firmware
} }
break; break;
case 55: // GetFeatures
process_msg_GetFeatures(USB_IFACE_NUM, msg_size, buf, vhdr, hdr);
break;
default: default:
process_msg_unknown(USB_IFACE_NUM, msg_size, buf); process_msg_unknown(USB_IFACE_NUM, msg_size, buf);
break; break;

View File

@ -140,9 +140,13 @@ static secbool _send_msg(uint8_t iface_num, uint16_t msg_id, const pb_field_t fi
return sectrue; return sectrue;
} }
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MSG_SEND_INIT(TYPE) TYPE msg_send = TYPE##_init_default #define MSG_SEND_INIT(TYPE) TYPE msg_send = TYPE##_init_default
#define MSG_SEND_ASSIGN_VALUE(FIELD, VALUE) { msg_send.has_##FIELD = true; msg_send.FIELD = VALUE; } #define MSG_SEND_ASSIGN_VALUE(FIELD, VALUE) { msg_send.has_##FIELD = true; msg_send.FIELD = VALUE; }
#define MSG_SEND_ASSIGN_STRING(FIELD, VALUE) { msg_send.has_##FIELD = true; memset(msg_send.FIELD, 0, sizeof(msg_send.FIELD)); strncpy(msg_send.FIELD, VALUE, sizeof(msg_send.FIELD) - 1); } #define MSG_SEND_ASSIGN_STRING(FIELD, VALUE) { msg_send.has_##FIELD = true; memset(msg_send.FIELD, 0, sizeof(msg_send.FIELD)); strncpy(msg_send.FIELD, VALUE, sizeof(msg_send.FIELD) - 1); }
#define MSG_SEND_ASSIGN_STRING_LEN(FIELD, VALUE, LEN) { msg_send.has_##FIELD = true; memset(msg_send.FIELD, 0, sizeof(msg_send.FIELD)); strncpy(msg_send.FIELD, VALUE, MIN(LEN, sizeof(msg_send.FIELD) - 1)); }
#define MSG_SEND_ASSIGN_BYTES(FIELD, VALUE, LEN) { msg_send.has_##FIELD = true; memset(msg_send.FIELD.bytes, 0, sizeof(msg_send.FIELD.bytes)); memcpy(msg_send.FIELD.bytes, VALUE, MIN(LEN, sizeof(msg_send.FIELD.bytes))); msg_send.FIELD.size = MIN(LEN, sizeof(msg_send.FIELD.bytes)); }
#define MSG_SEND(TYPE) _send_msg(iface_num, MessageType_MessageType_##TYPE, TYPE##_fields, &msg_send) #define MSG_SEND(TYPE) _send_msg(iface_num, MessageType_MessageType_##TYPE, TYPE##_fields, &msg_send)
typedef struct { typedef struct {
@ -227,23 +231,46 @@ void send_user_abort(uint8_t iface_num, const char *msg)
MSG_SEND(Failure); MSG_SEND(Failure);
} }
void process_msg_Initialize(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr) static void send_msg_features(uint8_t iface_num, const vendor_header * const vhdr, const image_header * const hdr)
{ {
MSG_RECV_INIT(Initialize);
MSG_RECV(Initialize);
MSG_SEND_INIT(Features); MSG_SEND_INIT(Features);
MSG_SEND_ASSIGN_STRING(vendor, "trezor.io"); MSG_SEND_ASSIGN_STRING(vendor, "trezor.io");
MSG_SEND_ASSIGN_VALUE(major_version, VERSION_MAJOR); MSG_SEND_ASSIGN_VALUE(major_version, VERSION_MAJOR);
MSG_SEND_ASSIGN_VALUE(minor_version, VERSION_MINOR); MSG_SEND_ASSIGN_VALUE(minor_version, VERSION_MINOR);
MSG_SEND_ASSIGN_VALUE(patch_version, VERSION_PATCH); MSG_SEND_ASSIGN_VALUE(patch_version, VERSION_PATCH);
MSG_SEND_ASSIGN_VALUE(bootloader_mode, true); MSG_SEND_ASSIGN_VALUE(bootloader_mode, true);
MSG_SEND_ASSIGN_VALUE(firmware_present, (vhdr && hdr));
MSG_SEND_ASSIGN_STRING(model, "T"); MSG_SEND_ASSIGN_STRING(model, "T");
if (vhdr && hdr) {
MSG_SEND_ASSIGN_VALUE(firmware_present, true);
MSG_SEND_ASSIGN_VALUE(fw_major, (hdr->version & 0xFF));
MSG_SEND_ASSIGN_VALUE(fw_minor, ((hdr->version >> 8) & 0xFF));
MSG_SEND_ASSIGN_VALUE(fw_patch, ((hdr->version >> 16) & 0xFF));
MSG_SEND_ASSIGN_STRING_LEN(fw_vendor, vhdr->vstr, vhdr->vstr_len);
uint8_t hash[32];
vendor_keys_hash(vhdr, hash);
MSG_SEND_ASSIGN_BYTES(fw_vendor_keys, hash, 32);
} else {
MSG_SEND_ASSIGN_VALUE(firmware_present, false);
}
// TODO: pass info about installed firmware (vendor, version, etc.) // TODO: pass info about installed firmware (vendor, version, etc.)
MSG_SEND(Features); MSG_SEND(Features);
} }
void process_msg_Initialize(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr)
{
MSG_RECV_INIT(Initialize);
MSG_RECV(Initialize);
send_msg_features(iface_num, vhdr, hdr);
}
void process_msg_GetFeatures(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr)
{
MSG_RECV_INIT(GetFeatures);
MSG_RECV(GetFeatures);
send_msg_features(iface_num, vhdr, hdr);
}
void process_msg_Ping(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) void process_msg_Ping(uint8_t iface_num, uint32_t msg_size, uint8_t *buf)
{ {
MSG_RECV_INIT(Ping); MSG_RECV_INIT(Ping);

View File

@ -17,6 +17,7 @@ secbool msg_parse_header(const uint8_t *buf, uint16_t *msg_id, uint32_t *msg_siz
void send_user_abort(uint8_t iface_num, const char *msg); void send_user_abort(uint8_t iface_num, const char *msg);
void process_msg_Initialize(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr); void process_msg_Initialize(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr);
void process_msg_GetFeatures(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, const vendor_header * const vhdr, const image_header * const hdr);
void process_msg_Ping(uint8_t iface_num, uint32_t msg_size, uint8_t *buf); void process_msg_Ping(uint8_t iface_num, uint32_t msg_size, uint8_t *buf);
void process_msg_FirmwareErase(uint8_t iface_num, uint32_t msg_size, uint8_t *buf); void process_msg_FirmwareErase(uint8_t iface_num, uint32_t msg_size, uint8_t *buf);
int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, uint8_t *buf); int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, uint8_t *buf);

View File

@ -5,6 +5,8 @@ Features.label max_size:33
Features.revision max_size:20 Features.revision max_size:20
Features.bootloader_hash max_size:32 Features.bootloader_hash max_size:32
Features.model max_size:17 Features.model max_size:17
Features.fw_vendor max_size:256
Features.fw_vendor_keys max_size:32
Ping.message max_size:256 Ping.message max_size:256

View File

@ -18,25 +18,24 @@ const pb_field_t GetFeatures_fields[1] = {
PB_LAST_FIELD PB_LAST_FIELD
}; };
const pb_field_t Features_fields[19] = { const pb_field_t Features_fields[18] = {
PB_FIELD( 1, STRING , OPTIONAL, STATIC , FIRST, Features, vendor, vendor, 0), PB_FIELD( 1, STRING , OPTIONAL, STATIC , FIRST, Features, vendor, vendor, 0),
PB_FIELD( 2, UINT32 , OPTIONAL, STATIC , OTHER, Features, major_version, vendor, 0), PB_FIELD( 2, UINT32 , OPTIONAL, STATIC , OTHER, Features, major_version, vendor, 0),
PB_FIELD( 3, UINT32 , OPTIONAL, STATIC , OTHER, Features, minor_version, major_version, 0), PB_FIELD( 3, UINT32 , OPTIONAL, STATIC , OTHER, Features, minor_version, major_version, 0),
PB_FIELD( 4, UINT32 , OPTIONAL, STATIC , OTHER, Features, patch_version, minor_version, 0), PB_FIELD( 4, UINT32 , OPTIONAL, STATIC , OTHER, Features, patch_version, minor_version, 0),
PB_FIELD( 5, BOOL , OPTIONAL, STATIC , OTHER, Features, bootloader_mode, patch_version, 0), PB_FIELD( 5, BOOL , OPTIONAL, STATIC , OTHER, Features, bootloader_mode, patch_version, 0),
PB_FIELD( 6, STRING , OPTIONAL, STATIC , OTHER, Features, device_id, bootloader_mode, 0), PB_FIELD( 6, STRING , OPTIONAL, STATIC , OTHER, Features, device_id, bootloader_mode, 0),
PB_FIELD( 7, BOOL , OPTIONAL, STATIC , OTHER, Features, pin_protection, device_id, 0), PB_FIELD( 9, STRING , OPTIONAL, STATIC , OTHER, Features, language, device_id, 0),
PB_FIELD( 8, BOOL , OPTIONAL, STATIC , OTHER, Features, passphrase_protection, pin_protection, 0),
PB_FIELD( 9, STRING , OPTIONAL, STATIC , OTHER, Features, language, passphrase_protection, 0),
PB_FIELD( 10, STRING , OPTIONAL, STATIC , OTHER, Features, label, language, 0), PB_FIELD( 10, STRING , OPTIONAL, STATIC , OTHER, Features, label, language, 0),
PB_FIELD( 12, BOOL , OPTIONAL, STATIC , OTHER, Features, initialized, label, 0), PB_FIELD( 12, BOOL , OPTIONAL, STATIC , OTHER, Features, initialized, label, 0),
PB_FIELD( 13, BYTES , OPTIONAL, STATIC , OTHER, Features, revision, initialized, 0), PB_FIELD( 13, BYTES , OPTIONAL, STATIC , OTHER, Features, revision, initialized, 0),
PB_FIELD( 14, BYTES , OPTIONAL, STATIC , OTHER, Features, bootloader_hash, revision, 0), PB_FIELD( 18, BOOL , OPTIONAL, STATIC , OTHER, Features, firmware_present, revision, 0),
PB_FIELD( 15, BOOL , OPTIONAL, STATIC , OTHER, Features, imported, bootloader_hash, 0),
PB_FIELD( 16, BOOL , OPTIONAL, STATIC , OTHER, Features, pin_cached, imported, 0),
PB_FIELD( 17, BOOL , OPTIONAL, STATIC , OTHER, Features, passphrase_cached, pin_cached, 0),
PB_FIELD( 18, BOOL , OPTIONAL, STATIC , OTHER, Features, firmware_present, passphrase_cached, 0),
PB_FIELD( 21, STRING , OPTIONAL, STATIC , OTHER, Features, model, firmware_present, 0), PB_FIELD( 21, STRING , OPTIONAL, STATIC , OTHER, Features, model, firmware_present, 0),
PB_FIELD( 22, UINT32 , OPTIONAL, STATIC , OTHER, Features, fw_major, model, 0),
PB_FIELD( 23, UINT32 , OPTIONAL, STATIC , OTHER, Features, fw_minor, fw_major, 0),
PB_FIELD( 24, UINT32 , OPTIONAL, STATIC , OTHER, Features, fw_patch, fw_minor, 0),
PB_FIELD( 25, STRING , OPTIONAL, STATIC , OTHER, Features, fw_vendor, fw_patch, 0),
PB_FIELD( 26, BYTES , OPTIONAL, STATIC , OTHER, Features, fw_vendor_keys, fw_vendor, 0),
PB_LAST_FIELD PB_LAST_FIELD
}; };

View File

@ -67,7 +67,7 @@ typedef struct _Failure {
} Failure; } Failure;
typedef PB_BYTES_ARRAY_T(20) Features_revision_t; typedef PB_BYTES_ARRAY_T(20) Features_revision_t;
typedef PB_BYTES_ARRAY_T(32) Features_bootloader_hash_t; typedef PB_BYTES_ARRAY_T(32) Features_fw_vendor_keys_t;
typedef struct _Features { typedef struct _Features {
bool has_vendor; bool has_vendor;
char vendor[33]; char vendor[33];
@ -81,10 +81,6 @@ typedef struct _Features {
bool bootloader_mode; bool bootloader_mode;
bool has_device_id; bool has_device_id;
char device_id[25]; char device_id[25];
bool has_pin_protection;
bool pin_protection;
bool has_passphrase_protection;
bool passphrase_protection;
bool has_language; bool has_language;
char language[17]; char language[17];
bool has_label; bool has_label;
@ -93,18 +89,20 @@ typedef struct _Features {
bool initialized; bool initialized;
bool has_revision; bool has_revision;
Features_revision_t revision; Features_revision_t revision;
bool has_bootloader_hash;
Features_bootloader_hash_t bootloader_hash;
bool has_imported;
bool imported;
bool has_pin_cached;
bool pin_cached;
bool has_passphrase_cached;
bool passphrase_cached;
bool has_firmware_present; bool has_firmware_present;
bool firmware_present; bool firmware_present;
bool has_model; bool has_model;
char model[17]; char model[17];
bool has_fw_major;
uint32_t fw_major;
bool has_fw_minor;
uint32_t fw_minor;
bool has_fw_patch;
uint32_t fw_patch;
bool has_fw_vendor;
char fw_vendor[256];
bool has_fw_vendor_keys;
Features_fw_vendor_keys_t fw_vendor_keys;
/* @@protoc_insertion_point(struct:Features) */ /* @@protoc_insertion_point(struct:Features) */
} Features; } Features;
@ -153,7 +151,7 @@ typedef struct _Success {
/* Initializer values for message structs */ /* Initializer values for message structs */
#define Initialize_init_default {0} #define Initialize_init_default {0}
#define GetFeatures_init_default {0} #define GetFeatures_init_default {0}
#define Features_init_default {false, "", false, 0, false, 0, false, 0, false, 0, false, "", false, 0, false, 0, false, "", false, "", false, 0, false, {0, {0}}, false, {0, {0}}, false, 0, false, 0, false, 0, false, 0, false, ""} #define Features_init_default {false, "", false, 0, false, 0, false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}, false, 0, false, "", false, 0, false, 0, false, 0, false, "", false, {0, {0}}}
#define Ping_init_default {false, "", false, 0, false, 0, false, 0} #define Ping_init_default {false, "", false, 0, false, 0, false, 0}
#define Success_init_default {false, ""} #define Success_init_default {false, ""}
#define Failure_init_default {false, (FailureType)0, false, ""} #define Failure_init_default {false, (FailureType)0, false, ""}
@ -164,7 +162,7 @@ typedef struct _Success {
#define FirmwareUpload_init_default {{{NULL}, NULL}, false, {0, {0}}} #define FirmwareUpload_init_default {{{NULL}, NULL}, false, {0, {0}}}
#define Initialize_init_zero {0} #define Initialize_init_zero {0}
#define GetFeatures_init_zero {0} #define GetFeatures_init_zero {0}
#define Features_init_zero {false, "", false, 0, false, 0, false, 0, false, 0, false, "", false, 0, false, 0, false, "", false, "", false, 0, false, {0, {0}}, false, {0, {0}}, false, 0, false, 0, false, 0, false, 0, false, ""} #define Features_init_zero {false, "", false, 0, false, 0, false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}, false, 0, false, "", false, 0, false, 0, false, 0, false, "", false, {0, {0}}}
#define Ping_init_zero {false, "", false, 0, false, 0, false, 0} #define Ping_init_zero {false, "", false, 0, false, 0, false, 0}
#define Success_init_zero {false, ""} #define Success_init_zero {false, ""}
#define Failure_init_zero {false, (FailureType)0, false, ""} #define Failure_init_zero {false, (FailureType)0, false, ""}
@ -185,18 +183,17 @@ typedef struct _Success {
#define Features_patch_version_tag 4 #define Features_patch_version_tag 4
#define Features_bootloader_mode_tag 5 #define Features_bootloader_mode_tag 5
#define Features_device_id_tag 6 #define Features_device_id_tag 6
#define Features_pin_protection_tag 7
#define Features_passphrase_protection_tag 8
#define Features_language_tag 9 #define Features_language_tag 9
#define Features_label_tag 10 #define Features_label_tag 10
#define Features_initialized_tag 12 #define Features_initialized_tag 12
#define Features_revision_tag 13 #define Features_revision_tag 13
#define Features_bootloader_hash_tag 14
#define Features_imported_tag 15
#define Features_pin_cached_tag 16
#define Features_passphrase_cached_tag 17
#define Features_firmware_present_tag 18 #define Features_firmware_present_tag 18
#define Features_model_tag 21 #define Features_model_tag 21
#define Features_fw_major_tag 22
#define Features_fw_minor_tag 23
#define Features_fw_patch_tag 24
#define Features_fw_vendor_tag 25
#define Features_fw_vendor_keys_tag 26
#define FirmwareErase_length_tag 1 #define FirmwareErase_length_tag 1
#define FirmwareRequest_offset_tag 1 #define FirmwareRequest_offset_tag 1
#define FirmwareRequest_length_tag 2 #define FirmwareRequest_length_tag 2
@ -211,7 +208,7 @@ typedef struct _Success {
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
extern const pb_field_t Initialize_fields[1]; extern const pb_field_t Initialize_fields[1];
extern const pb_field_t GetFeatures_fields[1]; extern const pb_field_t GetFeatures_fields[1];
extern const pb_field_t Features_fields[19]; extern const pb_field_t Features_fields[18];
extern const pb_field_t Ping_fields[5]; extern const pb_field_t Ping_fields[5];
extern const pb_field_t Success_fields[2]; extern const pb_field_t Success_fields[2];
extern const pb_field_t Failure_fields[3]; extern const pb_field_t Failure_fields[3];
@ -224,7 +221,7 @@ extern const pb_field_t FirmwareUpload_fields[3];
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define Initialize_size 0 #define Initialize_size 0
#define GetFeatures_size 0 #define GetFeatures_size 0
#define Features_size 229 #define Features_size 499
#define Ping_size 265 #define Ping_size 265
#define Success_size 259 #define Success_size 259
#define Failure_size 270 #define Failure_size 270

View File

@ -37,27 +37,32 @@ message GetFeatures {
* @prev GetFeatures * @prev GetFeatures
*/ */
message Features { message Features {
optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com" optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io"
optional uint32 major_version = 2; // major version of the device, e.g. 1 optional uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1
optional uint32 minor_version = 3; // minor version of the device, e.g. 0 optional uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0
optional uint32 patch_version = 4; // patch version of the device, e.g. 0 optional uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0
optional bool bootloader_mode = 5; // is device in bootloader mode? optional bool bootloader_mode = 5; // is device in bootloader mode?
optional string device_id = 6; // device's unique identifier optional string device_id = 6; // device's unique identifier
optional bool pin_protection = 7; // is device protected by PIN? // optional bool pin_protection = 7; // is device protected by PIN?
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase? // optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
optional string language = 9; // device language optional string language = 9; // device language
optional string label = 10; // device description label optional string label = 10; // device description label
// repeated CoinType coins = 11; // supported coins // repeated CoinType coins = 11; // supported coins
optional bool initialized = 12; // does device contain seed? optional bool initialized = 12; // does device contain seed?
optional bytes revision = 13; // SCM revision of firmware optional bytes revision = 13; // SCM revision of firmware
optional bytes bootloader_hash = 14; // hash of the bootloader // optional bytes bootloader_hash = 14; // hash of the bootloader
optional bool imported = 15; // was storage imported from an external source? // optional bool imported = 15; // was storage imported from an external source?
optional bool pin_cached = 16; // is PIN already cached in session? // optional bool pin_cached = 16; // is PIN already cached in session?
optional bool passphrase_cached = 17; // is passphrase already cached in session? // optional bool passphrase_cached = 17; // is passphrase already cached in session?
optional bool firmware_present = 18; // is valid firmware loaded? optional bool firmware_present = 18; // is valid firmware loaded?
// optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup) // optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
// optional uint32 flags = 20; // device flags (equals to Storage.flags) // optional uint32 flags = 20; // device flags (equals to Storage.flags)
optional string model = 21; // device hardware model optional string model = 21; // device hardware model
optional uint32 fw_major = 22; // reported firmware version if in bootloader mode
optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode
optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode
optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode
optional bytes fw_vendor_keys = 26; // reported firmware vendor keys (their hash)
} }
/** /**

View File

@ -219,6 +219,7 @@ class VendorHeader(object):
for i in range(self.vsig_n): for i in range(self.vsig_n):
print(' * vpub #%d :' % (i + 1), binascii.hexlify(self.vpub[i]).decode()) print(' * vpub #%d :' % (i + 1), binascii.hexlify(self.vpub[i]).decode())
print(' * vstr :', self.vstr.decode()) print(' * vstr :', self.vstr.decode())
print(' * vhash :', binascii.hexlify(self.vhash()).decode())
print(' * vimg : (%d bytes)' % len(self.vimg)) print(' * vimg : (%d bytes)' % len(self.vimg))
print(' * sigmask :', format_sigmask(self.sigmask)) print(' * sigmask :', format_sigmask(self.sigmask))
print(' * sig :', binascii.hexlify(self.sig).decode()) print(' * sig :', binascii.hexlify(self.sig).decode())
@ -246,6 +247,16 @@ class VendorHeader(object):
def fingerprint(self): def fingerprint(self):
return pyblake2.blake2s(self.serialize_header(sig=False)).hexdigest() return pyblake2.blake2s(self.serialize_header(sig=False)).hexdigest()
def vhash(self):
h = pyblake2.blake2s()
h.update(struct.pack('<BB', self.vsig_m, self.vsig_n))
for i in range(8):
if i < self.vsig_n:
h.update(self.vpub[i])
else:
h.update(b'\x00' * 32)
return h.digest()
def sign(self, sigmask, signature): def sign(self, sigmask, signature):
header = self.serialize_header(sig=False) header = self.serialize_header(sig=False)
assert len(header) == self.hdrlen assert len(header) == self.hdrlen

@ -1 +1 @@
Subproject commit 13499e256acfdde662b98ea402146ca105fb03b3 Subproject commit 8652df0c692ea4f13042539dcb6bde57b79137bc