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 |