https://fccid.io/PIY-HID00-19A5R
af0a6ec70001000a84a091559fc6f0de
af0a6ec70002000a84a091559fc6f0de: write, indicate // data
af0a6ec70003000a84a091559fc6f0de: read // factory
af0a6ec70004000a84a091559fc6f0de: write, indicate // session
af0a6ec70001000b84a091559fc6f0de //nxp ota service
af0a6ec70002000b84a091559fc6f0de: write, indicate // NXPConfig::otaCmd / NXP_OTAP
af0a6ec70003000b84a091559fc6f0de: writeWithoutResponse // NXPConfig::otaData
02 00 00 01 00 00 41 11 11 11 01
Android logs:
NewImageInfoRequest 02 00 00 01 00 00 41 11 11 11 01
build version 01 00 00
app Version 41
hardware Id 11 11 11
manufacturer 01
- subscribe to 'data' and 'session' characteristics
- read from 'factory' characteristic
01303739394f5331313032303938363239303730303031383902febf6d48ebd6ee1328a875b64def8641554e5bbc99d73c26b2cfc84045528e96005c919764000198967fcd604f291b4aeab61e14449a0ddc4c381b63946b9825df2d86c0548afeef07fe7b6ce4706762b2e5ef4930edb8ea88b977566df13d8f6dd1c743b33a03edf880d5c23603
NB: for the public key above (0x02febf) the private key is 9f69f7257a3bf97dcd6dee36e740a4e32c353ecb886b8a5b30184380c056d0f8
index | length | name | description | example |
---|---|---|---|---|
0 | 1 | protocolVersion | 1 | |
1 | 24 | serial | ascii encoded data, see next table for decoding | 0799OS110209862907000189 |
25 | 33 | compressedPublicKey | secp256r1/prime256v1 ECDH public key | 02febf6d48ebd6ee1328a875b64def8641554e5bbc99d73c26b2cfc84045528e96 |
58 | 5 | birthdate | 5 byte unsigned int of unix timestamp | |
63 | 2 | machineNumber | signed 16bit big endian | 1 |
65 | 3 | keyID | unsigned 24bit I think used to index into hardcoded array of public keys in app | 9999999 |
68 | 64 | signature | sha256 based sig (I haven't validated) | |
132 | 4 | salt | salt used for encryption | d5 c2 36 03 |
- Serial
index | length | name | description | example |
---|---|---|---|---|
0 | 3 | day | 079 | |
3 | 1 | year | 9 | |
4 | 2 | location | OS | |
6 | 10 | APNumber | 1102098629 | |
16 | 2 | revision | 07 | |
18 | 6 | itemNumber | 000189 |
- Write 37 bytes to 'session'
- 33 byte secp256r1/prime256v1 ECDH public key (compressed)
- 4 byte salt of your choosing
- Starting receiving messages, see format and decryption below
7e00000001001555fc7918da6ab8101a1c0fa0d9b19c4c590501ef7b33
index | length | name | description | example |
---|---|---|---|---|
0 | 1 | Always starts with this byte | 7e | |
1 | 4 | packetNumber | 00 00 00 01 | |
5 | 2 | length | 0015 (21 bytes) | |
7 | 1 | header crc8 | crc8 (aka crc8atm) but with a 0xff initial value | reveng -m "CRC-8" -i ff -c 7E000000010011 |
8 | length | contents | the contents, encrypted | |
-1 | 1 | content checksum | same as header checksum, but checked after decrypt |
Reencrypt sharedsecret with itself 100x:
- ECDH to get a shared secret
- Create IV with 9 zeros, 'mattel', and a final 0 (16 bytes in total)
- encrypt shared secret using shared secret and IV
- IV[7]++
- repeat until IV[7] is 99
- IV is concat of:
- 4 byte packetNumber
- 4 byte salt from peer
- 4 byte salt you chose
- 4 bytes of 0
- key is first 16 bytes of secretKey
- algorithm is aes-128-ctr
To encrypt, reverse order of salts: put your salt before the peer salt
- Protobuf
syntax = "proto3";
package hwid;
message PortalToApp {
uint32 timestampMs = 1;
Event event = 2;
DeviceInfo deviceInfo = 3;
CommandResponse commandResponse = 4;
bytes accessoryMessage = 5;
}
enum EventType {
UNKNOWN = 0;
LOW_BATTERY = 1;
CAR_ON = 2;
CAR_OFF = 3;
CAR_DRIVE_BY = 4;
CAR_HIST = 5;
ACC_ATTACH = 6;
ACC_DETATCH = 7;
}
message Event {
uint32 type = 1;
CarInfo carInfo = 2;
SpeedMeasurement speedMeasurement = 3;
repeated SpeedMeasurement measurementHistory = 4;
repeated OfflineRaceSession offlineRaceSessions = 5;
}
message OfflineRaceSession {
uint32 timePlayed = 1;
float topSpeed = 2;
uint32 scanCount = 3;
}
message CarInfo {
bytes tagUid = 1;
bool signatureStatus = 2;
bytes carNdef = 3;
bytes signature = 4;
bytes publicKey = 5;
}
message SpeedMeasurement {
uint32 timestampMs_ = 1;
float speed = 2;
int32 tIr1In = 3;
int32 tIr1Out = 4;
int32 tIr2In = 5;
int32 tIr2Out = 6;
}
message DeviceInfo {
uint32 firmwareVersion = 1;
uint32 hardwareVersion = 2;
float batteryLevel = 3;
uint32 deviceMode = 4;
uint32 bootTimestamp = 5;
string serialNumber = 6;
uint32 batteryState = 7; // 2: charging, 1: not charging, 3: full, 4: problem
uint32 qValue = 8;
uint32 iValue = 9;
string semanticFirmwareVersion = 10;
bool accessoryAttached = 11;
}
message CommandResponse {
int32 failed = 1;
string failMessage = 2;
}
message AppToPortal {
uint32 timestampSec = 1;
Command command = 2;
bytes accessoryMessage = 3;
}
message Command {
/*
UnknownCommandType = 0;
PortalMode = 1;
RaceMode = 2;
RequestDeviceInfo = 3;
TestMode = 4;
Reset = 5;
StartOta = 6;
SetLedcolor = 7;
ResetLedcontrol = 8;
*/
uint32 type = 1;
bytes otaSignature = 2;
bytes otaPublicKey = 3;
bytes rgbColor = 4;
}
message AccessoryToPortal {
uint32 timestampMs = 1;
DeviceInfo info = 2;
bytes accessoryMessage = 3;
}
message PortalToAccessory {
uint32 timestampMs = 1;
Event event = 2;
bytes accessoryMessage = 3;
}
index | length | name | description | example |
---|---|---|---|---|
0 | 1 | start-of-message | 0x8d | |
1 | 2 | ? | 0xffff | |
3 | 2 | ? | 0xfffe | |
5 | 2 | Track.CommandType | 0xbbbc | |
7 | 2 | length | 0x0002 | |
-2 | 2 | checksum | CRC-16/CCITT-FALSE |
Track.CommandType | value |
---|---|
TableRequest | 5 |
TableResponse | 6 |
SetUpstreamBase | 0x10 |
RequestBoosterBirthCert | 0x20 |
ResponseBoosterBirthCert | 0x21 |
RequestTrackBirthCert | 0x22 |
ResponseTrackBirthCert | 0x23 |
TestLED | 0x30 |
FirmwareOtaStart | 0xAAAA |
FirmwareOtaManifest | 0xAAAB |
FirmwareOtaPackage | 0xAAAC |
FirmwareOtaDone | 0xAAAD |
Acknowledge | 0xACAC |
FinishLineEvent | 0xBBBB |
BoosterRpmEvent | 0xBBBC |
8dfffffffe0006001b020041b0b0c60041b0b0c80101ffff000201ffff020200010015116013
Didn't figure out completely, but the first byte is a count of pieces, including the booster, and there are a list of 4 byte ids for the pieces
physical name | id | TrackType | Track.Type | SmartTrackID |
---|---|---|---|---|
booster | 41b0b0c6 | 0 | SmartBooster | |
long | 41b0b0c7 | 1 | Track12Inch | |
short | 41b0b0c8 | 3 | Track6Inch | |
41b0b0c9 | 6 | BankedCurve | ||
gate | 41b0b0ca | 2 | FinishLine | |
41b0b0cb | 7 | Loop | ||
jump | 41b0b0cc | 4 | JumpLaunch | |
41b0b0cd | 5 | JumpLanding | ||
8 | Unknown |
fffffffe00230058020041b0b0c8005c9099283037383973373032313998967f17b8e7d4d614172abb271d246041d83a3995fe87e7a53f3d7521efaab307461bd290988b2a7a592404c071b5ee99a69e26150004c56a430eb178a6ca5e70bb84
index | length | name | description | example |
---|---|---|---|---|
0 | 2 | ? | 0xffff | |
2 | 2 | ? | 0xfffe | |
4 | 2 | Track.CommandType | 0x0023 | |
6 | 2 | length | 0x0058 | |
8 | 1 | ? | 0x02 | |
9 | 5 | ? | track part id? (seen for track connected messages) | 0041b0b0c8 |
14 | 5 | timestamp | 0x005c909928 | |
19 | 10 | serialnumber | ascii | 0789s70219 |
29 | 3 | keyID | 9999999 | |
32 | 64 | signature? | I assume a signature like the first message |
fffffffe00210058020041b0b0c6005c8f4ad63037373973373038393998967f564a16af5f6f4fa95de9ca662f3591bde24602cdae71e95f6eb0d34b718bfd63419fe79b6fa7a052265df6f615a37baaa86465dcc69ff9a4159665ac2f281a7b
index | length | name | description | example |
---|---|---|---|---|
0 | 2 | ? | 0xffff | |
2 | 2 | ? | 0xfffe | |
4 | 2 | Track.CommandType | 0x0021 | |
6 | 2 | length | 0x0058 | |
8 | 1 | ? | 0x02 | |
9 | 5 | ? | booster part id? (seen for track connected messages) | 0041b0b0c6 |
14 | 5 | timestamp | 0x005c8f4ad6 | |
19 | 10 | serialnumber | ascii | 0779s70899 |
29 | 3 | keyID | 9999999 | |
32 | 64 | signature? | I assume a signature like the first message |