Files
cam-reverse/handlers.ts
T

167 lines
6.8 KiB
TypeScript

import { Commands, CommandsByValue, ControlCommands } from "./datatypes.js";
import { create_P2pRdy, SendStartVideo, SendDevStatus, SendUsrChk } from "./impl.js";
import { Session } from "./server.js";
import { u16_swap, u32_swap } from "./utils.js";
import { hexdump } from "./hexdump.js";
import { XqBytesDec } from "./func_replacements.js";
let curImage = null;
export const notImpl = (_: Session, dv: DataView) => {
const raw = dv.readU16();
const cmd = CommandsByValue[raw];
console.log(`^^ ${cmd} (${raw.toString(16)}) and it's not implemented yet`);
};
export const noop = (_: Session, __: DataView) => {};
const create_P2pAliveAck = (): DataView => {
const outbuf = new DataView(new Uint8Array(4).buffer);
outbuf.writeU16(Commands.P2PAliveAck);
outbuf.add(2).writeU16(0);
return outbuf;
};
export const handle_P2PAlive = (session: Session, _: DataView) => {
const b = create_P2pAliveAck();
session.send(b);
};
export const handle_PunchPkt = (session: Session, dv: DataView) => {
const punchCmd = dv.readU16();
const len = dv.add(2).readU16();
const prefix = dv.add(4).readString(4);
const serial = dv.add(8).readU64().toString();
const suffix = dv.add(16).readString(len - 16 + 4); // 16 = offset, +4 header
// f141 20 BATC 609531 EXLVS
session.eventEmitter.emit("connect", prefix.toString() + serial + suffix.toString());
session.send(create_P2pRdy(dv.add(4).readByteArray(len)));
};
export const createResponseForControlCommand = (session: Session, dv: DataView): DataView | null => {
const start_type = dv.add(8).readU16(); // 0xa11 on control; data starts here on DATA pkt
const cmd_id = dv.add(10).readU16(); // 0x1120
const payload_len = u16_swap(dv.add(0xc).readU16());
if (start_type != 0x110a) {
console.error(`Expected start_type to be 0xa11, got 0x${start_type.toString(16)}`);
return;
}
const rotate_chr = 4;
if (payload_len > rotate_chr) {
// 20 = 16 (header) + 4 (??)
XqBytesDec(dv.add(20), payload_len - 4, rotate_chr);
// console.log(hexdump(dv));
}
if (cmd_id == ControlCommands.ConnectUserAck) {
let c = new Uint8Array(dv.add(0x18).readByteArray(4).buffer);
session.ticket = [...c];
const buf = SendStartVideo(session);
return buf;
}
if (cmd_id == ControlCommands.DevStatusAck) {
let charging = u32_swap(dv.add(0x28).readU32()) & 1; // 0x14000101 v 0x14000100
let power = u16_swap(dv.add(0x18).readU16()); // '3730' or '3765', milliVolts?
let dbm = dv.add(0x24).readU8() - 0x100; // 0xbf - 0x100 = -65dbm .. constant??
// > -50 = excellent, -50 to -60 good, -60 to -70 fair, <-70 weak
console.log(`charging? ${charging}, batlevel? ${power}, wifi dbm: ${dbm}`);
}
// 0x6102 = parseWifisetting
/*
* 01-28 18:19:55.558 12566 3872 F LogUtils: cmdParser,setting:WifiSettingBean{ssid='FTYC477259EDEDE', psk='12345678', ip='0.255.255.255', mask='0.0.0.0', gw='0.0.0.0', dns1='0.0.0.0', dns2='0.0.0.0', enable=0, wifiStatus=0 , mode=2, channel=0, authtype=0, dhcp=0} [ file:IpcByte2ObjectParser.java, line:1361, method:ParseWifiSetting, class:com.ilnk.callback.IpcByte2ObjectParser ]
decrypted data
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 f1 d0 01 18 d1 00 00 c9 11 0a 02 61 0c 01 00 00 ...........a....
00000010 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 46 54 59 43 ............FTYC
00000030 34 37 37 32 35 39 45 44 45 44 45 00 00 00 00 00 477259EDEDE.....
00000040 00 00 00 00 00 00 00 00 00 00 00 00 31 32 33 34 ............1234
00000050 35 36 37 38 00 00 00 00 00 00 00 00 00 00 00 00 5678............
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 30 2e 32 35 ............0.25
000000d0 35 2e 32 35 35 2e 32 35 35 00 00 00 30 2e 30 2e 5.255.255...0.0.
000000e0 30 2e 30 00 00 00 00 00 00 00 00 00 30 2e 30 2e 0.0.........0.0.
000000f0 30 2e 30 00 00 00 00 00 00 00 00 00 30 2e 30 2e 0.0.........0.0.
00000100 30 2e 30 00 00 00 00 00 00 00 00 00 30 2e 30 2e 0.0.........0.0.
00000110 30 2e 30 00 00 00 00 00 01 01 01 01 0.0.........
*/
};
const deal_with_data = (session: Session, dv: DataView) => {
const pkt_len = dv.add(2).readU16();
// data
const JPEG_HEADER = [0xff, 0xd8, 0xff, 0xdb];
const AUDIO_HEADER = [0x55, 0xaa, 0x15, 0xa8];
const m_hdr = dv.add(8).readByteArray(4);
let is_new_image = true;
let audio = true;
for (let i = 0; i < 4; i++) {
is_new_image = is_new_image && m_hdr.add(i).readU8() == JPEG_HEADER[i];
audio = audio && m_hdr.add(i).readU8() == AUDIO_HEADER[i];
}
if (audio) {
// "stream_head_t->type == 0x06" per pdf
if (dv.add(12).readU8() == 0x06) {
const audio_len = u16_swap(dv.add(8 + 16).readU16());
const audio_buf = dv.add(32 + 8).readByteArray(audio_len).buffer; // 8 for pkt header, 32 for `stream_head_t`
session.eventEmitter.emit("audio", Buffer.from(audio_buf));
} else {
// not sure what these are for, there's one per frame. maybe alignment?
}
} else {
const data = dv.add(8).readByteArray(pkt_len - 4);
if (is_new_image) {
if (curImage != null) {
session.eventEmitter.emit("frame", curImage);
}
curImage = Buffer.from(data.buffer);
} else {
curImage = Buffer.concat([curImage, Buffer.from(data.buffer)]);
}
}
};
const makeDrwAck = (dv: DataView): DataView => {
const pkt_id = dv.add(6).readU16();
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
const item_count = 1; // TODO coalesce acks
const reply_len = item_count * 2 + 4; // 4 hdr, 2b per item
const outbuf = new DataView(new Uint8Array(32).buffer);
outbuf.writeU16(Commands.DrwAck);
outbuf.add(2).writeU16(reply_len);
outbuf.add(4).writeU8(0xd2);
outbuf.add(5).writeU8(m_stream);
outbuf.add(6).writeU16(item_count);
for (let i = 0; i < item_count; i++) {
outbuf.add(8 + i * 2).writeU16(pkt_id);
}
return outbuf;
};
export const handle_Drw = (session: Session, dv: DataView) => {
const ack = makeDrwAck(dv);
session.send(ack);
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
if (m_stream == 1) {
deal_with_data(session, dv);
} else {
const b = createResponseForControlCommand(session, dv);
if (b != null) {
session.send(b);
}
}
};
export const handle_P2PRdy = (session: Session, _: DataView) => {
const b = SendUsrChk("admin", "admin", session.outgoingCommandId);
session.send(b);
};