Test / test (push) Failing after 25m47s
Go port of the read path of pybyd 0.0.75 (the client behind jkaberg/hass-byd-vehicle): the bangcle white-box AES envelope, the signing/checkcode layer, login, vehicle list, and realtime readings. Scraping runs on a cron schedule rather than per Prometheus request because each reading wakes the car's T-Box and drains the traction battery; /metrics serves the last result. Every numeric field of the realtime payload becomes a gauge, so the exporter does not need a hand-maintained field list that goes stale when BYD changes the payload. Tests pin the wire format against pybyd byte-for-byte and drive the client end to end against a fake cloud that speaks the real protocol. Co-Authored-By: Claude Opus 5 <[email protected]>
166 lines
4.0 KiB
Go
166 lines
4.0 KiB
Go
package bangcle
|
|
|
|
// White-box AES block functions.
|
|
//
|
|
// The index arithmetic below is a one-for-one transcription of decompiled code
|
|
// (via pybyd). It is deliberately not tidied or renamed: keeping it diffable
|
|
// against the reference is worth more than readable variable names, because the
|
|
// only way to debug a mistake here is to compare intermediate state with pybyd.
|
|
|
|
import "encoding/binary"
|
|
|
|
// prepareMatrix transposes a 4x4 block into the working state layout (col*8+row).
|
|
func prepareMatrix(in, state []byte) {
|
|
for col := 0; col < 4; col++ {
|
|
for row := 0; row < 4; row++ {
|
|
state[col*8+row] = in[col+row*4]
|
|
}
|
|
}
|
|
}
|
|
|
|
func decryptBlock(t *lookupTables, block []byte) []byte {
|
|
state := make([]byte, 32)
|
|
temp64 := make([]byte, 64)
|
|
tmp32 := make([]byte, 32)
|
|
out := make([]byte, 16)
|
|
|
|
prepareMatrix(block, state)
|
|
|
|
for rnd := 9; rnd >= 1; rnd-- {
|
|
l21 := rnd * 4
|
|
permPtr := 0
|
|
for i := 0; i < 4; i++ {
|
|
b := int(t.permDecrypt[permPtr])
|
|
l16 := i * 8
|
|
base := i * 16
|
|
for j := 0; j < 4; j++ {
|
|
u7 := (b + j) & 3
|
|
idx := int(state[l16+u7]) + (i+(l21+u7)*4)*256
|
|
binary.LittleEndian.PutUint32(temp64[base+j*4:], binary.LittleEndian.Uint32(t.invRound[idx*4:]))
|
|
}
|
|
permPtr += 2
|
|
}
|
|
|
|
i15 := 1
|
|
for l21x := 0; l21x < 4; l21x++ {
|
|
pb := l21x
|
|
for l9 := 0; l9 < 4; l9++ {
|
|
u6 := int(temp64[pb]) & 0xF
|
|
u26 := int(temp64[pb]) & 0xF0
|
|
f := [3]int{int(temp64[pb+0x10]), int(temp64[pb+0x20]), int(temp64[pb+0x30])}
|
|
|
|
l2 := l9*0x18 + rnd*0x60
|
|
i25 := i15
|
|
for k := 0; k < 3; k++ {
|
|
b := f[k]
|
|
u27 := u6 | ((b << 4) & 0xFF)
|
|
u26 = ((u26 >> 4) | ((b >> 4) << 4)) & 0xFF
|
|
u6 = int(t.invXor[(l2+i25-1)*0x100+u27]) & 0xF
|
|
u26 = (int(t.invXor[(l2+i25)*0x100+u26]) & 0xF) << 4
|
|
i25 += 2
|
|
}
|
|
state[l9+l21x*8] = byte((u26 | u6) & 0xFF)
|
|
pb += 4
|
|
}
|
|
i15 += 6
|
|
}
|
|
}
|
|
|
|
copy(tmp32, state)
|
|
u8, u10, u12 := 1, 3, 2
|
|
for row := 0; row < 4; row++ {
|
|
state[row] = t.invFirst[int(tmp32[row])+row*0x400]
|
|
|
|
r1 := u10 & 3
|
|
state[8+row] = t.invFirst[int(tmp32[8+r1])+r1*0x400+0x100]
|
|
|
|
r2 := u12 & 3
|
|
state[0x10+row] = t.invFirst[int(tmp32[0x10+r2])+r2*0x400+0x200]
|
|
|
|
r3 := u8 & 3
|
|
state[0x18+row] = t.invFirst[int(tmp32[0x18+r3])+r3*0x400+0x300]
|
|
|
|
u8++
|
|
u10++
|
|
u12++
|
|
}
|
|
|
|
for col := 0; col < 4; col++ {
|
|
for row := 0; row < 4; row++ {
|
|
out[col+row*4] = state[col*8+row]
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func encryptBlock(t *lookupTables, block []byte) []byte {
|
|
state := make([]byte, 32)
|
|
temp64 := make([]byte, 64)
|
|
tmp32 := make([]byte, 32)
|
|
out := make([]byte, 16)
|
|
|
|
prepareMatrix(block, state)
|
|
|
|
for rnd := 0; rnd < 9; rnd++ {
|
|
l21 := rnd * 4
|
|
permPtr := 0
|
|
for i := 0; i < 4; i++ {
|
|
b := int(t.permEncrypt[permPtr])
|
|
l16 := i * 8
|
|
base := i * 16
|
|
for j := 0; j < 4; j++ {
|
|
u8 := (b + j) & 3
|
|
idx := int(state[l16+u8]) + (i+(l21+u8)*4)*256
|
|
binary.LittleEndian.PutUint32(temp64[base+j*4:], binary.LittleEndian.Uint32(t.round[idx*4:]))
|
|
}
|
|
permPtr += 2
|
|
}
|
|
|
|
i16 := 1
|
|
for l22 := 0; l22 < 4; l22++ {
|
|
pb := l22
|
|
for l10 := 0; l10 < 4; l10++ {
|
|
u7 := int(temp64[pb]) & 0xF
|
|
u26 := int(temp64[pb]) & 0xF0
|
|
f := [3]int{int(temp64[pb+0x10]), int(temp64[pb+0x20]), int(temp64[pb+0x30])}
|
|
|
|
l2 := l10*0x18 + rnd*0x60
|
|
i25 := i16
|
|
for k := 0; k < 3; k++ {
|
|
b := f[k]
|
|
u27 := u7 | ((b << 4) & 0xFF)
|
|
u26 = ((u26 >> 4) | ((b >> 4) << 4)) & 0xFF
|
|
u7 = int(t.xor[(l2+i25-1)*0x100+u27]) & 0xF
|
|
u26 = (int(t.xor[(l2+i25)*0x100+u26]) & 0xF) << 4
|
|
i25 += 2
|
|
}
|
|
state[l10+l22*8] = byte((u26 | u7) & 0xFF)
|
|
pb += 4
|
|
}
|
|
i16 += 6
|
|
}
|
|
}
|
|
|
|
copy(tmp32, state)
|
|
for row := 0; row < 4; row++ {
|
|
r0 := row & 3
|
|
state[row] = t.final[int(tmp32[r0])+r0*0x400]
|
|
|
|
r1 := (1 + row) & 3
|
|
state[8+row] = t.final[int(tmp32[8+r1])+r1*0x400+0x100]
|
|
|
|
r2 := (2 + row) & 3
|
|
state[0x10+row] = t.final[int(tmp32[0x10+r2])+r2*0x400+0x200]
|
|
|
|
r3 := (3 + row) & 3
|
|
state[0x18+row] = t.final[int(tmp32[0x18+r3])+r3*0x400+0x300]
|
|
}
|
|
|
|
for col := 0; col < 4; col++ {
|
|
for row := 0; row < 4; row++ {
|
|
out[col+row*4] = state[col*8+row]
|
|
}
|
|
}
|
|
return out
|
|
}
|