Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fe343b0bb | ||
|
|
84ca8ec083 | ||
|
|
fc06741192 | ||
|
|
1401af6e81 |
@@ -1,37 +0,0 @@
|
|||||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
||||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
||||||
|
|
||||||
name: Node.js CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node-version: [16.x, 18.x, 20.x]
|
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
cache: 'npm'
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run tsc
|
|
||||||
- run: npm run build
|
|
||||||
- run: npm test
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: bundle-${{ matrix.node-version }}
|
|
||||||
path: dist/bin.cjs
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
bundle.js
|
bundle.js
|
||||||
venv
|
venv
|
||||||
node_modules
|
node_modules
|
||||||
build
|
|
||||||
dist/
|
|
||||||
cameras.txt
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: run hook install-wireshark-dissector test build typecheck
|
.PHONY: run hook install-wireshark-dissector test
|
||||||
|
|
||||||
bundle.js: frida-hooks.js func_replacements.js
|
bundle.js: frida-hooks.js func_replacements.js
|
||||||
~/node_modules/.bin/frida-compile -o $@ frida-hooks.js
|
~/node_modules/.bin/frida-compile -o $@ frida-hooks.js
|
||||||
@@ -8,13 +8,8 @@ venv: requirements.txt
|
|||||||
./venv/bin/pip install -r requirements.txt
|
./venv/bin/pip install -r requirements.txt
|
||||||
touch venv
|
touch venv
|
||||||
|
|
||||||
typecheck: node_modules
|
run: node_modules
|
||||||
npm run tsc
|
./node_modules/.bin/ts-node --esm server.ts
|
||||||
build: node_modules
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
run: build
|
|
||||||
node dist/bin.cjs http_server --port=1234
|
|
||||||
|
|
||||||
hook: bundle.js venv
|
hook: bundle.js venv
|
||||||
./venv/bin/python3 -u loader3.py
|
./venv/bin/python3 -u loader3.py
|
||||||
@@ -23,7 +18,7 @@ node_modules:
|
|||||||
npm install
|
npm install
|
||||||
|
|
||||||
test: node_modules
|
test: node_modules
|
||||||
npm run test
|
./node_modules/.bin/mocha --require ts-node/register tests/fn.test.js
|
||||||
|
|
||||||
install-wireshark-dissector:
|
install-wireshark-dissector:
|
||||||
mkdir -p ~/.local/lib/wireshark/plugins
|
mkdir -p ~/.local/lib/wireshark/plugins
|
||||||
|
|||||||
@@ -1,95 +1,26 @@
|
|||||||
Re-implementation of the "iLnk"/"iLnkP2P"/"PPPP" protocol used on some cheap (\<$5) IP cameras (sometimes branded as 'X5' or 'A9').
|
Re-implementation of the "ilnk" protocol used on some cheap (\<$5) IP cameras (sometimes branded as 'X5' or 'A9').
|
||||||
|
|
||||||
* Bought [this X5](https://www.aliexpress.com/item/1005006287788979.html) and [this A9](https://www.aliexpress.com/item/1005006117593880.html).
|
* Bought [here](https://www.aliexpress.com/item/1005006287788979.html).
|
||||||
|
* Waiting for [this A9 camera](https://www.aliexpress.com/item/1005006117593880.html) to validate support.
|
||||||
* App is [YsxLite](https://play.google.com/store/apps/details?id=com.ysxlite.cam&hl=en&gl=US)
|
* App is [YsxLite](https://play.google.com/store/apps/details?id=com.ysxlite.cam&hl=en&gl=US)
|
||||||
|
|
||||||
|
|
||||||
Per pictures of the [X5](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb.jpg?raw=true), [A9](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb_a9.jpg?raw=true) the main chip is TXW817 ([chinese](https://www.taixin-semi.com/Product/ProductDetail?productId=306), [eng, google translate](https://www-taixin--semi-com.translate.goog/Product/ProductDetail?productId=306&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp))
|
Per [pictures](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb.jpg?raw=true) the main chip is TXW817 ([chinese](https://www.taixin-semi.com/Product/ProductDetail?productId=306), [eng, google translate](https://www-taixin--semi-com.translate.goog/Product/ProductDetail?productId=306&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp))
|
||||||
|
|
||||||
## Features
|
The interesting implementation is in `libvdp.so`, part of the apk bundle.
|
||||||
|
|
||||||
- Multi camera support
|
Protocol reversing was done with a combination of static analysis of the shared object with [Ghidra](https://ghidra-sre.org/) and dynamic analysis with [Frida](https://frida.re/docs/javascript-api/).
|
||||||
- Audio & video streaming
|
|
||||||
- Rotation / mirroring of video streams
|
|
||||||
- Friendly names for cameras
|
|
||||||
- Ability to configure "blank" cameras with Wifi settings
|
|
||||||
|
|
||||||
## Building
|
The headers reversed with Ghidra are at `types/all.h`. They are almost not used by this minimal implementation though.
|
||||||
|
|
||||||
Run `make build` or `npm run build` to build the server artifact. You can also find some pre-built files [in the CI results](https://github.com/DavidVentura/cam-reverse/actions) or [in the releases](https://github.com/DavidVentura/cam-reverse/releases/)
|
The hooks used with frida are at `frida-hooks.js`, but it's mostly a playground - some useful functions got deleted once I understood the protocol.
|
||||||
|
|
||||||
## Pairing a new camera
|
|
||||||
|
|
||||||
Ensure your device in access point mode (the blue LED blinks slowly to indicate that); optionally, press the MODE button for 5s to switch to access point mode.
|
|
||||||
|
|
||||||
Connect to the device's access point (e.g., FTYC811847AGFDZ) and run `node dist/bin.cjs pair --ssid <SSID> --password <PASSWORD>`.
|
|
||||||
|
|
||||||
|
There's also a pretty crappy Wireshark dissector at `dissector.lua`. You can install it with `make install-wireshark-dissector`.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
To execute the server, run `make run`; JPEG files and `audio.pcm` will be created in a folder named `captures`.
|
||||||
|
|
||||||
### HTTP Server
|
There's no live-stream server built into this project yet.
|
||||||
To execute the HTTP server, run `node dist/bin.cjs http_server`; you can access the JPEG stream at http://localhost:5000/.
|
|
||||||
|
|
||||||
The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg?raw=true).
|
|
||||||
|
|
||||||
There's a basic UI which can display multiple cameras:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Clicking on the image will take you to a page that has audio streaming. Click the button below the image to mute/unmute the audio.
|
|
||||||
|
|
||||||
|
|
||||||
#### Settings
|
|
||||||
|
|
||||||
You can provide a config file in `yml` format, then pass it as an argument: `node bin.cjs http_server --config_file <your_config.yml>`
|
|
||||||
|
|
||||||
```yml
|
|
||||||
http_server:
|
|
||||||
port: 5000
|
|
||||||
|
|
||||||
logging:
|
|
||||||
level: debug
|
|
||||||
use_color: true
|
|
||||||
|
|
||||||
cameras:
|
|
||||||
FTYC477360FAWUK:
|
|
||||||
alias: "A9"
|
|
||||||
rotate: 1
|
|
||||||
mirror: false
|
|
||||||
fix_packet_loss: yes
|
|
||||||
audio: true
|
|
||||||
BATC609531EXLVS:
|
|
||||||
alias: "X5"
|
|
||||||
|
|
||||||
# If you are crossing broadcast domains (VLANs) then
|
|
||||||
# you need to specify all IPs as unicast targets
|
|
||||||
discovery_ips:
|
|
||||||
- 192.168.40.101
|
|
||||||
- 192.168.40.102
|
|
||||||
- 192.168.40.103
|
|
||||||
- 192.168.40.104
|
|
||||||
- 192.168.40.105
|
|
||||||
|
|
||||||
# If you are in the same broadcast domain, then
|
|
||||||
# it's easier to just use the broadcast address of your network
|
|
||||||
# discovery_ips:
|
|
||||||
# - 192.168.1.255
|
|
||||||
|
|
||||||
blacklisted_ips:
|
|
||||||
- 192.168.40.102
|
|
||||||
```
|
|
||||||
|
|
||||||
All keys are optional
|
|
||||||
|
|
||||||
You must restart the HTTP server for changes to the settings file to take effect.
|
|
||||||
|
|
||||||
### Single capture mode
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node bin.cjs frame --discovery_ip 192.168.40.104 --out out.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
## Protocol
|
## Protocol
|
||||||
|
|
||||||
@@ -158,95 +89,6 @@ sequenceDiagram
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Serial
|
|
||||||
|
|
||||||
The A9 cameras have a TX/RX test points - connecting with UART at 921600 8N1 gives _read only_ access to some debug logs.
|
|
||||||
|
|
||||||
### Discrepancies between cameras
|
|
||||||
|
|
||||||
1. Wifi Strength
|
|
||||||
- A9 reports '100%' strength
|
|
||||||
- X5 reports different strength values
|
|
||||||
|
|
||||||
I bricked two cameras by patching out part of the WiFi setup - unclear yet which commands.
|
|
||||||
|
|
||||||
After bricking itself, it reports very broken configuration via serial:
|
|
||||||
|
|
||||||
```
|
|
||||||
network interface: ƀ (Default)
|
|
||||||
MTU: 51050
|
|
||||||
MAC: 06 18 40 06 3e 51 b4 e2 c6 80 06 3f 77 30 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 74 00 00 00 01 00 00 00 9c ea 01 20 00 00 00 00 00 00 00 00 00 28 60 00 00 00 00 00 00 00 00 00 06 4e 00 20 2a 00 2a 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 00 01 08 00 06 04 00 01 3e 51 b4 e2 c6 80 01 01 01 01 00 00 00 00 00 00 01 01 01 01 00 28 74 00 00 00 00 00 00 00 00 00 58 4e 00 20 48 00 48 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 45 00 00 48 00 51 00 00 ff 11 c8 be 01 01 01 01 23 9c cc f7 7d 6c
|
|
||||||
FLAGS: DOWN LINK_DOWN IGMP
|
|
||||||
ip address: 1.1.1.1
|
|
||||||
gw address: 1.1.1.1
|
|
||||||
net mask : 1.1.1.1
|
|
||||||
|
|
||||||
network i
|
|
||||||
nterface: ^@^@
|
|
||||||
MTU: 0
|
|
||||||
MAC:
|
|
||||||
FLAGS: DOWN LINK_DOWN
|
|
||||||
ip address: 127.0.0.1
|
|
||||||
gw address: 127.0.0.1
|
|
||||||
net mask : 255.0.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Spyware
|
|
||||||
|
|
||||||
When connecting the camera to a network, it tries to send a HELLO (?) to 4 IP addresses:
|
|
||||||
```
|
|
||||||
139.155.68.77 - Shenzhen Tencent Computer Systems Company Limited
|
|
||||||
119.45.114.92 - Shenzhen Tencent Computer Systems Company Limited
|
|
||||||
162.62.63.154 - Tencent Building, Kejizhongyi Avenue
|
|
||||||
3.132.215.40 - ec2-3-132-215-40.us-east-2.compute.amazonaws.com
|
|
||||||
```
|
|
||||||
|
|
||||||
With the payload
|
|
||||||
```
|
|
||||||
0000 f1 10 00 28 42 41 54 43 00 00 00 00 00 09 4d 2c ...(BATC......M,
|
|
||||||
0010 48 56 44 43 53 00 00 00 08 00 02 01 00 00 6c 7d HVDCS.........l}
|
|
||||||
0020 65 28 a8 c0 00 00 00 00 00 00 00 00 e(..........
|
|
||||||
```
|
|
||||||
|
|
||||||
which is `DevLogin`
|
|
||||||
|
|
||||||
|
|
||||||
These addresses are decoded (script at `scripts/dec_svr.py`) from the string `SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG`.
|
|
||||||
|
|
||||||
Every 8-10s
|
|
||||||
|
|
||||||
There are some other strings in the APK ending in `-$$` which decode to other ips/hostnames.
|
|
||||||
|
|
||||||
Most of the IPs point to AWS compute instances, and this connection is probably used to see live streams over the Internet using the app. It's fine (and recommended!) to block outgoing traffic from the cameras, as it won't affect the HTTP server.
|
|
||||||
|
|
||||||
|
|
||||||
## Other stuff
|
|
||||||
|
|
||||||
These little cameras have quite some packet loss - I _tried_ to deal with it by splicing around it on the JPEG payloads, but it's probably wrong, I expected artifacts like this:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
but most of the time got:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
which _moves_ the rest of the image, causing more visual noise.
|
|
||||||
|
|
||||||
For now, images on which there was packet loss get skipped. The algorithm to "fix" packet loss can be enabled as an option.
|
|
||||||
|
|
||||||
|
|
||||||
## Reversing
|
|
||||||
|
|
||||||
The interesting implementation is in `libvdp.so`, part of the apk bundle.
|
|
||||||
|
|
||||||
Protocol reversing was done with a combination of static analysis of the shared object with [Ghidra](https://ghidra-sre.org/) and dynamic analysis with [Frida](https://frida.re/docs/javascript-api/).
|
|
||||||
|
|
||||||
The headers reversed with Ghidra are at `types/all.h`. They are almost not used by this minimal implementation though.
|
|
||||||
|
|
||||||
The hooks used with frida are at `frida-hooks.js`, but it's mostly a playground - some useful functions got deleted once I understood the protocol.
|
|
||||||
|
|
||||||
There's also a partial Wireshark dissector at `dissector.lua`. You can install it with `make install-wireshark-dissector`.
|
|
||||||
|
|
||||||
### Take APK from emulator/sacrificial device
|
### Take APK from emulator/sacrificial device
|
||||||
```
|
```
|
||||||
adb shell pm list packages | grep ysx
|
adb shell pm list packages | grep ysx
|
||||||
@@ -262,8 +104,3 @@ adb install-multiple *apk
|
|||||||
|
|
||||||
[docs](https://frida.re/docs/android/)
|
[docs](https://frida.re/docs/android/)
|
||||||
|
|
||||||
### Start frida server
|
|
||||||
|
|
||||||
```
|
|
||||||
adb shell 'su -c nohup /data/local/tmp/frida-server-16.1.11-android-arm64 &'
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import beamcoder from "beamcoder";
|
||||||
|
import EventEmitter from "node:events";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import Stream from "node:stream";
|
||||||
|
|
||||||
|
const sleep = (waitTimeInMs) => new Promise((resolve) => setTimeout(resolve, waitTimeInMs));
|
||||||
|
let c = 10;
|
||||||
|
const readableStream = new Stream.Readable({
|
||||||
|
async read() {
|
||||||
|
await sleep(10);
|
||||||
|
if (c < 810) {
|
||||||
|
const data = fs.readFileSync(
|
||||||
|
`captures/${parseInt(c / 10)
|
||||||
|
.toString()
|
||||||
|
.padStart(4, "0")}.jpg`,
|
||||||
|
);
|
||||||
|
this.push(data);
|
||||||
|
} else {
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ee = new EventEmitter();
|
||||||
|
|
||||||
|
let demuxers = beamcoder.demuxers();
|
||||||
|
// console.log(demuxers);
|
||||||
|
|
||||||
|
ee.on("inputFrame", (f) => {});
|
||||||
|
|
||||||
|
const makeEncoder = (frame, frameRate) => {
|
||||||
|
return beamcoder.encoder({
|
||||||
|
name: "libx264",
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
bit_rate: 400000,
|
||||||
|
// qmin: 22,
|
||||||
|
time_base: [1, frameRate],
|
||||||
|
framerate: [frameRate, 1],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
preset: "faster",
|
||||||
|
gop_size: 10,
|
||||||
|
max_b_frames: 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
async function imageToVideo(imagePath, duration, frameRate = 20) {
|
||||||
|
const muxTimeBase = 90000;
|
||||||
|
let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 });
|
||||||
|
|
||||||
|
console.log("piping");
|
||||||
|
readableStream.pipe(demuxerStream);
|
||||||
|
console.log("creating demuxer");
|
||||||
|
// Create a demuxer for the JPEG image
|
||||||
|
// let demuxer = await beamcoder.demuxer(imagePath);
|
||||||
|
let demuxer = await demuxerStream.demuxer({ name: "jpeg_pipe" });
|
||||||
|
// Create a decoder for the image
|
||||||
|
let decoder = beamcoder.decoder({ demuxer: demuxer, name: "mjpeg" });
|
||||||
|
|
||||||
|
// Read the image packet
|
||||||
|
console.log("wait demuxer");
|
||||||
|
// let packet = await demuxer.read();
|
||||||
|
let encoder = null;
|
||||||
|
let muxer = null;
|
||||||
|
let vstr = null;
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
let packet = await demuxer.read();
|
||||||
|
if (packet == null) break;
|
||||||
|
let frames = await decoder.decode(packet);
|
||||||
|
let frame = frames.frames[0];
|
||||||
|
if (encoder == null) {
|
||||||
|
encoder = makeEncoder(frame, frameRate);
|
||||||
|
// Create an H.264 encoder
|
||||||
|
// https://stackoverflow.com/a/13646293/3530257
|
||||||
|
// > the codec unit of measurement is commonly set to the interval between
|
||||||
|
// each frame and the next, > so that frame times are successive integers.
|
||||||
|
|
||||||
|
// TODO a muxer per client?
|
||||||
|
let stream = beamcoder.muxerStream({});
|
||||||
|
stream.pipe(fs.createWriteStream("test.mp4"));
|
||||||
|
// Create a muxer for the output video
|
||||||
|
muxer = stream.muxer({ format_name: "mp4" });
|
||||||
|
|
||||||
|
// console.log(demuxer.streams[0].codecpar.extradata); // null
|
||||||
|
vstr = muxer.newStream({
|
||||||
|
name: "h264",
|
||||||
|
time_base: [1, muxTimeBase], // frameRate],
|
||||||
|
interleaved: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// the Object.assign is structural (!!)
|
||||||
|
Object.assign(vstr.codecpar, {
|
||||||
|
width: encoder.width,
|
||||||
|
height: encoder.height,
|
||||||
|
format: encoder.pix_fmt,
|
||||||
|
});
|
||||||
|
|
||||||
|
await muxer.openIO();
|
||||||
|
// adding "empty_moov" crashes mpv/ffmpeg
|
||||||
|
// await muxer.initOutput({ movflags:
|
||||||
|
// "frag_keyframe+default_base_moof+faststart" });
|
||||||
|
await muxer.initOutput({ movflags: "frag_keyframe" });
|
||||||
|
console.log("inited");
|
||||||
|
// Add a video stream to the muxer
|
||||||
|
await muxer.writeHeader();
|
||||||
|
console.log("header written");
|
||||||
|
}
|
||||||
|
if (frame) {
|
||||||
|
frame.pts = i; // << the successive integers
|
||||||
|
frame.dts = i; // << the successive integers
|
||||||
|
let encodedPackets = await encoder.encode(frame);
|
||||||
|
|
||||||
|
// Write the encoded packets to the output file
|
||||||
|
for (let packet of encodedPackets.packets) {
|
||||||
|
packet.duration = 1;
|
||||||
|
packet.stream_index = vstr.index;
|
||||||
|
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
||||||
|
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
||||||
|
// packet.pts = i;
|
||||||
|
await muxer.writeFrame(packet);
|
||||||
|
// outFile.write(packet.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// Finalize the encoder and muxer
|
||||||
|
let encodedPackets = await encoder.flush();
|
||||||
|
// after flushing the encoder, we may hve some more packets
|
||||||
|
|
||||||
|
// Write the encoded packets to the output file
|
||||||
|
for (let packet of encodedPackets.packets) {
|
||||||
|
packet.duration = 1;
|
||||||
|
packet.stream_index = vstr.index;
|
||||||
|
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
||||||
|
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
||||||
|
await muxer.writeFrame(packet);
|
||||||
|
}
|
||||||
|
await muxer.writeTrailer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage example
|
||||||
|
imageToVideo("captures/0010.jpg", 20)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Video created successfully");
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="shortcut icon" href="/favicon.ico">
|
|
||||||
<title>${name}</title>
|
|
||||||
</head><body>
|
|
||||||
<h2>${name}</h2><a href="/camera/${id}"><img style="height: 640px" src="/camera/${id}"/></a><hr/>
|
|
||||||
<button onclick="toggle_audio()" id=audio disabled=true>Audio: disabled</button>
|
|
||||||
<button onclick="fetch('/rotate/${id}')">Rotate</button>
|
|
||||||
<button onclick="fetch('/mirror/${id}')">Mirror</button>
|
|
||||||
<script>
|
|
||||||
const alaw_to_s16_table = [
|
|
||||||
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752,
|
|
||||||
-2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016,
|
|
||||||
-20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160,
|
|
||||||
-27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544,
|
|
||||||
-14080, -13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88,
|
|
||||||
-72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120,
|
|
||||||
-1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624,
|
|
||||||
-592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296,
|
|
||||||
8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264,
|
|
||||||
3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088,
|
|
||||||
28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080,
|
|
||||||
13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56,
|
|
||||||
40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952,
|
|
||||||
1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848,
|
|
||||||
];
|
|
||||||
|
|
||||||
const alaw_to_s16 = (a_val) => {
|
|
||||||
return alaw_to_s16_table[a_val];
|
|
||||||
};
|
|
||||||
|
|
||||||
var audio_context;
|
|
||||||
const audio_button = document.getElementById('audio');
|
|
||||||
audio_button.disabled = !${audio};
|
|
||||||
update_audio_button();
|
|
||||||
|
|
||||||
function setup_audio() {
|
|
||||||
audio_context = new AudioContext();
|
|
||||||
const gain_node = audio_context.createGain(); // Declare gain node
|
|
||||||
const channels =1;
|
|
||||||
const sample_rate = 8000;
|
|
||||||
const audioBuffer = audio_context.createBuffer(channels, 960, sample_rate); // 960??
|
|
||||||
//const audioBuffer = audio_context.createBuffer(channels, decoded.length, sample_rate);
|
|
||||||
|
|
||||||
audio_context.onstatechange = () => {
|
|
||||||
console.log("Audio state is now ", audio_context.state);
|
|
||||||
update_audio_button(audio_context.state == "running");
|
|
||||||
};
|
|
||||||
|
|
||||||
gain_node.connect(audio_context.destination); // Connect gain node to speakers
|
|
||||||
audio_context.resume();
|
|
||||||
|
|
||||||
const evtSource = new EventSource("/audio/${id}");
|
|
||||||
evtSource.onopen = (e) => {
|
|
||||||
console.log("evtsource open");
|
|
||||||
}
|
|
||||||
evtSource.onerror = (e) => {
|
|
||||||
console.log("evtsource error", e);
|
|
||||||
}
|
|
||||||
let endsAt = 0;
|
|
||||||
let startAt = 0;
|
|
||||||
evtSource.onmessage = (e) => {
|
|
||||||
const nowBuffering = audioBuffer.getChannelData(0);
|
|
||||||
const u8 = Uint8Array.from(atob(e.data), c => c.charCodeAt(0));
|
|
||||||
new Int16Array(u8).map(alaw_to_s16).forEach((el, i) => nowBuffering[i] = el / 0x8000 );
|
|
||||||
|
|
||||||
const source_node = audio_context.createBufferSource();
|
|
||||||
source_node.buffer = audioBuffer;
|
|
||||||
source_node.connect(gain_node);
|
|
||||||
const now = Date.now();
|
|
||||||
if(now > endsAt) { // lost packets
|
|
||||||
startAt = 0;
|
|
||||||
} else {
|
|
||||||
startAt += audioBuffer.duration;
|
|
||||||
}
|
|
||||||
source_node.start(startAt);
|
|
||||||
endsAt = now + audioBuffer.duration * 1000;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_audio_button(on) {
|
|
||||||
if (${audio}) {
|
|
||||||
audio_button.innerText = "Audio: " + (on ? "on \u{1F508}" : "off \u{1F507}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_audio() {
|
|
||||||
if (audio_context == undefined) {
|
|
||||||
setup_audio();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (audio_context.state == "running") {
|
|
||||||
audio_context.suspend();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (audio_context.state == "suspended") {
|
|
||||||
audio_context.resume();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Unknown audio stream status");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
BIN
Binary file not shown.
@@ -1,39 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import { DevSerial } from "./impl.js";
|
|
||||||
import { RemoteInfo } from "dgram";
|
|
||||||
import { logger } from "./logger.js";
|
|
||||||
import { startVideoStream } from "./session.js";
|
|
||||||
import { discoverDevices } from "./discovery.js";
|
|
||||||
import { Session } from "./session.js";
|
|
||||||
import { Handlers, makeSession } from "./session.js";
|
|
||||||
import { config } from "./settings.js";
|
|
||||||
|
|
||||||
const sessions: Record<string, Session> = {};
|
|
||||||
export const captureSingle = ({ discovery_ip, out_file }: { discovery_ip: string; out_file: string }) => {
|
|
||||||
let devEv = discoverDevices([discovery_ip]);
|
|
||||||
|
|
||||||
const startSession = (s: Session) => {
|
|
||||||
startVideoStream(s);
|
|
||||||
logger.info(`Camera ${s.devName} is now ready to stream`);
|
|
||||||
};
|
|
||||||
|
|
||||||
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
|
||||||
if (dev.devId in sessions) {
|
|
||||||
logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`);
|
|
||||||
const s = makeSession(Handlers, dev, rinfo, startSession, 5000);
|
|
||||||
sessions[dev.devId] = s;
|
|
||||||
config.cameras[dev.devId] = { fix_packet_loss: false };
|
|
||||||
|
|
||||||
s.eventEmitter.on("frame", () => {
|
|
||||||
const assembled = Buffer.concat(s.curImage);
|
|
||||||
fs.writeFileSync(out_file, assembled);
|
|
||||||
logger.info(`Got frame. Exiting`);
|
|
||||||
devEv.emit("close");
|
|
||||||
s.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
import process from "node:process";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import yargs from "yargs/yargs";
|
|
||||||
|
|
||||||
import { captureSingle } from "../capture_single.js";
|
|
||||||
import { serveHttp } from "../http_server.js";
|
|
||||||
import { pair } from "../pair.js";
|
|
||||||
import { loadConfig, config } from "../settings.js";
|
|
||||||
|
|
||||||
import { buildLogger, logger } from "../logger.js";
|
|
||||||
|
|
||||||
const majorVersion = process.versions.node.split(".").map(Number)[0];
|
|
||||||
|
|
||||||
yargs(hideBin(process.argv))
|
|
||||||
.command(
|
|
||||||
"http_server",
|
|
||||||
"start http server",
|
|
||||||
(yargs) => {
|
|
||||||
return yargs
|
|
||||||
.option("color", { describe: "Use color in logs" })
|
|
||||||
.boolean(["audio", "color"])
|
|
||||||
.option("config_file", { describe: "Specify config file" })
|
|
||||||
.option("log_level", { describe: "Set log level" })
|
|
||||||
.option("discovery_ip", { describe: "Camera discovery IP address" })
|
|
||||||
.option("port", { describe: "HTTP Port to listen on" })
|
|
||||||
.string(["log_level", "discovery_ip", "config_file"])
|
|
||||||
.number(["port"])
|
|
||||||
.strict();
|
|
||||||
},
|
|
||||||
(argv) => {
|
|
||||||
if (argv.config_file !== undefined) {
|
|
||||||
loadConfig(argv.config_file);
|
|
||||||
}
|
|
||||||
if (argv.port) {
|
|
||||||
config.http_server.port = argv.port;
|
|
||||||
}
|
|
||||||
if (argv.color !== undefined) {
|
|
||||||
config.logging.use_color = argv.color;
|
|
||||||
}
|
|
||||||
if (argv.log_level !== undefined) {
|
|
||||||
config.logging.level = argv.log_level;
|
|
||||||
}
|
|
||||||
if (argv.discovery_ip !== undefined) {
|
|
||||||
config.discovery_ips = [argv.discovery_ip];
|
|
||||||
}
|
|
||||||
|
|
||||||
buildLogger(config.logging.level, config.logging.use_color);
|
|
||||||
if (majorVersion < 16) {
|
|
||||||
logger.error(`Node version ${majorVersion} is not supported, may malfunction`);
|
|
||||||
}
|
|
||||||
serveHttp(config.http_server.port);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.command(
|
|
||||||
"pair",
|
|
||||||
"configure a camera",
|
|
||||||
(yargs) => {
|
|
||||||
return yargs
|
|
||||||
.option("log_level", { describe: "Set log level", default: "info" })
|
|
||||||
.option("discovery_ip", { describe: "Camera discovery IP address", default: "192.168.1.255" })
|
|
||||||
.option("ssid", { describe: "Wifi network for the camera to connect to" })
|
|
||||||
.option("password", { describe: "Wifi network password" })
|
|
||||||
.demandOption(["ssid", "password"])
|
|
||||||
.string(["ssid", "password"]);
|
|
||||||
},
|
|
||||||
(argv) => {
|
|
||||||
buildLogger(argv.log_level, undefined);
|
|
||||||
if (majorVersion < 16) {
|
|
||||||
logger.error(`Node version ${majorVersion} is not supported, may malfunction`);
|
|
||||||
}
|
|
||||||
pair({ ssid: argv.ssid, password: argv.password });
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.command(
|
|
||||||
"frame",
|
|
||||||
"capture a single frame from the first discovered camera",
|
|
||||||
(yargs) => {
|
|
||||||
return yargs
|
|
||||||
.option("log_level", { describe: "Set log level", default: "info" })
|
|
||||||
.option("discovery_ip", { describe: "Camera discovery IP address", default: "192.168.1.255" })
|
|
||||||
.option("out", { describe: "Path for output file" })
|
|
||||||
.demandOption(["out"])
|
|
||||||
.string(["out", "discovery_ip"]);
|
|
||||||
},
|
|
||||||
(argv) => {
|
|
||||||
buildLogger(argv.log_level, undefined);
|
|
||||||
if (majorVersion < 16) {
|
|
||||||
logger.error(`Node version ${majorVersion} is not supported, may malfunction`);
|
|
||||||
}
|
|
||||||
captureSingle({ discovery_ip: argv.discovery_ip, out_file: argv.out });
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.demandCommand()
|
|
||||||
.parseSync();
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
http_server:
|
|
||||||
port: 5000
|
|
||||||
|
|
||||||
logging:
|
|
||||||
level: debug
|
|
||||||
use_color: true
|
|
||||||
|
|
||||||
cameras:
|
|
||||||
FTYC477360FAWUK:
|
|
||||||
alias: "A9"
|
|
||||||
rotate: 1
|
|
||||||
mirror: false
|
|
||||||
fix_packet_loss: yes
|
|
||||||
audio: true
|
|
||||||
BATC609531EXLVS:
|
|
||||||
alias: "X5"
|
|
||||||
|
|
||||||
# If you are crossing broadcast domains (VLANs) then
|
|
||||||
# you need to specify all IPs as unicast targets
|
|
||||||
discovery_ips:
|
|
||||||
- 192.168.40.101
|
|
||||||
- 192.168.40.102
|
|
||||||
- 192.168.40.103
|
|
||||||
- 192.168.40.104
|
|
||||||
- 192.168.40.105
|
|
||||||
- 192.168.1.1
|
|
||||||
|
|
||||||
# If you are in the same broadcast domain, then
|
|
||||||
# it's easier to just use the broadcast address of your network
|
|
||||||
# discovery_ips:
|
|
||||||
# - 192.168.1.255
|
|
||||||
|
|
||||||
blacklisted_ips:
|
|
||||||
- 192.168.40.102
|
|
||||||
Binary file not shown.
+2
-36
@@ -24,46 +24,12 @@ export const Commands = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Record<keyof typeof Commands,
|
// Record<keyof typeof Commands,
|
||||||
type t = Record<number, keyof typeof Commands>;
|
export const CommandsByValue = Object.keys(Commands).reduce((acc, cur) => {
|
||||||
export const CommandsByValue: t = Object.keys(Commands).reduce((acc: t, cur) => {
|
acc[Commands[cur]] = cur;
|
||||||
let key: keyof typeof Commands = cur as keyof typeof Commands;
|
|
||||||
acc[Commands[key]] = key;
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
export const DrwStart = 0x0a11;
|
|
||||||
export const ControlCommands = {
|
export const ControlCommands = {
|
||||||
// TODO: flip these..
|
|
||||||
ConnectUser: 0x2010,
|
ConnectUser: 0x2010,
|
||||||
ConnectUserAck: 0x2011,
|
ConnectUserAck: 0x2011,
|
||||||
// CloseSession: 0x3110,
|
|
||||||
// CloseSessionAck: 0x3111,
|
|
||||||
DevStatus: 0x0810, // CMD_SYSTEM_STATUS_GET
|
|
||||||
DevStatusAck: 0x0811,
|
|
||||||
WifiSettingsSet: 0x0160, // CMD_NET_WIFISETTING_SET
|
|
||||||
WifiSettings: 0x0260, // CMD_NET_WIFISETTING_GET
|
|
||||||
WifiSettingsAck: 0x0261,
|
|
||||||
ListWifi: 0x0360, // CMD_NET_WIFI_SCAN
|
|
||||||
ListWifiAck: 0x0361,
|
|
||||||
StartVideo: 0x1030, // CMD_PEER_LIVEVIDEO_START
|
|
||||||
StartVideoAck: 0x1031,
|
|
||||||
StopVideo: 0x1130, // CMD_PEER_LIVEVIDEO_STOP
|
|
||||||
Shutdown: 0x1010, //CMD_SYSTEM_SHUTDOWN,
|
|
||||||
Reboot: 0x1110, //CMD_SYSTEM_REBOOT,
|
|
||||||
VideoParamSet: 0x1830, // CMD_PEER_VIDEOPARAM_SET
|
|
||||||
VideoParamSetAck: 0x1831,
|
|
||||||
VideoParamGet: 0x1930, // CMD_PEER_VIDEOPARAM_GET
|
|
||||||
IRToggle: 0x0a30, // CMD_PEER_IRCUT_ONOFF
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ccDest: Record<number, number> = {
|
|
||||||
[ControlCommands.ConnectUser]: 0xff00,
|
|
||||||
[ControlCommands.DevStatus]: 0x0000,
|
|
||||||
[ControlCommands.StartVideo]: 0x0000,
|
|
||||||
[ControlCommands.ListWifi]: 0x0000,
|
|
||||||
[ControlCommands.WifiSettings]: 0x0000,
|
|
||||||
|
|
||||||
[ControlCommands.ListWifiAck]: 0xaa55,
|
|
||||||
[ControlCommands.ConnectUserAck]: 0xaa55,
|
|
||||||
[ControlCommands.DevStatusAck]: 0xaa55,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { createSocket, RemoteInfo } from "node:dgram";
|
|
||||||
import EventEmitter from "node:events";
|
|
||||||
|
|
||||||
import { Commands } from "./datatypes.js";
|
|
||||||
import { create_LanSearch, parse_PunchPkt } from "./impl.js";
|
|
||||||
import { logger } from "./logger.js";
|
|
||||||
import { config } from "./settings.js";
|
|
||||||
|
|
||||||
const handleIncomingPunch = (msg: Buffer, ee: EventEmitter, rinfo: RemoteInfo) => {
|
|
||||||
const ab = new Uint8Array(msg).buffer;
|
|
||||||
const dv = new DataView(ab);
|
|
||||||
const cmd_id = dv.readU16();
|
|
||||||
if (cmd_id != Commands.PunchPkt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (config.blacklisted_ips.indexOf(rinfo.address) !== -1) {
|
|
||||||
logger.debug(`Dropping packet of blacklisted IP: ${rinfo.address}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug("Received a PunchPkt message");
|
|
||||||
ee.emit("discover", rinfo, parse_PunchPkt(dv));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const discoverDevices = (discovery_ips: string[]): EventEmitter => {
|
|
||||||
const sock = createSocket("udp4");
|
|
||||||
const SEND_PORT = 32108;
|
|
||||||
const ee = new EventEmitter();
|
|
||||||
|
|
||||||
sock.on("error", (err) => {
|
|
||||||
console.error(`sock error:\n${err.stack}`);
|
|
||||||
sock.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.on("message", (msg, rinfo) => handleIncomingPunch(msg, ee, rinfo));
|
|
||||||
|
|
||||||
let timers = [];
|
|
||||||
sock.on("listening", () => {
|
|
||||||
let ls_buf = create_LanSearch();
|
|
||||||
sock.setBroadcast(true);
|
|
||||||
discovery_ips.forEach((discovery_ip) => {
|
|
||||||
logger.info(`Searching for devices on ${discovery_ip}`);
|
|
||||||
|
|
||||||
let int = setInterval(() => {
|
|
||||||
logger.log("trace", `>> LanSearch [${discovery_ip}]`);
|
|
||||||
sock.send(new Uint8Array(ls_buf.buffer), SEND_PORT, discovery_ip);
|
|
||||||
}, 3000);
|
|
||||||
timers.push(int);
|
|
||||||
logger.log("trace", `>> LanSearch [${discovery_ip}]`);
|
|
||||||
sock.send(new Uint8Array(ls_buf.buffer), SEND_PORT, discovery_ip);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.bind();
|
|
||||||
|
|
||||||
sock.on("close", () => timers.forEach((timer) => clearInterval(timer)));
|
|
||||||
ee.on("close", () => sock.close());
|
|
||||||
return ee;
|
|
||||||
};
|
|
||||||
+46
-468
@@ -1,47 +1,24 @@
|
|||||||
-- Create a new protocol for your custom packets
|
-- Create a new protocol for your custom packets
|
||||||
ilnk_proto = Proto("iLnkP2P", "iLnk")
|
my_protocol = Proto("myprotocol", "My Custom Protocol")
|
||||||
|
|
||||||
-- Define the fields you want to display in Wireshark
|
-- Define the fields you want to display in Wireshark
|
||||||
ilnk_proto.fields = {}
|
my_protocol.fields = {}
|
||||||
ilnk_proto.fields.type = ProtoField.string("iLnkP2P.type", "Type")
|
my_protocol.fields.type = ProtoField.string("myprotocol.type", "Type")
|
||||||
-- ilnk_proto.fields.payload = ProtoField.bytes("iLnkP2P.payload", "Payload")
|
my_protocol.fields.payload = ProtoField.bytes("myprotocol.payload", "Data")
|
||||||
ilnk_proto.fields.len = ProtoField.uint16("iLnkP2P.len", "Packet length", base.HEX)
|
my_protocol.fields.len = ProtoField.uint16("myprotocol.len", "Len", base.HEX)
|
||||||
|
|
||||||
ilnk_proto.fields.m_type = ProtoField.uint8("iLnkP2P.m_type", "Stream type", base.HEX)
|
my_protocol.fields.m_type = ProtoField.uint8("myprotocol.m_type", "Stream Type", base.HEX)
|
||||||
ilnk_proto.fields.m_stream_id = ProtoField.uint8("iLnkP2P.m_stream_id", "Stream ID", base.HEX)
|
my_protocol.fields.m_stream_id = ProtoField.uint8("myprotocol.m_stream_id", "Stream ID", base.HEX)
|
||||||
ilnk_proto.fields.pkt_seq = ProtoField.uint16("iLnkP2P.pkt_seq", "Packet ID", base.HEX)
|
my_protocol.fields.elem_count = ProtoField.uint16("myprotocol.elem_count", "Elem count", base.DEC)
|
||||||
ilnk_proto.fields.elem_count = ProtoField.uint16("iLnkP2P.elem_count", "Elem count", base.DEC)
|
|
||||||
|
|
||||||
ilnk_proto.fields.cmd_payload_len = ProtoField.uint16("iLnkP2P.cmd_payload_len", "CMD Payload Len", base.HEX)
|
my_protocol.fields.cmd_payload_len = ProtoField.uint16("myprotocol.cmd_payload_len", "CMD Payload Len", base.HEX)
|
||||||
ilnk_proto.fields.cmd = ProtoField.uint16("iLnkP2P.cmd", "CMD", base.HEX)
|
my_protocol.fields.cmd = ProtoField.uint16("myprotocol.cmd", "CMD", base.HEX)
|
||||||
ilnk_proto.fields.start = ProtoField.uint16("iLnkP2P.start", "Start", base.HEX)
|
my_protocol.fields.start = ProtoField.uint16("myprotocol.start", "Start", base.HEX)
|
||||||
ilnk_proto.fields.cmd_dest = ProtoField.uint16("iLnkP2P.cmd_dest", "Dest", base.HEX)
|
my_protocol.fields.cmd_dest = ProtoField.uint16("myprotocol.cmd_dest", "Dest", base.HEX)
|
||||||
ilnk_proto.fields.auth_token = ProtoField.bytes("iLnkP2P.auth_token", "CMD auth token", base.DASH)
|
my_protocol.fields.cmd_payload = ProtoField.bytes("myprotocol.payload", "Payload", base.DASH)
|
||||||
ilnk_proto.fields.cmd_payload = ProtoField.bytes("iLnkP2P.payload", "CMD Payload", base.DASH)
|
|
||||||
ilnk_proto.fields.warning = ProtoField.string("iLnkP2P.warning", "Warning")
|
|
||||||
--
|
|
||||||
-- jpeg | audio | continuation type?
|
|
||||||
ilnk_proto.fields.data_payload = ProtoField.bytes("iLnkP2P.data_payload", "Data Payload", base.DASH)
|
|
||||||
ilnk_proto.fields.payload_type = ProtoField.string("iLnkP2P.payload_type", "Payload type")
|
|
||||||
ilnk_proto.fields.payload_subtype = ProtoField.string("iLnkP2P.payload_type", "Payload type")
|
|
||||||
ilnk_proto.fields.payload_len = ProtoField.uint32("iLnkP2P.payload_len", "Payload len")
|
|
||||||
ilnk_proto.fields.frame_no = ProtoField.uint32("iLnkP2P.frame_no", "Frame no")
|
|
||||||
-- audio
|
|
||||||
ilnk_proto.fields.audio_header = ProtoField.bytes("iLnkP2P.audio_header", "Audio Header")
|
|
||||||
ilnk_proto.fields.hdr_type = ProtoField.uint16("iLnkP2P.hdr_type", "Header Type")
|
|
||||||
ilnk_proto.fields.hdr_streamid = ProtoField.uint16("iLnkP2P.hdr_streamid", "Header Stream ID")
|
|
||||||
ilnk_proto.fields.hdr_frameno = ProtoField.uint32("iLnkP2P.hdr_frameno", "Header Frame")
|
|
||||||
ilnk_proto.fields.hdr_len = ProtoField.uint16("iLnkP2P.hdr_len", "Header Len")
|
|
||||||
ilnk_proto.fields.hdr_ver = ProtoField.uint16("iLnkP2P.hdr_ver", "Header version")
|
|
||||||
ilnk_proto.fields.hdr_res = ProtoField.uint16("iLnkP2P.hdr_red", "Header resolution")
|
|
||||||
|
|
||||||
ilnk_proto.fields.encrypted = ProtoField.bool("iLnkP2P.encrypted", "Encrypted")
|
my_protocol.fields.encrypted = ProtoField.bool("myprotocol.encrypted", "Encrypted")
|
||||||
ilnk_proto.fields.cmd_type = ProtoField.string("iLnkP2P.cmd_type", "Cmd Pkt Type")
|
my_protocol.fields.cmd_type = ProtoField.string("myprotocol.payload", "Cmd Pkt Type")
|
||||||
ilnk_proto.fields.cmd_name = ProtoField.string("iLnkP2P.cmd_name", "Cmd Pkt")
|
|
||||||
ilnk_proto.fields.decrypted_data = ProtoField.bytes("iLnkP2P.decrypted_data", "Decrypted data")
|
|
||||||
|
|
||||||
-- PunchPkt
|
|
||||||
ilnk_proto.fields.serial = ProtoField.string("iLnkP2P.serial", "Serial")
|
|
||||||
|
|
||||||
lut = {
|
lut = {
|
||||||
[0xf1f0] = "Close",
|
[0xf1f0] = "Close",
|
||||||
@@ -64,440 +41,52 @@ lut = {
|
|||||||
[0xf169] = "ListenReqAck",
|
[0xf169] = "ListenReqAck",
|
||||||
[0xf170] = "RlyHelloAck",
|
[0xf170] = "RlyHelloAck",
|
||||||
[0xf171] = "RlyHelloAck2",
|
[0xf171] = "RlyHelloAck2",
|
||||||
|
__index = function(tbl, key)
|
||||||
|
return "UNK " .. string.format("0x%X", key)
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
setmetatable(lut, lut)
|
||||||
control_lut = {
|
|
||||||
[0x0000] = "CFGID_VERSION",
|
|
||||||
[0x0000] = "CMD_ACK_OK",
|
|
||||||
[0x0001] = "CFGID_LANGUAGE",
|
|
||||||
[0x0001] = "CMD_ACK_UNAUTH",
|
|
||||||
[0x0002] = "CFGID_PRODUCTE",
|
|
||||||
[0x0002] = "CMD_ACK_NO_PRIVILEGE",
|
|
||||||
[0x0003] = "CFGID_UPGRADE",
|
|
||||||
[0x0003] = "CMD_ACK_INVALID_PARAM",
|
|
||||||
[0x0004] = "CFGID_P2P",
|
|
||||||
[0x0004] = "CMD_ACK_CMDEXCUTE_FAILED",
|
|
||||||
[0x0005] = "CFGID_TZ",
|
|
||||||
[0x0005] = "CMD_ACK_NONE_RESULT",
|
|
||||||
[0x0006] = "CFGID_USER",
|
|
||||||
[0x0006] = "CMD_ACK_UNKNOWN",
|
|
||||||
[0x0007] = "CFGID_OPR",
|
|
||||||
[0x0008] = "CFGID_SERIAL",
|
|
||||||
[0x0009] = "CFGID_WIRED",
|
|
||||||
[0x000a] = "CFGID_WLAN",
|
|
||||||
[0x000b] = "CFGID_OSD",
|
|
||||||
[0x000c] = "CFGID_IMG",
|
|
||||||
[0x000d] = "CFGID_CMOS",
|
|
||||||
[0x000e] = "CFGID_PTZ",
|
|
||||||
[0x000f] = "CFGID_AUDIO",
|
|
||||||
[0x0010] = "CFGID_VIDEO",
|
|
||||||
[0x0011] = "CFGID_RECPOLICY",
|
|
||||||
[0x0012] = "CFGID_RESCH",
|
|
||||||
[0x0013] = "CFGID_MDALARM",
|
|
||||||
[0x0014] = "CFGID_ADCALARM",
|
|
||||||
[0x0015] = "CFGID_INPUTALARM",
|
|
||||||
[0x0016] = "CFGID_SMTP",
|
|
||||||
[0x0017] = "CFGID_FTP",
|
|
||||||
[0x0018] = "CFGID_PUSH",
|
|
||||||
[0x0019] = "CFGID_WLANPMK",
|
|
||||||
[0x00fe] = "CGICMD",
|
|
||||||
[0x00ff] = "BINCMD",
|
|
||||||
[0x03e8] = "CMD_ACK_ILLIGAL",
|
|
||||||
[0x0eff] = "CMD_DEV_BROADCAST",
|
|
||||||
[0x1000] = "CMD_SYSTEM_DFTCFG_IMPORT",
|
|
||||||
[0x1001] = "CMD_SYSTEM_DFTCFG_EXPORT",
|
|
||||||
[0x1002] = "CMD_SYSTEM_DFTCFG_RECOVERY",
|
|
||||||
[0x1003] = "CMD_SYSTEM_ITEMDFTCFG_RECOVERY",
|
|
||||||
[0x1004] = "CMD_SYSTEM_CRNCFG_EXPORT",
|
|
||||||
[0x1005] = "CMD_SYSTEM_CRNCFG_IMPORT",
|
|
||||||
[0x1006] = "CMD_SYSTEM_DFTCFG_CREATE",
|
|
||||||
[0x1007] = "CMD_SYSTEM_UPGRAD_SET",
|
|
||||||
[0x1008] = "CMD_SYSTEM_STATUS_GET",
|
|
||||||
[0x1009] = "CMD_SYSTEM_UPGRAD_GET",
|
|
||||||
[0x1010] = "CMD_SYSTEM_SHUTDOWN",
|
|
||||||
[0x1011] = "CMD_SYSTEM_REBOOT",
|
|
||||||
[0x1012] = "CMD_SYSTEM_INF_GET",
|
|
||||||
[0x1013] = "CMD_SYSTEM_ALIAS_SET",
|
|
||||||
[0x1020] = "CMD_SYSTEM_USER_CHK",
|
|
||||||
[0x1021] = "CMD_SYSTEM_USER_SET",
|
|
||||||
[0x1022] = "CMD_SYSTEM_USER_GET",
|
|
||||||
[0x1023] = "CMD_SYSTEM_USER_CHG",
|
|
||||||
[0x1030] = "CMD_SYSTEM_P2PPARAM_SET",
|
|
||||||
[0x1031] = "CMD_SYSTEM_OPRPOLICY_SET",
|
|
||||||
[0x1032] = "CMD_SYSTEM_OPRPOLICY_GET",
|
|
||||||
[0x1033] = "CMD_SYSTEM_P2PPARAM_GET",
|
|
||||||
[0x1040] = "CMD_SYSTEM_DATETIME_SET",
|
|
||||||
[0x1041] = "CMD_SYSTEM_DATETIME_GET",
|
|
||||||
[0x1051] = "CMD_NOTIFICATION",
|
|
||||||
[0x1100] = "ACK_SYSTEM_DFTCFG_IMPORT",
|
|
||||||
[0x1101] = "ACK_SYSTEM_DFTCFG_EXPORT",
|
|
||||||
[0x1102] = "ACK_SYSTEM_DFTCFG_RECOVERY",
|
|
||||||
[0x1103] = "ACK_SYSTEM_ITEMDFTCFG_RECOVERY",
|
|
||||||
[0x1104] = "ACK_SYSTEM_CRNCFG_EXPORT",
|
|
||||||
[0x1105] = "ACK_SYSTEM_CRNCFG_IMPORT",
|
|
||||||
[0x1106] = "ACK_SYSTEM_DFTCFG_CREATE",
|
|
||||||
[0x1107] = "ACK_SYSTEM_UPGRAD_SET",
|
|
||||||
[0x1108] = "ACK_SYSTEM_STATUS_GET",
|
|
||||||
[0x1109] = "ACK_SYSTEM_UPGRAD_GET",
|
|
||||||
[0x1110] = "ACK_SYSTEM_SHUTDOWN",
|
|
||||||
[0x1111] = "ACK_SYSTEM_REBOOT",
|
|
||||||
[0x1112] = "ACK_SYSTEM_INF_GET",
|
|
||||||
[0x1113] = "ACK_SYSTEM_ALIAS_SET",
|
|
||||||
[0x1120] = "ACK_SYSTEM_USER_CHK",
|
|
||||||
[0x1121] = "ACK_SYSTEM_USER_SET",
|
|
||||||
[0x1122] = "ACK_SYSTEM_USER_GET",
|
|
||||||
[0x1123] = "ACK_SYSTEM_USER_CHG",
|
|
||||||
[0x1130] = "ACK_SYSTEM_P2PPARAM_SET",
|
|
||||||
[0x1131] = "ACK_SYSTEM_OPRPOLICY_SET",
|
|
||||||
[0x1132] = "ACK_SYSTEM_OPRPOLICY_GET",
|
|
||||||
[0x1133] = "ACK_SYSTEM_P2PPARAM_GET",
|
|
||||||
[0x1140] = "ACK_SYSTEM_DATETIME_SET",
|
|
||||||
[0x1141] = "ACK_SYSTEM_DATETIME_GET",
|
|
||||||
[0x1151] = "ACK_NOTIFICATION",
|
|
||||||
[0x2000] = "CMD_SD_FORMAT",
|
|
||||||
[0x2001] = "CMD_SD_RECPOLICY_SET",
|
|
||||||
[0x2002] = "CMD_SD_RECPOLICY_GET",
|
|
||||||
[0x2003] = "CMD_SD_RECORDING_NOW",
|
|
||||||
[0x2004] = "CMD_SD_INFO_GET",
|
|
||||||
[0x2005] = "CMD_SD_RECORDFILE_GET",
|
|
||||||
[0x2006] = "CMD_SD_RECORDSCH_GET",
|
|
||||||
[0x2007] = "CMD_SD_RECORDSCH_SET",
|
|
||||||
[0x2008] = "CMD_SD_RETRIVEL",
|
|
||||||
[0x2009] = "CMD_SD_PICFILE_GET",
|
|
||||||
[0x200a] = "CMD_SD_PIC_CAPTURE",
|
|
||||||
[0x200b] = "CMD_SD_REC_DEL",
|
|
||||||
[0x200c] = "CMD_SD_PIC_DEL",
|
|
||||||
[0x200d] = "CMD_SD_SPL_DEL",
|
|
||||||
[0x2100] = "ACK_SD_FORMAT",
|
|
||||||
[0x2101] = "ACK_SD_RECPOLICY_SET",
|
|
||||||
[0x2102] = "ACK_SD_RECPOLICY_GET",
|
|
||||||
[0x2103] = "ACK_SD_RECORDING_NOW",
|
|
||||||
[0x2104] = "ACK_SD_INFO_GET",
|
|
||||||
[0x2105] = "ACK_SD_RECORDFILE_GET",
|
|
||||||
[0x2106] = "ACK_SD_RECORDSCH_GET",
|
|
||||||
[0x2107] = "ACK_SD_RECORDSCH_SET",
|
|
||||||
[0x2108] = "ACK_SD_RETRIVEL",
|
|
||||||
[0x2109] = "ACK_SD_PICFILE_GET",
|
|
||||||
[0x210a] = "ACK_SD_PIC_CAPTURE",
|
|
||||||
[0x210b] = "ACK_SD_REC_DEL",
|
|
||||||
[0x210c] = "ACK_SD_PIC_DEL",
|
|
||||||
[0x210d] = "ACK_SD_SPL_DEL",
|
|
||||||
[0x3000] = "CMD_PEER_LIVEAUDIO_START",
|
|
||||||
[0x3001] = "CMD_PEER_LIVEAUDIO_STOP",
|
|
||||||
[0x3002] = "CMD_LOCAL_LIVEAUDIO_START",
|
|
||||||
[0x3003] = "CMD_LOCAL_LIVEAUDIO_STOP",
|
|
||||||
[0x3004] = "CMD_PEER_AUDIOPARAM_SET",
|
|
||||||
[0x3005] = "CMD_PEER_AUDIOPARAM_GET",
|
|
||||||
[0x3006] = "CMD_PEER_AUDIOFILE_STARTPLAY",
|
|
||||||
[0x3007] = "CMD_PEER_AUDIOFILE_STOPPLAY",
|
|
||||||
[0x3008] = "CMD_PEER_AUDIOFILELIST_GET",
|
|
||||||
[0x300a] = "CMD_PEER_IRCUT_ONOFF",
|
|
||||||
[0x300b] = "CMD_PEER_LIGHTFILL_ONOFF",
|
|
||||||
[0x3010] = "CMD_PEER_LIVEVIDEO_START",
|
|
||||||
[0x3011] = "CMD_PEER_LIVEVIDEO_STOP",
|
|
||||||
[0x3012] = "CMD_PEER_PLAYBACK_START",
|
|
||||||
[0x3013] = "CMD_PEER_PLAYBACK_STOP",
|
|
||||||
[0x3014] = "CMD_PEER_PLAYBACK_SEEK",
|
|
||||||
[0x3015] = "CMD_PEER_PLAYBACK_SPEED",
|
|
||||||
[0x3016] = "CMD_PEER_PLAYBACK_PAUSE",
|
|
||||||
[0x3017] = "CMD_PEER_PLAYBACK_RESUME",
|
|
||||||
[0x3018] = "CMD_PEER_VIDEOPARAM_SET",
|
|
||||||
[0x3019] = "CMD_PEER_VIDEOPARAM_GET",
|
|
||||||
[0x301a] = "CMD_SNAPSHOT_GET",
|
|
||||||
[0x301b] = "CMD_PEER_PLAYBACK_END",
|
|
||||||
[0x301c] = "CMD_PEER_PLAYBACK_STEP",
|
|
||||||
[0x3020] = "CMD_DOORBELL_CALL_OPEN",
|
|
||||||
[0x3021] = "CMD_DOORBELL_CALL_CLOSE",
|
|
||||||
[0x3022] = "CMD_DOORBELL_CALL_ACCEPT",
|
|
||||||
[0x3023] = "CMD_DOORBELL_CALL_REJECT",
|
|
||||||
[0x3024] = "CMD_LOCAL_LIVEVIDIO_SEND_ON",
|
|
||||||
[0x3025] = "CMD_LOCAL_LIVEVIDIO_SEND_OFF",
|
|
||||||
[0x3026] = "CMD_LOCAL_AUDIO_STATUS_SET",
|
|
||||||
[0x3027] = "CMD_LOCAL_AUDIO_STATUS_GET",
|
|
||||||
[0x3028] = "CMD_LOCAL_AVREC_START",
|
|
||||||
[0x3029] = "CMD_LOCAL_AVREC_STOP",
|
|
||||||
[0x3030] = "CMD_LOCAL_PLAYBACK_START",
|
|
||||||
[0x3031] = "CMD_LOCAL_PLAYBACK_STOP",
|
|
||||||
[0x3032] = "CMD_LOCAL_PLAYBACK_SEEK",
|
|
||||||
[0x3033] = "CMD_LOCAL_PLAYBACK_PAUSE",
|
|
||||||
[0x3034] = "CMD_LOCAL_PLAYBACK_RESUME",
|
|
||||||
[0x3035] = "CMD_LOCAL_PLAYBACK_START1",
|
|
||||||
[0x3036] = "CMD_LOCAL_PLAYBACK_STEP",
|
|
||||||
[0x3037] = "CMD_LOCAL_PLAYBACK_START2",
|
|
||||||
[0x3040] = "CMD_LOCAL_MJREC_START",
|
|
||||||
[0x3041] = "CMD_LOCAL_MJREC_STOP",
|
|
||||||
[0x3100] = "ACK_PEER_LIVEAUDIO_START",
|
|
||||||
[0x3101] = "ACK_PEER_LIVEAUDIO_STOP",
|
|
||||||
[0x3102] = "ACK_LOCAL_LIVEAUDIO_START",
|
|
||||||
[0x3103] = "ACK_LOCAL_LIVEAUDIO_STOP",
|
|
||||||
[0x3104] = "ACK_PEER_AUDIOPARAM_SET",
|
|
||||||
[0x3105] = "ACK_PEER_AUDIOPARAM_GET",
|
|
||||||
[0x3106] = "ACK_PEER_AUDIOFILE_STARTPLAY",
|
|
||||||
[0x3107] = "ACK_PEER_AUDIOFILE_STOPPLAY",
|
|
||||||
[0x3108] = "ACK_PEER_AUDIOFILELIST_GET",
|
|
||||||
[0x310a] = "ACK_PEER_IRCUT_ONOFF",
|
|
||||||
[0x310b] = "ACK_PEER_LIGHTFILL_ONOFF",
|
|
||||||
[0x3110] = "ACK_PEER_LIVEVIDEO_START",
|
|
||||||
[0x3111] = "ACK_PEER_LIVEVIDEO_STOP",
|
|
||||||
[0x3112] = "ACK_PEER_PLAYBACK_START",
|
|
||||||
[0x3113] = "ACK_PEER_PLAYBACK_STOP",
|
|
||||||
[0x3114] = "ACK_PEER_PLAYBACK_SEEK",
|
|
||||||
[0x3115] = "ACK_PEER_PLAYBACK_SPEED",
|
|
||||||
[0x3116] = "ACK_PEER_PLAYBACK_PAUSE",
|
|
||||||
[0x3117] = "ACK_PEER_PLAYBACK_RESUME",
|
|
||||||
[0x3118] = "ACK_PEER_VIDEOPARAM_SET",
|
|
||||||
[0x3119] = "ACK_PEER_VIDEOPARAM_GET",
|
|
||||||
[0x311a] = "ACK_SNAPSHOT_GET",
|
|
||||||
[0x311b] = "ACK_PEER_PLAYBACK_END",
|
|
||||||
[0x311c] = "ACK_PEER_PLAYBACK_STEP",
|
|
||||||
[0x4000] = "CMD_FILE_CTRL",
|
|
||||||
[0x4005] = "CMD_FILETRANSFER_FILELIST_GET",
|
|
||||||
[0x4010] = "CMD_LOCALPATH",
|
|
||||||
[0x4101] = "ACK_FILE_CREATE",
|
|
||||||
[0x4102] = "ACK_FILE_RENAME",
|
|
||||||
[0x4103] = "ACK_FILE_DELETE",
|
|
||||||
[0x4104] = "ACK_FILE_MOVE",
|
|
||||||
[0x4105] = "ACK_FILE_LIST",
|
|
||||||
[0x4110] = "ACK_FILE_DOWNLOAD",
|
|
||||||
[0x4111] = "ACK_FILE_DOWNLOAD_PAUSE",
|
|
||||||
[0x4112] = "ACK_FILE_DOWNLOAD_RESUME",
|
|
||||||
[0x4113] = "ACK_FILE_DOWNLOAD_CANCEL",
|
|
||||||
[0x4120] = "ACK_FILE_UPLOAD",
|
|
||||||
[0x4121] = "ACK_FILE_UPLOAD_PAUSE",
|
|
||||||
[0x4122] = "ACK_FILE_UPLOAD_RESUME",
|
|
||||||
[0x4123] = "ACK_FILE_UPLOAD_CANCEL",
|
|
||||||
[0x50ff] = "CMD_PASSTHROUGH_STRING_PUT",
|
|
||||||
[0x51ff] = "ACK_PASSTHROUGH_STRING_PUT",
|
|
||||||
[0x55fe] = "CMD_SESSION_CHECK",
|
|
||||||
[0x6001] = "CB_IEGET_STATUS",
|
|
||||||
[0x6001] = "CMD_NET_WIFISETTING_SET",
|
|
||||||
[0x6002] = "CB_IEGET_PARAM",
|
|
||||||
[0x6002] = "CMD_NET_WIFISETTING_GET",
|
|
||||||
[0x6003] = "CB_IEGET_CAM_PARAMS",
|
|
||||||
[0x6003] = "CMD_NET_WIFI_SCAN",
|
|
||||||
[0x6004] = "CB_IEGET_LOG",
|
|
||||||
[0x6004] = "CMD_NET_WIREDSETTING_SET",
|
|
||||||
[0x6005] = "CB_IEGET_MISC",
|
|
||||||
[0x6005] = "CMD_NET_WIREDSETTING_GET",
|
|
||||||
[0x6006] = "CB_IEGET_RECORD",
|
|
||||||
[0x6007] = "CB_IEGET_RECORD_FILE",
|
|
||||||
[0x6008] = "CB_IEGET_WIFI_SCAN",
|
|
||||||
[0x6009] = "CB_IEGET_FACTORY",
|
|
||||||
[0x600a] = "CB_IESET_IR",
|
|
||||||
[0x600b] = "CB_IESET_UPNP",
|
|
||||||
[0x600c] = "CB_IESET_ALARM",
|
|
||||||
[0x600d] = "CB_IESET_LOG",
|
|
||||||
[0x600e] = "CB_IESET_USER",
|
|
||||||
[0x600f] = "CB_IESET_ALIAS",
|
|
||||||
[0x6010] = "CB_IESET_MAIL",
|
|
||||||
[0x6011] = "CB_IESET_WIFI",
|
|
||||||
[0x6012] = "CB_CAM_CONTROL",
|
|
||||||
[0x6013] = "CB_IESET_DATE",
|
|
||||||
[0x6014] = "CB_IESET_MEDIA",
|
|
||||||
[0x6015] = "CB_IESET_SNAPSHOT",
|
|
||||||
[0x6016] = "CB_IESET_DDNS",
|
|
||||||
[0x6017] = "CB_IESET_MISC",
|
|
||||||
[0x6018] = "CB_IEGET_FTPTEST",
|
|
||||||
[0x6019] = "CB_DECODER_CONTROL",
|
|
||||||
[0x601a] = "CB_IESET_DEFAULT",
|
|
||||||
[0x601b] = "CB_IESET_MOTO",
|
|
||||||
[0x601c] = "CB_IEGET_MAILTEST",
|
|
||||||
[0x601d] = "CB_IESET_MAILTEST",
|
|
||||||
[0x601e] = "CB_IEDEL_FILE",
|
|
||||||
[0x601f] = "CB_IELOGIN",
|
|
||||||
[0x6020] = "CB_IESET_DEVICE",
|
|
||||||
[0x6021] = "CB_IESET_NETWORK",
|
|
||||||
[0x6022] = "CB_IESET_FTPTEST",
|
|
||||||
[0x6023] = "CB_IESET_DNS",
|
|
||||||
[0x6024] = "CB_IESET_OSD",
|
|
||||||
[0x6025] = "CB_IESET_FACTORY",
|
|
||||||
[0x6026] = "CB_IESET_PPPOE",
|
|
||||||
[0x6027] = "CB_IEREBOOT",
|
|
||||||
[0x6028] = "CB_IEFORMATSD",
|
|
||||||
[0x6029] = "CB_IESET_RECORDSCH",
|
|
||||||
[0x602a] = "CB_IESET_WIFISCAN",
|
|
||||||
[0x602b] = "CB_IERESTORE",
|
|
||||||
[0x602c] = "CB_IESET_FTP",
|
|
||||||
[0x602d] = "CB_IESET_RTSP",
|
|
||||||
[0x602e] = "CB_IEGET_VIDEOSTREAM",
|
|
||||||
[0x602f] = "CB_UPGRADE_APP",
|
|
||||||
[0x6030] = "CB_UPGRADE_SYS",
|
|
||||||
[0x6031] = "CB_SET_IIC",
|
|
||||||
[0x6032] = "CB_GET_IIC",
|
|
||||||
[0x6033] = "CB_IEGET_ALARMLOG",
|
|
||||||
[0x6034] = "CB_IESET_ALARMLOGCLR",
|
|
||||||
[0x6035] = "CB_IEGET_SYSWIFI",
|
|
||||||
[0x6036] = "CB_IESET_SYSWIFI",
|
|
||||||
[0x6037] = "CB_IEGET_LIVESTREAM",
|
|
||||||
[0x6040] = "CB_NOTIFICATION",
|
|
||||||
[0x6053] = "CB_IEGET_BILL",
|
|
||||||
[0x6054] = "CB_APP_VERSION",
|
|
||||||
[0x60a0] = "CB_CHECK_USER",
|
|
||||||
[0x60a1] = "CB_IESET_BILL",
|
|
||||||
[0x6101] = "ACK_NET_WIFISETTING_SET",
|
|
||||||
[0x6102] = "ACK_NET_WIFISETTING_GET",
|
|
||||||
[0x6103] = "ACK_NET_WIFI_SCAN",
|
|
||||||
[0x6104] = "ACK_NET_WIREDSETTING_SET",
|
|
||||||
[0x6105] = "ACK_NET_WIREDSETTING_GET",
|
|
||||||
[0x7000] = "CMD_FRIEND_MSG",
|
|
||||||
[0x99f0] = "CB_SET_P2PPARAM",
|
|
||||||
[0x99fe] = "CB_GET_SYSOPR",
|
|
||||||
[0x99ff] = "CB_SET_SYSOPR",
|
|
||||||
[0xf000] = "CMD_LOCAL_SESSION_INF",
|
|
||||||
[0xf001] = "CMD_LOCAL_SESSION_CHECK",
|
|
||||||
[0xf002] = "CMD_LOCAL_SESSION_GET",
|
|
||||||
[0xf003] = "CMD_LOCAL_SESSION_CTRL",
|
|
||||||
[0xf004] = "CMD_LOCAL_REC_START",
|
|
||||||
[0xf005] = "CMD_LOCAL_REC_STOP",
|
|
||||||
[0xf006] = "CMD_LOCAL_REC_MERGECTRL",
|
|
||||||
[0xf007] = "CMD_LOCAL_P2P_START",
|
|
||||||
[0xf008] = "CMD_LOCAL_P2P_STOP",
|
|
||||||
[0xf00f] = "CMD_SESSION_CLOSE",
|
|
||||||
[0xf010] = "CMD_LOCAL_PUSH_STRING",
|
|
||||||
[0xf011] = "CMD_LOCAL_PUSH_CFG",
|
|
||||||
[0xf012] = "CMD_LOCAL_RCVVID_DEC",
|
|
||||||
[0xf021] = "CMD_LOCAL_LAPSED",
|
|
||||||
[0xff01] = "CB_SET_SINGLE_SETTING_DEFAULT",
|
|
||||||
[0xff10] = "CB_GET_FILE",
|
|
||||||
[0xff11] = "CB_PUT_FILE",
|
|
||||||
[0xff12] = "CB_SET_FILE",
|
|
||||||
[0xff13] = "CB_GET_FILELIST",
|
|
||||||
[0xff14] = "CB_SET_GPIO",
|
|
||||||
[0xff15] = "CB_GET_GPIO",
|
|
||||||
[0xff16] = "CB_GET_ADC",
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Define a function to dissect the packets
|
-- Define a function to dissect the packets
|
||||||
function ilnk_proto.dissector(buffer, pinfo, tree)
|
function my_protocol.dissector(buffer, pinfo, tree)
|
||||||
local packet_length = buffer:len()
|
local packet_length = buffer:len()
|
||||||
|
|
||||||
local subtree = tree:add(ilnk_proto, buffer(), "iLnkP2P")
|
local subtree = tree:add(my_protocol, buffer(), "My Custom Protocol Data")
|
||||||
|
|
||||||
-- Add the entire packet as a field
|
-- Add the entire packet as a field
|
||||||
local packettype = buffer(0, 2):uint()
|
local packetname = lut[buffer(0, 2):uint()]
|
||||||
local packetname = lut[packettype]
|
subtree:add(my_protocol.fields.type, packetname)
|
||||||
packetname = packetname or "UNK " .. string.format("0x%X", packettype)
|
|
||||||
subtree:add(ilnk_proto.fields.type, buffer(0, 2), packetname)
|
|
||||||
|
|
||||||
-- Set the protocol description in the packet list
|
-- Set the protocol description in the packet list
|
||||||
pinfo.cols.protocol:set("iLnkP2P")
|
pinfo.cols.protocol:set("myprotocol")
|
||||||
pinfo.cols.info:set(packetname)
|
|
||||||
if packetname == "PunchPkt" or packetname == "P2pRdy" then
|
|
||||||
local len = buffer(2, 2)
|
|
||||||
subtree:add(ilnk_proto.fields.len, len)
|
|
||||||
local serial_prefix = buffer(4, 4):string()
|
|
||||||
local serial_no = UInt64(buffer(12, 4):uint(), buffer(8, 4):uint())
|
|
||||||
local serial_suffix = buffer(16, 5):string()
|
|
||||||
subtree:add(ilnk_proto.fields.serial, buffer(4, len:uint()-3), serial_prefix..serial_no..serial_suffix)
|
|
||||||
end
|
|
||||||
if packetname == "DrwAck" then
|
if packetname == "DrwAck" then
|
||||||
subtree:add(ilnk_proto.fields.len, buffer(2, 2))
|
subtree:add(my_protocol.fields.len, buffer(2, 2))
|
||||||
subtree:add(ilnk_proto.fields.m_type, buffer(4, 1))
|
subtree:add(my_protocol.fields.m_type, buffer(4, 1))
|
||||||
subtree:add(ilnk_proto.fields.m_stream_id, buffer(5, 1))
|
subtree:add(my_protocol.fields.m_stream_id, buffer(5, 1))
|
||||||
subtree:add(ilnk_proto.fields.elem_count, buffer(6, 2))
|
subtree:add(my_protocol.fields.elem_count, buffer(6, 2))
|
||||||
|
|
||||||
end
|
end
|
||||||
if packetname == "Drw" then
|
if packetname == "Drw" then
|
||||||
local b_pkt_len = buffer(2, 2)
|
subtree:add(my_protocol.fields.len, buffer(2, 2))
|
||||||
local pkt_len = b_pkt_len:uint()
|
subtree:add(my_protocol.fields.m_type, buffer(4, 1))
|
||||||
local is_data_packet = buffer(5, 1):uint() == 1
|
subtree:add(my_protocol.fields.m_stream_id, buffer(5, 1))
|
||||||
subtree:add(ilnk_proto.fields.len, b_pkt_len)
|
subtree:add_le(my_protocol.fields.start, buffer(8, 2))
|
||||||
subtree:add(ilnk_proto.fields.m_type, buffer(4, 1))
|
subtree:add_le(my_protocol.fields.cmd, buffer(0xa, 2))
|
||||||
subtree:add(ilnk_proto.fields.m_stream_id, buffer(5, 1))
|
|
||||||
if pkt_len < 12 then
|
|
||||||
subtree:add(ilnk_proto.fields.warning, "Short read"):set_generated()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
subtree:add(ilnk_proto.fields.pkt_seq, buffer(6, 2))
|
|
||||||
local b_payload_len = buffer(0xc, 2)
|
|
||||||
local payload_len = buffer(0xc, 2):le_uint()
|
local payload_len = buffer(0xc, 2):le_uint()
|
||||||
if payload_len > pkt_len then
|
subtree:add_le(my_protocol.fields.cmd_payload_len, buffer(0xc, 2))
|
||||||
payload_len = pkt_len - 12
|
|
||||||
end
|
|
||||||
|
|
||||||
if not is_data_packet then
|
subtree:add(my_protocol.fields.encrypted, payload_len >= 5):set_generated()
|
||||||
subtree:add_le(ilnk_proto.fields.start, buffer(8, 2))
|
if buffer(0xb, 1):uint() % 2 == 1 then
|
||||||
subtree:add_le(ilnk_proto.fields.cmd, buffer(0xa, 2))
|
cmdtype = "ack"
|
||||||
local cmdname = control_lut[buffer(0xa, 2):le_uint()] or "UNK"
|
|
||||||
pinfo.cols.info:set(cmdname)
|
|
||||||
subtree:add(ilnk_proto.fields.cmd_name, cmdname):set_generated()
|
|
||||||
subtree:add_le(ilnk_proto.fields.cmd_payload_len, b_payload_len)
|
|
||||||
subtree:add_le(ilnk_proto.fields.cmd_dest, buffer(0xe, 2))
|
|
||||||
-- inline value for short-payload bytes
|
|
||||||
subtree:add(ilnk_proto.fields.auth_token, buffer(0x10, 4))
|
|
||||||
|
|
||||||
if buffer(0xb, 1):uint() % 2 == 1 then
|
|
||||||
cmdtype = "ack"
|
|
||||||
else
|
|
||||||
cmdtype = "cmd"
|
|
||||||
end
|
|
||||||
subtree:add(ilnk_proto.fields.cmd_type, cmdtype):set_generated()
|
|
||||||
subtree:add(ilnk_proto.fields.encrypted, payload_len >= 5):set_generated()
|
|
||||||
|
|
||||||
if payload_len >= 5 then
|
|
||||||
local payload = buffer(0x14, payload_len - 4)
|
|
||||||
local dec_payload = ByteArray.new()
|
|
||||||
dec_payload:set_size(payload_len - 4)
|
|
||||||
for i=4,payload_len-5 do -- inclusive upper range
|
|
||||||
local v = buffer(0x14 + i-4, 1):uint()
|
|
||||||
if (v % 2) == 0 then
|
|
||||||
v = v + 1
|
|
||||||
else
|
|
||||||
v = v - 1
|
|
||||||
end
|
|
||||||
dec_payload:set_index(i, v)
|
|
||||||
end
|
|
||||||
for i=0,3 do
|
|
||||||
local v = buffer(pkt_len+i, 1):uint()
|
|
||||||
if v % 2 == 0 then
|
|
||||||
v = v + 1
|
|
||||||
else
|
|
||||||
v = v - 1
|
|
||||||
end
|
|
||||||
dec_payload:set_index(i, v)
|
|
||||||
end
|
|
||||||
local dec_tvb = ByteArray.tvb(dec_payload, "Decrypted payload")
|
|
||||||
subtree:add(ilnk_proto.fields.decrypted_data, dec_tvb:range(0, payload_len -4) ):set_generated()
|
|
||||||
|
|
||||||
local payload_tvb = ByteArray.tvb(buffer(0x14, payload_len -4):bytes(), "CMD Payload")
|
|
||||||
subtree:add(ilnk_proto.fields.cmd_payload, payload_tvb:range(0, payload_len -4))
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
local payload_type
|
cmdtype = "cmd"
|
||||||
local payload_subtype
|
|
||||||
local payload_tvb = ByteArray.tvb(buffer(8, packet_length-8):bytes(), "Data Payload")
|
|
||||||
if payload_tvb:range(0, 4):uint() == 0xffd8ffdb then
|
|
||||||
payload_subtype = "new frame"
|
|
||||||
-- start of new frame
|
|
||||||
end
|
|
||||||
if payload_tvb:range(0, 4):uint() == 0x55aa15a8 then
|
|
||||||
payload_type = "audio"
|
|
||||||
subtree:add(ilnk_proto.fields.audio_header, buffer(8, 32))
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_type, buffer(12, 1))
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_streamid, buffer(13, 1))
|
|
||||||
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_frameno, buffer(20, 4), buffer(20, 4):le_uint())
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_len, buffer(24, 4), buffer(24, 4):le_uint())
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_ver, buffer(28, 1), buffer(28, 1):le_uint())
|
|
||||||
subtree:add(ilnk_proto.fields.hdr_res, buffer(29, 1), buffer(29, 1):le_uint())
|
|
||||||
|
|
||||||
subtree:add(ilnk_proto.fields.payload_len, buffer(24, 4), buffer(24, 4):le_uint())
|
|
||||||
subtree:add(ilnk_proto.fields.frame_no, buffer(20, 4), buffer(20, 4):le_uint())
|
|
||||||
if payload_tvb:range(4, 1):uint() == 0x06 then
|
|
||||||
payload_subtype = "audio data"
|
|
||||||
elseif payload_tvb:range(4, 1):uint() == 0x03 then
|
|
||||||
payload_subtype = "maybe audio metadata"
|
|
||||||
else
|
|
||||||
payload_subtype = "REALLY not sure audio data"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
payload_type = "jpeg"
|
|
||||||
payload_subtype = "jpeg continuation"
|
|
||||||
end
|
|
||||||
subtree:add(ilnk_proto.fields.payload_type, buffer(8, 4), payload_type)
|
|
||||||
subtree:add(ilnk_proto.fields.payload_subtype, buffer(12, 1), payload_subtype)
|
|
||||||
subtree:add(ilnk_proto.fields.data_payload, payload_tvb:range(0, packet_length-8))
|
|
||||||
end
|
end
|
||||||
|
subtree:add(my_protocol.fields.cmd_type, cmdtype):set_generated()
|
||||||
|
|
||||||
|
subtree:add_le(my_protocol.fields.cmd_dest, buffer(0xe, 2))
|
||||||
|
subtree:add(my_protocol.fields.cmd_payload, buffer(0x10, payload_len))
|
||||||
|
-- subtree:add(my_protocol.fields.cmd, buffer(0xc, 2))
|
||||||
|
|
||||||
end
|
end
|
||||||
-- AvcLIB = src/IpcSession.cpp, line 1113, CmdSndProc:BATC609531EXLVS[0:0:10] now CmdSend[start=a11,cmd=1032,len=4,dest=0]=12
|
-- AvcLIB = src/IpcSession.cpp, line 1113, CmdSndProc:BATC609531EXLVS[0:0:10] now CmdSend[start=a11,cmd=1032,len=4,dest=0]=12
|
||||||
-- UDP PKT SEND Drw (0xf1d0)
|
-- UDP PKT SEND Drw (0xf1d0)
|
||||||
@@ -505,18 +94,7 @@ function ilnk_proto.dissector(buffer, pinfo, tree)
|
|||||||
-- 00000000 f1 d0 00 10 d1 00 00 04 11 0a 32 10 04 00 00 00 ..........2.....
|
-- 00000000 f1 d0 00 10 d1 00 00 04 11 0a 32 10 04 00 00 00 ..........2.....
|
||||||
-- 00000010 50 70 77 35 Ppw5
|
-- 00000010 50 70 77 35 Ppw5
|
||||||
|
|
||||||
-- subtree:add(ilnk_proto.fields.payload, buffer(2, packet_length-2))
|
subtree:add(my_protocol.fields.payload, buffer(2, packet_length-2))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function heuristic_checker(buffer, pinfo, tree)
|
udp_table = DissectorTable.get("udp.port"):add(49512, my_protocol)
|
||||||
length = buffer:len()
|
|
||||||
if length < 2 then return false end
|
|
||||||
local packetname = lut[buffer(0, 2):uint()]
|
|
||||||
if packetname ~= nil then
|
|
||||||
ilnk_proto.dissector(buffer, pinfo, tree)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
udp_table2 = DissectorTable.get("udp.port"):add(32108, ilnk_proto)
|
|
||||||
h = ilnk_proto:register_heuristic("udp", heuristic_checker)
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
const KEY_TABLE = new Uint8Array([
|
|
||||||
0x7c, 0x9c, 0xe8, 0x4a, 0x13, 0xde, 0xdc, 0xb2, 0x2f, 0x21, 0x23, 0xe4, 0x30, 0x7b, 0x3d, 0x8c, 0xbc, 0x0b, 0x27,
|
|
||||||
0x0c, 0x3c, 0xf7, 0x9a, 0xe7, 0x08, 0x71, 0x96, 0x00, 0x97, 0x85, 0xef, 0xc1, 0x1f, 0xc4, 0xdb, 0xa1, 0xc2, 0xeb,
|
|
||||||
0xd9, 0x01, 0xfa, 0xba, 0x3b, 0x05, 0xb8, 0x15, 0x87, 0x83, 0x28, 0x72, 0xd1, 0x8b, 0x5a, 0xd6, 0xda, 0x93, 0x58,
|
|
||||||
0xfe, 0xaa, 0xcc, 0x6e, 0x1b, 0xf0, 0xa3, 0x88, 0xab, 0x43, 0xc0, 0x0d, 0xb5, 0x45, 0x38, 0x4f, 0x50, 0x22, 0x66,
|
|
||||||
0x20, 0x7f, 0x07, 0x5b, 0x14, 0x98, 0x1d, 0x9b, 0xa7, 0x2a, 0xb9, 0xa8, 0xcb, 0xf1, 0xfc, 0x49, 0x47, 0x06, 0x3e,
|
|
||||||
0xb1, 0x0e, 0x04, 0x3a, 0x94, 0x5e, 0xee, 0x54, 0x11, 0x34, 0xdd, 0x4d, 0xf9, 0xec, 0xc7, 0xc9, 0xe3, 0x78, 0x1a,
|
|
||||||
0x6f, 0x70, 0x6b, 0xa4, 0xbd, 0xa9, 0x5d, 0xd5, 0xf8, 0xe5, 0xbb, 0x26, 0xaf, 0x42, 0x37, 0xd8, 0xe1, 0x02, 0x0a,
|
|
||||||
0xae, 0x5f, 0x1c, 0xc5, 0x73, 0x09, 0x4e, 0x69, 0x24, 0x90, 0x6d, 0x12, 0xb3, 0x19, 0xad, 0x74, 0x8a, 0x29, 0x40,
|
|
||||||
0xf5, 0x2d, 0xbe, 0xa5, 0x59, 0xe0, 0xf4, 0x79, 0xd2, 0x4b, 0xce, 0x89, 0x82, 0x48, 0x84, 0x25, 0xc6, 0x91, 0x2b,
|
|
||||||
0xa2, 0xfb, 0x8f, 0xe9, 0xa6, 0xb0, 0x9e, 0x3f, 0x65, 0xf6, 0x03, 0x31, 0x2e, 0xac, 0x0f, 0x95, 0x2c, 0x5c, 0xed,
|
|
||||||
0x39, 0xb7, 0x33, 0x6c, 0x56, 0x7e, 0xb4, 0xa0, 0xfd, 0x7a, 0x81, 0x53, 0x51, 0x86, 0x8d, 0x9f, 0x77, 0xff, 0x6a,
|
|
||||||
0x80, 0xdf, 0xe2, 0xbf, 0x10, 0xd7, 0x75, 0x64, 0x57, 0x76, 0xf3, 0x55, 0xcd, 0xd0, 0xc8, 0x18, 0xe6, 0x36, 0x41,
|
|
||||||
0x62, 0xcf, 0x99, 0xf2, 0x32, 0x4c, 0x67, 0x60, 0x61, 0x92, 0xca, 0xd3, 0xea, 0x63, 0x7d, 0x16, 0xb6, 0x8e, 0xd4,
|
|
||||||
0x68, 0x35, 0xc3, 0x52, 0x9d, 0x46, 0x44, 0x1e, 0x17,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ENC_KEY = new Uint8Array([0x69, 0x97, 0xcc, 0x19]);
|
|
||||||
|
|
||||||
export const decode = (dv: DataView): DataView => {
|
|
||||||
let prevByte = 0;
|
|
||||||
let buf = new Uint8Array(dv.byteLength);
|
|
||||||
for (let i = 0; i < dv.byteLength; i++) {
|
|
||||||
const index = (ENC_KEY[prevByte & 0x03] + prevByte) & 0xff;
|
|
||||||
const origByte = dv.getUint8(i);
|
|
||||||
buf[i] = origByte ^ KEY_TABLE[index];
|
|
||||||
prevByte = origByte;
|
|
||||||
}
|
|
||||||
return new DataView(buf.buffer);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const encode = (dv: DataView): DataView => {
|
|
||||||
let prevByte = 0;
|
|
||||||
let buf = new Uint8Array(dv.byteLength);
|
|
||||||
for (let i = 0; i < dv.byteLength; i++) {
|
|
||||||
const index = (ENC_KEY[prevByte & 0x03] + prevByte) & 0xff;
|
|
||||||
buf[i] = dv.getUint8(i) ^ KEY_TABLE[index];
|
|
||||||
prevByte = buf[i];
|
|
||||||
}
|
|
||||||
return new DataView(buf.buffer);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Create a minimal EXIF segment with orientation
|
|
||||||
export const createExifOrientation = (orientation: number) => {
|
|
||||||
const tiffHeader = Buffer.from("49492A0008000000", "hex");
|
|
||||||
const ifdEntry = Buffer.concat([
|
|
||||||
Buffer.from("0100", "hex"), // Number of IFD entries
|
|
||||||
Buffer.from("1201030001000000", "hex"), // Tag, Type, Count
|
|
||||||
Buffer.from(orientation.toString(16).padStart(2, "0"), "hex"), // Orientation value
|
|
||||||
Buffer.from("0000", "hex"), // No more IFDs
|
|
||||||
Buffer.from("0000000000", "hex"), // padding??
|
|
||||||
]);
|
|
||||||
|
|
||||||
const exifData = Buffer.concat([Buffer.from("457869660000", "hex"), tiffHeader, ifdEntry]);
|
|
||||||
const segmentLength = Buffer.from([(exifData.length + 2) >> 8, (exifData.length + 2) & 0xff]);
|
|
||||||
const exifHeader = Buffer.concat([Buffer.from("FFE1", "hex"), segmentLength]);
|
|
||||||
|
|
||||||
return Buffer.concat([exifHeader, exifData]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addExifToJpeg = (jpegData: Buffer, exifSegment: Buffer) => {
|
|
||||||
// Check for existing EXIF (simplified check)
|
|
||||||
if (jpegData.includes(Buffer.from("FFE1", "hex"))) {
|
|
||||||
throw new Error("JPEG already contains EXIF segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
const soiEnd = 2; // After FFD8
|
|
||||||
const modifiedJpeg = Buffer.concat([jpegData.subarray(0, soiEnd), exifSegment, jpegData.subarray(soiEnd)]);
|
|
||||||
return modifiedJpeg;
|
|
||||||
};
|
|
||||||
+9
-169
@@ -1,10 +1,9 @@
|
|||||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
import { replaceFunctions, Commands, CommandsByValue } from "./func_replacements.js";
|
||||||
import { replaceFunctions, XqBytesDec } from "./func_replacements.js";
|
|
||||||
import { placeholderTypes, sprintf, u16_swap } from "./utils.js";
|
import { placeholderTypes, sprintf, u16_swap } from "./utils.js";
|
||||||
|
|
||||||
const hook_fn = (name_in_elf, enter, leave) => {
|
const hook_fn = (name_in_elf, enter, leave) => {
|
||||||
var symbol_addr = DebugSymbol.fromName(name_in_elf).address;
|
var symbol_addr = DebugSymbol.fromName(name_in_elf).address;
|
||||||
console.log(`${name_in_elf} addr is: ${symbol_addr}`);
|
console.log(`${name_in_elf} addr is: ${symbol_addr}, this is ${this}`);
|
||||||
Interceptor.attach(symbol_addr, {
|
Interceptor.attach(symbol_addr, {
|
||||||
onEnter: enter,
|
onEnter: enter,
|
||||||
onLeave: leave,
|
onLeave: leave,
|
||||||
@@ -12,18 +11,6 @@ const hook_fn = (name_in_elf, enter, leave) => {
|
|||||||
console.log(`Hooked ${name_in_elf}`);
|
console.log(`Hooked ${name_in_elf}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const global = (name_in_elf) => {
|
|
||||||
//var symbol_addr = DebugSymbol.fromName(name_in_elf).address;
|
|
||||||
// Module.enumerateSections("libvdp.so").forEach((s) => console.log(JSON.stringify(s, null, 2)));
|
|
||||||
// "name": ".bss",
|
|
||||||
//Module.enumerateSymbols("libvdp.so").forEach((s) => console.log(JSON.stringify(s, null, 2)));
|
|
||||||
const syms = Module.enumerateSymbols("libvdp.so").filter((s) => s.name == name_in_elf);
|
|
||||||
var symbol_addr = syms[0].address;
|
|
||||||
console.log(`global ${name_in_elf} addr is: ${symbol_addr}`);
|
|
||||||
if (symbol_addr == 0x0 || symbol_addr == null) throw new Error(`can't read ${name_in_elf}`);
|
|
||||||
return new NativePointer(symbol_addr);
|
|
||||||
};
|
|
||||||
|
|
||||||
function hook_export_fn(name_in_elf, enter, leave) {
|
function hook_export_fn(name_in_elf, enter, leave) {
|
||||||
var symbol_addr = Module.findExportByName("libvdp.so", name_in_elf);
|
var symbol_addr = Module.findExportByName("libvdp.so", name_in_elf);
|
||||||
console.log(`${name_in_elf} addr is: ${symbol_addr}`);
|
console.log(`${name_in_elf} addr is: ${symbol_addr}`);
|
||||||
@@ -53,54 +40,6 @@ const hook_p2p_read = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hook_Log = () => {
|
|
||||||
Java.perform(function () {
|
|
||||||
var Log = Java.use("android.util.Log");
|
|
||||||
Log.d.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
|
||||||
console.log("The application reports Log.d(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.d(a, b, c);
|
|
||||||
};
|
|
||||||
Log.v.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
|
||||||
console.log("The application reports Log.v(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.v(a, b, c);
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.i.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
|
||||||
console.log("The application reports Log.i(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.i(a, b, c);
|
|
||||||
};
|
|
||||||
Log.e.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
|
||||||
console.log("The application reports Log.e(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.e(a, b, c);
|
|
||||||
};
|
|
||||||
Log.w.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
|
||||||
console.log("The application reports Log.w(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.w(a, b, c);
|
|
||||||
};
|
|
||||||
Log.d.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
|
||||||
console.log("The application reports Log.d(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.d(a, b);
|
|
||||||
};
|
|
||||||
Log.v.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
|
||||||
console.log("The application reports Log.v(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.v(a, b);
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.i.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
|
||||||
console.log("The application reports Log.i(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.i(a, b);
|
|
||||||
};
|
|
||||||
Log.e.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
|
||||||
console.log("The application reports Log.e(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.e(a, b);
|
|
||||||
};
|
|
||||||
Log.w.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
|
||||||
console.log("The application reports Log.w(" + a.toString() + ", " + b.toString() + ")");
|
|
||||||
return this.w(a, b);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const hook___android_log_print = () => {
|
const hook___android_log_print = () => {
|
||||||
const sym = "__android_log_print";
|
const sym = "__android_log_print";
|
||||||
hook_fn(
|
hook_fn(
|
||||||
@@ -124,100 +63,21 @@ const hook___android_log_print = () => {
|
|||||||
|
|
||||||
const values = types.map((t, idx) => o[t](args[idx + 3]));
|
const values = types.map((t, idx) => o[t](args[idx + 3]));
|
||||||
const newStr = sprintf(fmt, values);
|
const newStr = sprintf(fmt, values);
|
||||||
console.log(_tag, newStr.trim());
|
console.log(_tag, newStr);
|
||||||
},
|
},
|
||||||
() => {},
|
() => {},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hook_udpsend = () => {
|
const hook_udpsend = () => {
|
||||||
const codeTable = global("codeTable");
|
|
||||||
/*
|
|
||||||
* WanAddrGet
|
|
||||||
139.155.68.77
|
|
||||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=139.155.68.77
|
|
||||||
WanAddrGet
|
|
||||||
119.45.114.92
|
|
||||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=119.45.114.92
|
|
||||||
WanAddrGet
|
|
||||||
162.62.63.154
|
|
||||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=162.62.63.154
|
|
||||||
WanAddrGet
|
|
||||||
3.132.215.40
|
|
||||||
*/
|
|
||||||
let x = {};
|
|
||||||
hook_fn(
|
|
||||||
"XQ_WanAddrGet",
|
|
||||||
(args) => {
|
|
||||||
console.log("WanAddrGet");
|
|
||||||
console.log(args[0].readCString());
|
|
||||||
x.ret = args[2];
|
|
||||||
},
|
|
||||||
(retval) => {
|
|
||||||
console.log("WanAddrGet RET");
|
|
||||||
console.log(x.ret.readCString());
|
|
||||||
//console.log("on wanaddrget, ret");
|
|
||||||
//console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let d = {};
|
|
||||||
hook_fn(
|
|
||||||
"XqStrDec",
|
|
||||||
(args) => {
|
|
||||||
console.log("XqStrDec param1", args[0].readCString());
|
|
||||||
console.log("codetable", codeTable.readCString());
|
|
||||||
console.log("codetable hexarr\n", hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
(retval) => {
|
|
||||||
console.log("XqStrDec RET");
|
|
||||||
console.log(retval.readCString());
|
|
||||||
//console.log("on wanaddrget, ret");
|
|
||||||
//console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
hook_fn(
|
|
||||||
"XqCodeTableInit",
|
|
||||||
(args) => {
|
|
||||||
console.log("on codetableinit, it was");
|
|
||||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
(retval) => {
|
|
||||||
console.log("on codetableinit, ret");
|
|
||||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
hook_fn(
|
|
||||||
"XQ_InitEncryption",
|
|
||||||
(args) => {
|
|
||||||
console.log("on initenc, it was");
|
|
||||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
(retval) => {
|
|
||||||
console.log("on initenc, ret");
|
|
||||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
hook_fn(
|
hook_fn(
|
||||||
"XQ_UdpPktSend",
|
"XQ_UdpPktSend",
|
||||||
(args) => {
|
(args) => {
|
||||||
const data = args[0].readByteArray(args[1].toInt32());
|
const data = args[0].readByteArray(args[1].toInt32());
|
||||||
const cmd = u16_swap(args[0].readU16());
|
const cmd = u16_swap(args[0].readU16());
|
||||||
const name = CommandsByValue[cmd];
|
const name = CommandsByValue[cmd];
|
||||||
if (name != "P2PAliveAck") {
|
console.log(`UDP PKT SEND ${name} (0x${cmd.toString(16)})`);
|
||||||
if (name != "LanSearch" && name != "LanSearchExt") {
|
console.log(data);
|
||||||
let cmd = "";
|
|
||||||
if (name == "Drw") {
|
|
||||||
cmd = args[0].add(0xa).readU16().toString(16);
|
|
||||||
}
|
|
||||||
let tstamp = Date.now();
|
|
||||||
console.log(`${tstamp} UDP PKT SEND ${name} (0x${cmd.toString(16)}) - CMD? ${cmd}`);
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("> P2PAliveAck");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(retval) => {},
|
(retval) => {},
|
||||||
);
|
);
|
||||||
@@ -232,32 +92,14 @@ WanAddrGet
|
|||||||
(retval) => {
|
(retval) => {
|
||||||
const data = o.buf.readByteArray(retval.toInt32());
|
const data = o.buf.readByteArray(retval.toInt32());
|
||||||
const cmd = u16_swap(o.buf.readU16());
|
const cmd = u16_swap(o.buf.readU16());
|
||||||
const len = u16_swap(o.buf.add(2).readU16());
|
|
||||||
const name = CommandsByValue[cmd];
|
const name = CommandsByValue[cmd];
|
||||||
const isData = o.buf.add(5).readU8();
|
console.log(`UDP PKT RECV, cmd=${name}, 0x${cmd.toString(16)}, ret=${retval}`);
|
||||||
if (name != "P2PAlive") {
|
console.log(data);
|
||||||
let tstamp = Date.now();
|
if (cmd == Commands.Drw) {
|
||||||
console.log(`${tstamp} UDP PKT RECV, len=${len}, cmd=${name}, 0x${cmd.toString(16)}, ret=${retval}`);
|
|
||||||
if (cmd != Commands.Drw || (cmd == Commands.Drw && !isData)) {
|
|
||||||
// dont log data payloads
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("< P2PAlive");
|
|
||||||
}
|
|
||||||
if (cmd == Commands.Drw && !isData) {
|
|
||||||
if (len > 0x18) {
|
|
||||||
// pos(0xa11) == 8 + 0xc == 0x14 == 20
|
|
||||||
const under = data.unwrap().add(0x14); //, len - 0x20;
|
|
||||||
XqBytesDec(under, len - 0x10, 4);
|
|
||||||
console.log("decrypted data");
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
|
||||||
let s = {};
|
let s = {};
|
||||||
hook_fn(
|
hook_fn(
|
||||||
"PktSeq_seqGet",
|
"PktSeq_seqGet",
|
||||||
@@ -270,7 +112,6 @@ WanAddrGet
|
|||||||
console.log(data);
|
console.log(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
hook_fn(
|
hook_fn(
|
||||||
@@ -372,11 +213,10 @@ function doHooks() {
|
|||||||
var libnative_addr = Module.findBaseAddress("libvdp.so");
|
var libnative_addr = Module.findBaseAddress("libvdp.so");
|
||||||
if (libnative_addr) {
|
if (libnative_addr) {
|
||||||
hook___android_log_print();
|
hook___android_log_print();
|
||||||
hook_Log();
|
|
||||||
// hook_in_out_buf("create_LstReq", 0x1c, 0x1c);
|
// hook_in_out_buf("create_LstReq", 0x1c, 0x1c);
|
||||||
//hook_in_out_buf("create_P2pRdy", 0x1c, 0x1c);
|
//hook_in_out_buf("create_P2pRdy", 0x1c, 0x1c);
|
||||||
hook_udpsend();
|
hook_udpsend();
|
||||||
//doReplaceFunctions();
|
doReplaceFunctions();
|
||||||
|
|
||||||
// hook_p2p_read();
|
// hook_p2p_read();
|
||||||
// hook_pack_P2pId();
|
// hook_pack_P2pId();
|
||||||
|
|||||||
+32
-44
@@ -1,5 +1,5 @@
|
|||||||
|
import { swap_endianness_u32, swap_endianness_u16, u16_swap } from "./utils.js";
|
||||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||||
import { swap_endianness_u16, swap_endianness_u32, u16_swap } from "./utils.js";
|
|
||||||
|
|
||||||
const writeCommand2 = (command, buf) => {
|
const writeCommand2 = (command, buf) => {
|
||||||
buf.writeByteArray([(command & 0xff00) >> 8, command & 0xff]);
|
buf.writeByteArray([(command & 0xff00) >> 8, command & 0xff]);
|
||||||
@@ -12,7 +12,6 @@ export const XqBytesDec = (inoutbuf, buflen, rotate) => {
|
|||||||
// only rotation is different
|
// only rotation is different
|
||||||
let new_buf = new Uint8Array(buflen);
|
let new_buf = new Uint8Array(buflen);
|
||||||
new_buf.fill(0x1);
|
new_buf.fill(0x1);
|
||||||
|
|
||||||
for (let i = 0; i < buflen; i++) {
|
for (let i = 0; i < buflen; i++) {
|
||||||
let b = inoutbuf.add(i).readU8();
|
let b = inoutbuf.add(i).readU8();
|
||||||
if ((b & 1) != 0) {
|
if ((b & 1) != 0) {
|
||||||
@@ -207,26 +206,16 @@ const dbg_create_Drw = (og_func) => {
|
|||||||
};
|
};
|
||||||
const dbg__ZN12CPPPPChannel10CmdSndPushEiiPci = (og_func) => {
|
const dbg__ZN12CPPPPChannel10CmdSndPushEiiPci = (og_func) => {
|
||||||
const CmdSndPush = (_this, dest, cmdtype, idk, cmdlen) => {
|
const CmdSndPush = (_this, dest, cmdtype, idk, cmdlen) => {
|
||||||
console.log("CmdSndPushPre", _this, dest.toString(16), cmdtype.toString(16), idk, cmdlen);
|
console.log("CmdSndPush", _this, dest.toString(16), cmdtype.toString(16), idk, cmdlen);
|
||||||
// CmdSndPush 0x1020 ret: 172
|
//CmdSndPush 0x1020 ret: 172
|
||||||
// CmdSndPush 0x1040 ret: 92
|
//CmdSndPush 0x1040 ret: 92
|
||||||
// CmdSndPush 0x50ff ret: 564
|
//CmdSndPush 0x50ff ret: 564
|
||||||
// CmdSndPush 0x1008 ret: 12
|
//CmdSndPush 0x1008 ret: 12
|
||||||
//
|
//
|
||||||
//if (cmdtype == 0x1020) return 172; // login
|
//if (cmdtype == 0x1020) return 172; // mask
|
||||||
|
if (cmdtype == 0x1040) return 92; // mask
|
||||||
// maybe these brick it
|
if (cmdtype == 0x50ff) return 564; // mask
|
||||||
//if (cmdtype == 0x1040) return 92; // mask
|
if (cmdtype == 0x1008) return 12; // mask
|
||||||
//if (cmdtype == 0x50ff) return 564; // mask
|
|
||||||
//if (cmdtype == 0x1008) return 12; // battery + status online?
|
|
||||||
//// new
|
|
||||||
////if (cmdtype == 0x6003) return 12; // wifilist
|
|
||||||
//if (cmdtype == 0x6002) return 12; // wifisettings
|
|
||||||
//if (cmdtype == 0x1031) return 44; // "open settings panel" ??
|
|
||||||
//if (cmdtype == 0x1032) return 12; //idk
|
|
||||||
|
|
||||||
//// mb reboot
|
|
||||||
//if (cmdtype == 0x2005) return 20; //idk
|
|
||||||
|
|
||||||
let ret = og_func(_this, dest, cmdtype, idk, cmdlen);
|
let ret = og_func(_this, dest, cmdtype, idk, cmdlen);
|
||||||
console.log(`CmdSndPush 0x${cmdtype.toString(16)} ret: ${ret}`);
|
console.log(`CmdSndPush 0x${cmdtype.toString(16)} ret: ${ret}`);
|
||||||
@@ -235,36 +224,38 @@ const dbg__ZN12CPPPPChannel10CmdSndPushEiiPci = (og_func) => {
|
|||||||
return CmdSndPush;
|
return CmdSndPush;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbg__Z6NetCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
|
||||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
|
||||||
const NetCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
|
||||||
console.log("NetCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
|
||||||
// if (cmd == 0x0000) return 0;
|
|
||||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
|
||||||
console.log(`NetCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
return NetCmd;
|
|
||||||
};
|
|
||||||
|
|
||||||
const dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
const dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
||||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
||||||
const SystemCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
const AvCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||||
console.log("SystemCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
console.log("SystemCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
||||||
|
if (cmd == 0x3018) return 20; // mask
|
||||||
|
if (cmd == 0x3019) return 12; // mask
|
||||||
|
if (cmd == 0x3005) return 12; // mask
|
||||||
|
if (cmd == 0x3026) return 0; // mask
|
||||||
|
if (cmd == 0x3001) return 12;
|
||||||
|
if (cmd == 0x3003) return 12;
|
||||||
|
|
||||||
|
//if (cmd == 0x3011) return 272; // this is STOP !!
|
||||||
|
//if (cmd == 0x3010) return 272; // borks
|
||||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
||||||
console.log(`SystemCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
console.log(`SystemCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
return SystemCmd;
|
return AvCmd;
|
||||||
};
|
};
|
||||||
const dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
const dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
||||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
||||||
const AvCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
const AvCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||||
console.log("AvCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
console.log("AvCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
||||||
|
if (cmd == 0x3018) return 20; // mask
|
||||||
|
if (cmd == 0x3019) return 12; // mask
|
||||||
|
if (cmd == 0x3005) return 12; // mask
|
||||||
|
if (cmd == 0x3026) return 0; // mask
|
||||||
|
if (cmd == 0x3001) return 12;
|
||||||
|
if (cmd == 0x3003) return 12;
|
||||||
|
|
||||||
// if (cmd == 0x3011) return 272; // this is STOP !!
|
//if (cmd == 0x3011) return 272; // this is STOP !!
|
||||||
// if (cmd == 0x3010) return 272; // borks
|
//if (cmd == 0x3010) return 272; // borks
|
||||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
||||||
console.log(`AvCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
console.log(`AvCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -293,10 +284,10 @@ const dbg_pack_ClntPkt = (og_func) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
P2PAlive: 0xf1e0,
|
P2PAlive: 0xf1e0,
|
||||||
P2PAliveAck: 0xf1e1,
|
P2PAliveAck: 0xf1e1,
|
||||||
P2pRdy: 0xf142, // idk??
|
P2pRdy: 0xf142, // idk??
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fn = packFn[cmd];
|
const fn = packFn[cmd];
|
||||||
if (fn == undefined) {
|
if (fn == undefined) {
|
||||||
@@ -387,7 +378,6 @@ const replace_func = (stub, ret, args) => {
|
|||||||
// CSession_CtrlPkt_Proc(struct, *cmd) == control?
|
// CSession_CtrlPkt_Proc(struct, *cmd) == control?
|
||||||
export const replaceFunctions = () => {
|
export const replaceFunctions = () => {
|
||||||
const replacements = [
|
const replacements = [
|
||||||
/*
|
|
||||||
[create_P2pAlive, "uint8", ["pointer"]],
|
[create_P2pAlive, "uint8", ["pointer"]],
|
||||||
[create_P2pAliveAck, "uint8", ["pointer"]],
|
[create_P2pAliveAck, "uint8", ["pointer"]],
|
||||||
[create_LanSearch, "uint8", ["pointer"]],
|
[create_LanSearch, "uint8", ["pointer"]],
|
||||||
@@ -407,8 +397,6 @@ export const replaceFunctions = () => {
|
|||||||
[dbg_pack_ClntPkt, "uint32", ["uint32", "pointer", "pointer"]],
|
[dbg_pack_ClntPkt, "uint32", ["uint32", "pointer", "pointer"]],
|
||||||
[dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
[dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
||||||
[dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
[dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
||||||
*/
|
|
||||||
[dbg__Z6NetCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
|
||||||
[dbg__ZN12CPPPPChannel10CmdSndPushEiiPci, "uint64", ["pointer", "uint32", "uint32", "pointer", "uint32"]],
|
[dbg__ZN12CPPPPChannel10CmdSndPushEiiPci, "uint64", ["pointer", "uint32", "uint32", "pointer", "uint32"]],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+55
-290
@@ -1,14 +1,15 @@
|
|||||||
import { Commands, CommandsByValue, ControlCommands } from "./datatypes.js";
|
import { Commands, CommandsByValue, ControlCommands } from "./datatypes.js";
|
||||||
import { XqBytesDec } from "./func_replacements.js";
|
import { create_P2pRdy, SendStartVideo, SendUsrChk } from "./impl.js";
|
||||||
import { create_P2pRdy, DevSerial, SendListWifi, SendUsrChk } from "./impl.js";
|
import { Session } from "./server.js";
|
||||||
import { logger } from "./logger.js";
|
import { u16_swap } from "./utils.js";
|
||||||
import { Session } from "./session.js";
|
import { hexdump } from "./hexdump.js";
|
||||||
import { config } from "./settings.js";
|
|
||||||
|
|
||||||
export const notImpl = (session: Session, dv: DataView) => {
|
let curImage = null;
|
||||||
|
|
||||||
|
export const notImpl = (_: Session, dv: DataView) => {
|
||||||
const raw = dv.readU16();
|
const raw = dv.readU16();
|
||||||
const cmd = CommandsByValue[raw];
|
const cmd = CommandsByValue[raw];
|
||||||
logger.debug(`^^ ${cmd} (${raw.toString(16)}) and it's not implemented yet`);
|
console.log(`^^ ${cmd} (${raw.toString(16)}) and it's not implemented yet`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const noop = (_: Session, __: DataView) => {};
|
export const noop = (_: Session, __: DataView) => {};
|
||||||
@@ -23,296 +24,72 @@ export const handle_P2PAlive = (session: Session, _: DataView) => {
|
|||||||
const b = create_P2pAliveAck();
|
const b = create_P2pAliveAck();
|
||||||
session.send(b);
|
session.send(b);
|
||||||
};
|
};
|
||||||
|
export const handle_PunchPkt = (session: Session, dv: DataView) => {
|
||||||
export const handle_P2PRdy = (session: Session, _: DataView) => {
|
const punchCmd = dv.readU16();
|
||||||
// TODO - config
|
const len = dv.add(2).readU16();
|
||||||
const b = SendUsrChk(session, "admin", "admin");
|
const prefix = dv.add(4).readString(4);
|
||||||
session.send(b);
|
const serial = dv.add(8).readU64().toString();
|
||||||
|
const suffix = dv.add(16).readString(4);
|
||||||
|
// f141 20 BATC 609531 EXLV
|
||||||
|
session.eventEmitter.emit("connect", prefix.toString() + serial + suffix.toString());
|
||||||
|
session.send(create_P2pRdy(dv.add(4).readByteArray(len)));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeP2pRdy = (dev: DevSerial): DataView => {
|
export const createResponseForControlCommand = (session: Session, dv: DataView): DataView | null => {
|
||||||
const outbuf = new DataView(new Uint8Array(0x14).buffer); // 8 = serial u64
|
|
||||||
// The protocol seems to expect 4 bytes -- check the regression test
|
|
||||||
// `replies properly to PunchPkt with 3-letters-long prefix` for a case with a
|
|
||||||
// real device
|
|
||||||
const devPrefixLength = 4;
|
|
||||||
outbuf.add(0).writeString(dev.prefix);
|
|
||||||
outbuf.add(4).writeU64(dev.serialU64);
|
|
||||||
outbuf.add(8 + devPrefixLength).writeString(dev.suffix);
|
|
||||||
return create_P2pRdy(outbuf);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const swVerToString = (swver: number): string => {
|
|
||||||
return (
|
|
||||||
((swver >> 24) & 255).toString() +
|
|
||||||
"." +
|
|
||||||
((swver >> 16) & 255).toString() +
|
|
||||||
"." +
|
|
||||||
((swver >> 8) & 255).toString() +
|
|
||||||
"." +
|
|
||||||
(swver & 255).toString()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export type DevStatus = {
|
|
||||||
charging: boolean;
|
|
||||||
battery_mV: number;
|
|
||||||
dbm: number;
|
|
||||||
swver: string;
|
|
||||||
};
|
|
||||||
export const parseDevStatusAck = (dv: DataView): DevStatus => {
|
|
||||||
const charging = dv.add(0x28).readU32LE() & 1; // 0x14000101 v 0x14000100
|
|
||||||
const power = dv.add(0x18).readU16LE(); // '3730' or '3765', milliVolts
|
|
||||||
const dbm = dv.add(0x24).readU8() - 0x100; // 0xbf - 0x100 = -65dbm .. constant??
|
|
||||||
const n_swver = dv.add(0x14).readU32LE();
|
|
||||||
const swver = swVerToString(n_swver);
|
|
||||||
return {
|
|
||||||
charging: charging > 0,
|
|
||||||
battery_mV: power,
|
|
||||||
dbm,
|
|
||||||
swver,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const createResponseForControlCommand = (session: Session, dv: DataView): DataView[] => {
|
|
||||||
const start_type = dv.add(8).readU16(); // 0xa11 on control; data starts here on DATA pkt
|
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 cmd_id = dv.add(10).readU16(); // 0x1120
|
||||||
let payload_len = dv.add(0xc).readU16LE();
|
|
||||||
if (dv.byteLength > 20 && payload_len > dv.byteLength) {
|
|
||||||
logger.warning(`Received a cropped payload: ${payload_len} when packet is ${dv.byteLength}`);
|
|
||||||
payload_len = dv.byteLength - 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_type != 0x110a) {
|
if (start_type != 0x110a) {
|
||||||
logger.error(`Expected start_type to be 0xa11, got 0x${start_type.toString(16)}`);
|
console.error(`Expected start_type to be 0xa11, got 0x${start_type.toString(16)}`);
|
||||||
return [];
|
return;
|
||||||
}
|
|
||||||
const rotate_chr = 4;
|
|
||||||
if (payload_len > rotate_chr) {
|
|
||||||
// 20 = 16 (header) + 4 (??)
|
|
||||||
XqBytesDec(dv.add(20), payload_len - 4, rotate_chr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the first 20 bytes are header
|
if (cmd_id == ControlCommands.ConnectUserAck) {
|
||||||
|
let c = new Uint8Array(dv.add(0x14).readByteArray(4).buffer);
|
||||||
switch (cmd_id) {
|
session.ticket[0] = c[0] % 2 == 0 ? c[0] + 1 : c[0] - 1;
|
||||||
case ControlCommands.ConnectUserAck:
|
session.ticket[1] = c[1] % 2 == 0 ? c[1] + 1 : c[1] - 1;
|
||||||
let c = new Uint8Array(dv.add(0x18).readByteArray(4).buffer);
|
session.ticket[2] = c[2] % 2 == 0 ? c[2] + 1 : c[2] - 1;
|
||||||
session.ticket = [...c];
|
session.ticket[3] = c[3] % 2 == 0 ? c[3] + 1 : c[3] - 1;
|
||||||
session.eventEmitter.emit("login");
|
const buf = SendStartVideo(session);
|
||||||
return [];
|
return buf;
|
||||||
|
|
||||||
case ControlCommands.DevStatusAck:
|
|
||||||
// ParseDevStatus -> offset relevant?
|
|
||||||
const status = parseDevStatusAck(dv);
|
|
||||||
// > -50 = excellent, -50 to -60 good, -60 to -70 fair, <-70 weak
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`Camera ${session.devName}: sw: ${status.swver}, ${status.charging ? "" : "not "}charging, battery at ${
|
|
||||||
status.battery_mV
|
|
||||||
}mV, Wifi ${status.dbm} dBm`,
|
|
||||||
);
|
|
||||||
return [];
|
|
||||||
|
|
||||||
case ControlCommands.WifiSettingsAck:
|
|
||||||
const wifiSettings = {
|
|
||||||
enable: dv.add(0x14).readU32(),
|
|
||||||
status: dv.add(0x18).readU32(),
|
|
||||||
mode: dv.add(0x1c).readU32LE(),
|
|
||||||
channel: dv.add(0x20).readU32(),
|
|
||||||
authtype: dv.add(0x24).readU32(),
|
|
||||||
dhcp: dv.add(0x28).readU32(),
|
|
||||||
ssid: dv.add(0x2c).readString(0x20),
|
|
||||||
psk: dv.add(0x4c).readString(0x80),
|
|
||||||
ip: dv.add(0xcc).readString(0x10),
|
|
||||||
mask: dv.add(0xdc).readString(0x10),
|
|
||||||
gw: dv.add(0xec).readString(0x10),
|
|
||||||
dns1: dv.add(0xfc).readString(0x10),
|
|
||||||
dns2: dv.add(0x10c).readString(0x10),
|
|
||||||
};
|
|
||||||
const buf = SendListWifi(session);
|
|
||||||
logger.info(`Current Wifi settings: ${JSON.stringify(wifiSettings, null, 2)}`);
|
|
||||||
return [buf];
|
|
||||||
|
|
||||||
case ControlCommands.ListWifiAck:
|
|
||||||
if (payload_len == 4) {
|
|
||||||
logger.debug("ListWifi returned []");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const items = parseListWifi(dv);
|
|
||||||
session.eventEmitter.emit("ListWifi", items);
|
|
||||||
return [];
|
|
||||||
case ControlCommands.StartVideoAck:
|
|
||||||
logger.debug("Start video ack");
|
|
||||||
return [];
|
|
||||||
case ControlCommands.VideoParamSetAck:
|
|
||||||
logger.debug("Video param set ack");
|
|
||||||
return [];
|
|
||||||
default:
|
|
||||||
logger.info(`Unhandled control command: 0x${cmd_id.toString(16)}`);
|
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WifiListItem = {
|
|
||||||
ssid: string;
|
|
||||||
mac: string;
|
|
||||||
security: number;
|
|
||||||
dbm0: number;
|
|
||||||
dbm1: number;
|
|
||||||
mode: number;
|
|
||||||
channel: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parseListWifi = (dv: DataView): WifiListItem[] => {
|
|
||||||
let startat = 0x10;
|
|
||||||
const msg_len = 0x5c; // 0x58 + 0x4 of the last u32
|
|
||||||
const msg_count = dv.add(startat).readU32LE();
|
|
||||||
startat += 4;
|
|
||||||
let items = [];
|
|
||||||
for (let i = 0; i < msg_count; i++) {
|
|
||||||
if (startat + msg_len > dv.byteLength) {
|
|
||||||
logger.warning("Wifi listing got cropped");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const macBytes = dv.add(startat + 0x40).readByteArray(6).buffer;
|
|
||||||
const mb = new Uint8Array(macBytes);
|
|
||||||
const wifiListItem = {
|
|
||||||
ssid: dv.add(startat).readString(0x40),
|
|
||||||
mac: [...mb].map((b) => b.toString(16).padStart(2, "0")).join(":"),
|
|
||||||
security: dv.add(startat + 0x48).readU32LE(),
|
|
||||||
dbm0: dv.add(startat + 0x4c).readU32LE(),
|
|
||||||
dbm1: dv.add(startat + 0x50).readU32LE(),
|
|
||||||
mode: dv.add(startat + 0x54).readU32LE(),
|
|
||||||
channel: dv.add(startat + 0x58).readU32LE(),
|
|
||||||
};
|
|
||||||
startat += msg_len;
|
|
||||||
items.push(wifiListItem);
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deal_with_data = (session: Session, dv: DataView) => {
|
const deal_with_data = (session: Session, dv: DataView) => {
|
||||||
const pkt_len = dv.add(2).readU16();
|
const pkt_len = dv.add(2).readU16();
|
||||||
|
// data
|
||||||
// 12 equals start of header (0x8) + header length (0x4)
|
const JPEG_HEADER = [0xff, 0xd8, 0xff, 0xdb];
|
||||||
if (pkt_len < 12) {
|
const AUDIO_HEADER = [0x55, 0xaa, 0x15, 0xa8];
|
||||||
logger.log("trace", "Got a short Drw packet, ignoring");
|
const m_hdr = dv.add(8).readByteArray(4);
|
||||||
return;
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
const FRAME_HEADER = [0x55, 0xaa, 0x15, 0xa8];
|
if (audio) {
|
||||||
const m_hdr = dv.add(8).readByteArray(4);
|
// "stream_head_t->type == 0x06" per pdf
|
||||||
const pkt_id = dv.add(6).readU16();
|
if (dv.add(12).readU8() == 0x06) {
|
||||||
const STREAM_TYPE_AUDIO = 0x06;
|
const audio_len = u16_swap(dv.add(8 + 16).readU16());
|
||||||
const STREAM_TYPE_JPEG = 0x03;
|
|
||||||
|
|
||||||
const startNewFrame = (buf: ArrayBuffer) => {
|
|
||||||
if (session.curImage.length > 0 && !session.frame_is_bad) {
|
|
||||||
session.eventEmitter.emit("frame");
|
|
||||||
}
|
|
||||||
|
|
||||||
session.frame_was_fixed = false;
|
|
||||||
session.frame_is_bad = false;
|
|
||||||
session.curImage = [Buffer.from(buf)];
|
|
||||||
session.rcvSeqId = pkt_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_framed = m_hdr.startsWith(FRAME_HEADER);
|
|
||||||
|
|
||||||
if (is_framed) {
|
|
||||||
const stream_type = dv.add(12).readU8();
|
|
||||||
if (stream_type == STREAM_TYPE_AUDIO) {
|
|
||||||
const audio_len = dv.add(8 + 16).readU16LE();
|
|
||||||
const audio_buf = dv.add(32 + 8).readByteArray(audio_len).buffer; // 8 for pkt header, 32 for `stream_head_t`
|
const audio_buf = dv.add(32 + 8).readByteArray(audio_len).buffer; // 8 for pkt header, 32 for `stream_head_t`
|
||||||
session.eventEmitter.emit("audio", { gap: false, data: Buffer.from(audio_buf) });
|
session.eventEmitter.emit("audio", Buffer.from(audio_buf));
|
||||||
} else if (stream_type == STREAM_TYPE_JPEG) {
|
|
||||||
const to_read = pkt_len - 4 - 32;
|
|
||||||
if (to_read > 0) {
|
|
||||||
// some cameras do not send the data with the frame, but rather as a
|
|
||||||
// followup message skip 8 bytes (drw header) + 32 bytes (data frame)
|
|
||||||
const data = dv.add(32 + 8).readByteArray(to_read);
|
|
||||||
startNewFrame(data.buffer);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`Ignoring data frame with stream type ${stream_type} - not implemented`);
|
|
||||||
// not sure what these are for, there's one per frame. maybe alignment?
|
// not sure what these are for, there's one per frame. maybe alignment?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const JPEG_HEADER = [0xff, 0xd8, 0xff, 0xdb];
|
|
||||||
// a new JPEG image may begin either
|
|
||||||
// - as a frame with stream_type == 0x03
|
|
||||||
// - as unframed data, started by JPEG_HEADER
|
|
||||||
// but for both types of cameras, unframed data which does not start with
|
|
||||||
// JPEG_HEADER are segments of the (potentially already started) JPEG image
|
|
||||||
const data = dv.add(8).readByteArray(pkt_len - 4);
|
const data = dv.add(8).readByteArray(pkt_len - 4);
|
||||||
// this only happens on un-framed-cameras, which start the JPEG image
|
|
||||||
// directly
|
|
||||||
let is_new_image = m_hdr.startsWith(JPEG_HEADER);
|
|
||||||
|
|
||||||
if (is_new_image) {
|
if (is_new_image) {
|
||||||
startNewFrame(data.buffer);
|
if (curImage != null) {
|
||||||
|
session.eventEmitter.emit("frame", curImage);
|
||||||
|
}
|
||||||
|
curImage = Buffer.from(data.buffer);
|
||||||
} else {
|
} else {
|
||||||
if (pkt_id <= session.rcvSeqId) {
|
curImage = Buffer.concat([curImage, Buffer.from(data.buffer)]);
|
||||||
// retransmit
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (session.curImage.length == 0) return; // missed the start-of-frame packet
|
|
||||||
|
|
||||||
let b = Buffer.from(data.buffer);
|
|
||||||
|
|
||||||
if (pkt_id > session.rcvSeqId + 1) {
|
|
||||||
if (!session.frame_is_bad) {
|
|
||||||
session.frame_is_bad = true;
|
|
||||||
logger.debug(`Dropping corrupt frame ${pkt_id}, expected ${session.rcvSeqId + 1}`);
|
|
||||||
}
|
|
||||||
// this should always be enabled but currently it seems to cause more
|
|
||||||
// visual distortion than just missing some frames
|
|
||||||
if (!config.cameras[session.devName]?.fix_packet_loss) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.curImage.length == 1) return; // header does not have markers
|
|
||||||
|
|
||||||
let lastFrameSlice = session.curImage[session.curImage.length - 1];
|
|
||||||
const lastResetMarker = findAllResetMarkers(lastFrameSlice).pop();
|
|
||||||
if (lastResetMarker == undefined) {
|
|
||||||
// not storing rcvSeqId as this frame did not put us back in track
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstResetMarker = findAllResetMarkers(b).shift();
|
|
||||||
if (firstResetMarker == undefined) {
|
|
||||||
// not storing rcvSeqId as this frame did not put us back in track
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.curImage[session.curImage.length - 1] = Buffer.from(lastFrameSlice.subarray(0, lastResetMarker));
|
|
||||||
b = Buffer.from(b.subarray(firstResetMarker));
|
|
||||||
session.frame_is_bad = false;
|
|
||||||
session.frame_was_fixed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.rcvSeqId = pkt_id;
|
|
||||||
if (session.curImage != null) {
|
|
||||||
session.curImage.push(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findAllResetMarkers = (b: Buffer): number[] => {
|
|
||||||
// a reset marker is a byte 0xff followed by a byte 0xd0-0xd7
|
|
||||||
let ret = [];
|
|
||||||
for (let i = 0; i < b.length - 1; i++) {
|
|
||||||
if (b[i] == 0xff) {
|
|
||||||
const nb = b[i + 1];
|
|
||||||
if (nb >= 0xd0 && nb <= 0xd7) {
|
|
||||||
ret.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeDrwAck = (dv: DataView): DataView => {
|
const makeDrwAck = (dv: DataView): DataView => {
|
||||||
const pkt_id = dv.add(6).readU16();
|
const pkt_id = dv.add(6).readU16();
|
||||||
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
|
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
|
||||||
@@ -329,17 +106,6 @@ const makeDrwAck = (dv: DataView): DataView => {
|
|||||||
}
|
}
|
||||||
return outbuf;
|
return outbuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handle_DrwAck = (session: Session, dv: DataView) => {
|
|
||||||
const packetlen = dv.add(2).readU16();
|
|
||||||
const str_type = dv.add(4).readU8();
|
|
||||||
const str_id = dv.add(5).readU8();
|
|
||||||
const ack_count = dv.add(6).readU16();
|
|
||||||
for (let i = 0; i < ack_count; i++) {
|
|
||||||
const ack_id = dv.add(8 + i * 2).readU16();
|
|
||||||
session.ackDrw(ack_id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const handle_Drw = (session: Session, dv: DataView) => {
|
export const handle_Drw = (session: Session, dv: DataView) => {
|
||||||
const ack = makeDrwAck(dv);
|
const ack = makeDrwAck(dv);
|
||||||
session.send(ack);
|
session.send(ack);
|
||||||
@@ -347,16 +113,15 @@ export const handle_Drw = (session: Session, dv: DataView) => {
|
|||||||
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
|
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
|
||||||
if (m_stream == 1) {
|
if (m_stream == 1) {
|
||||||
deal_with_data(session, dv);
|
deal_with_data(session, dv);
|
||||||
} else if (m_stream == 0) {
|
|
||||||
const b = createResponseForControlCommand(session, dv);
|
|
||||||
b.forEach(session.send);
|
|
||||||
} else {
|
} else {
|
||||||
logger.warning(`Received a Drw packet with stream tag: ${m_stream}, which is not implemented`);
|
const b = createResponseForControlCommand(session, dv);
|
||||||
|
if (b != null) {
|
||||||
|
session.send(b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const handle_Close = (session: Session, dv: DataView) => {
|
|
||||||
const ack = makeDrwAck(dv);
|
export const handle_P2PRdy = (session: Session, _: DataView) => {
|
||||||
session.send(ack);
|
const b = SendUsrChk("admin", "admin", session.outgoingCommandId);
|
||||||
logger.info("Requested to close connection");
|
session.send(b);
|
||||||
session.close();
|
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-5
@@ -1,5 +1,5 @@
|
|||||||
// hacked hexdump from frida to work on normal ArrayBuffers
|
// hacked hexdump from frida to work on normal ArrayBuffers
|
||||||
import "./shim.js";
|
import "./shim.ts";
|
||||||
export const hexdump = (target, options) => {
|
export const hexdump = (target, options) => {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
@@ -10,15 +10,12 @@ export const hexdump = (target, options) => {
|
|||||||
const ansiColor = options.hasOwnProperty("ansiColor") ? options.ansiColor : 0;
|
const ansiColor = options.hasOwnProperty("ansiColor") ? options.ansiColor : 0;
|
||||||
|
|
||||||
let buffer;
|
let buffer;
|
||||||
if (target instanceof DataView) {
|
|
||||||
target = target.buffer;
|
|
||||||
}
|
|
||||||
if (target instanceof ArrayBuffer) {
|
if (target instanceof ArrayBuffer) {
|
||||||
if (length === undefined) length = target.byteLength;
|
if (length === undefined) length = target.byteLength;
|
||||||
else length = Math.min(length, target.byteLength);
|
else length = Math.min(length, target.byteLength);
|
||||||
buffer = target;
|
buffer = target;
|
||||||
} else {
|
} else {
|
||||||
throw "Gimme array buffer or DataView";
|
throw "Gimme array buffer";
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAddress = 0;
|
const startAddress = 0;
|
||||||
|
|||||||
-192
@@ -1,192 +0,0 @@
|
|||||||
import { RemoteInfo } from "dgram";
|
|
||||||
import http from "node:http";
|
|
||||||
|
|
||||||
import { logger } from "./logger.js";
|
|
||||||
import { config } from "./settings.js";
|
|
||||||
import { discoverDevices } from "./discovery.js";
|
|
||||||
import { DevSerial } from "./impl.js";
|
|
||||||
import { Handlers, makeSession, Session, startVideoStream } from "./session.js";
|
|
||||||
import { addExifToJpeg, createExifOrientation } from "./exif.js";
|
|
||||||
|
|
||||||
// @ts-expect-error TS2307
|
|
||||||
import favicon from "./cam.ico.gz";
|
|
||||||
// @ts-expect-error TS2307
|
|
||||||
import html_template from "./asd.html";
|
|
||||||
|
|
||||||
const BOUNDARY = "a very good boundary line";
|
|
||||||
const responses: Record<string, http.ServerResponse[]> = {};
|
|
||||||
const audioResponses: Record<string, http.ServerResponse[]> = {};
|
|
||||||
const sessions: Record<string, Session> = {};
|
|
||||||
|
|
||||||
// https://sirv.com/help/articles/rotate-photos-to-be-upright/
|
|
||||||
const oMap = [1, 8, 3, 6];
|
|
||||||
const oMapMirror = [2, 7, 4, 5];
|
|
||||||
const orientations = [1, 2, 3, 4, 5, 6, 7, 8].reduce((acc, cur) => {
|
|
||||||
return { [cur]: createExifOrientation(cur), ...acc };
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// Reads the mapping of serial numbers to camera names from the text file.
|
|
||||||
|
|
||||||
// Returns the camera name (custom name, if it exists, otherwise its ID).
|
|
||||||
const cameraName = (id: string): string => config.cameras[id].alias || id;
|
|
||||||
|
|
||||||
// The HTTP server.
|
|
||||||
export const serveHttp = (port: number) => {
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
if (req.url.startsWith("/ui/")) {
|
|
||||||
let devId = req.url.split("/")[2];
|
|
||||||
let s = sessions[devId];
|
|
||||||
if (s === undefined) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end("invalid ID");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!s.connected) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end("Nothing online");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ui = html_template
|
|
||||||
.toString()
|
|
||||||
.replace(/\${id}/g, devId)
|
|
||||||
.replace(/\${name}/g, cameraName(devId))
|
|
||||||
.replace(/\${audio}/g, config.cameras[devId].audio ? "true" : "false");
|
|
||||||
res.end(ui);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (req.url.startsWith("/audio/")) {
|
|
||||||
let devId = req.url.split("/")[2];
|
|
||||||
let s = sessions[devId];
|
|
||||||
if (s === undefined) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end("invalid ID");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!s.connected) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end("Nothing online");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.setHeader("Content-Type", `text/event-stream`);
|
|
||||||
audioResponses[devId].push(res);
|
|
||||||
logger.info(`Audio stream requested for camera ${devId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.url.startsWith("/favicon.ico")) {
|
|
||||||
res.setHeader("Content-Type", "image/x-icon");
|
|
||||||
res.setHeader("Content-Encoding", "gzip");
|
|
||||||
res.end(Buffer.from(favicon));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.url.startsWith("/rotate/")) {
|
|
||||||
let devId = req.url.split("/")[2];
|
|
||||||
let curPos = config.cameras[devId]?.rotate || 0;
|
|
||||||
let nextPos = (curPos + 1) % 4;
|
|
||||||
logger.debug(`Rotating ${devId} to ${nextPos}`);
|
|
||||||
config.cameras[devId].rotate = nextPos;
|
|
||||||
res.writeHead(204);
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
} else if (req.url.startsWith("/mirror/")) {
|
|
||||||
let devId = req.url.split("/")[2];
|
|
||||||
logger.debug(`Mirroring ${devId}`);
|
|
||||||
config.cameras[devId].mirror = !config.cameras[devId].mirror;
|
|
||||||
res.writeHead(204);
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
} else if (req.url.startsWith("/camera/")) {
|
|
||||||
let devId = req.url.split("/")[2];
|
|
||||||
logger.info(`Video stream requested for camera ${devId}`);
|
|
||||||
let s = sessions[devId];
|
|
||||||
|
|
||||||
if (s === undefined) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end(`Camera ${devId} not discovered`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!s.connected) {
|
|
||||||
res.writeHead(400);
|
|
||||||
res.end(`Camera ${devId} offline`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader("Content-Type", `multipart/x-mixed-replace; boundary="${BOUNDARY}"`);
|
|
||||||
responses[devId].push(res);
|
|
||||||
res.on("close", () => {
|
|
||||||
responses[devId] = responses[devId].filter((r) => r !== res);
|
|
||||||
logger.info(`Video stream closed for camera ${devId}`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.write("<html>");
|
|
||||||
res.write("<head>");
|
|
||||||
res.write(`<link rel="shortcut icon" href="/favicon.ico">`);
|
|
||||||
res.write("<title>All cameras</title>");
|
|
||||||
res.write("</head>");
|
|
||||||
res.write("<body>");
|
|
||||||
res.write("<h1>All cameras</h1><hr/>");
|
|
||||||
Object.keys(sessions).forEach((id) =>
|
|
||||||
res.write(`<h2>${cameraName(id)}</h2><a href="/ui/${id}"><img src="/camera/${id}"/></a><hr/>`),
|
|
||||||
);
|
|
||||||
res.write("</body>");
|
|
||||||
res.write("</html>");
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let devEv = discoverDevices(config.discovery_ips);
|
|
||||||
|
|
||||||
const startSession = (s: Session) => {
|
|
||||||
startVideoStream(s);
|
|
||||||
logger.info(`Camera ${s.devName} is now ready to stream`);
|
|
||||||
};
|
|
||||||
|
|
||||||
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
|
||||||
if (dev.devId in sessions) {
|
|
||||||
logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`);
|
|
||||||
responses[dev.devId] = [];
|
|
||||||
audioResponses[dev.devId] = [];
|
|
||||||
const s = makeSession(Handlers, dev, rinfo, startSession, 5000);
|
|
||||||
sessions[dev.devId] = s;
|
|
||||||
config.cameras[dev.devId] = { rotate: 0, mirror: false, audio: true, ...(config.cameras[dev.devId] || {}) };
|
|
||||||
|
|
||||||
const header = Buffer.from(`--${BOUNDARY}\r\nContent-Type: image/jpeg\r\n\r\n`);
|
|
||||||
|
|
||||||
s.eventEmitter.on("frame", () => {
|
|
||||||
// Add an EXIF header to indicate if the image should be rotated or mirrored
|
|
||||||
let orientation = config.cameras[dev.devId].rotate;
|
|
||||||
orientation = config.cameras[dev.devId].mirror ? oMapMirror[orientation] : oMap[orientation];
|
|
||||||
const exifSegment = orientations[orientation];
|
|
||||||
const jpegHeader = addExifToJpeg(s.curImage[0], exifSegment);
|
|
||||||
const assembled = Buffer.concat([jpegHeader, ...s.curImage.slice(1)]);
|
|
||||||
responses[dev.devId].forEach((res) => {
|
|
||||||
res.write(header);
|
|
||||||
res.write(assembled);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
s.eventEmitter.on("disconnect", () => {
|
|
||||||
logger.info(`Camera ${dev.devId} disconnected`);
|
|
||||||
delete sessions[dev.devId];
|
|
||||||
});
|
|
||||||
if (config.cameras[dev.devId].audio) {
|
|
||||||
s.eventEmitter.on("audio", ({ gap, data }) => {
|
|
||||||
// ew, maybe WS?
|
|
||||||
var b64encoded = Buffer.from(data).toString("base64");
|
|
||||||
audioResponses[dev.devId].forEach((res) => {
|
|
||||||
res.write("data: ");
|
|
||||||
res.write(b64encoded);
|
|
||||||
res.write("\n\n");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Starting HTTP server on port ${port}`);
|
|
||||||
server.listen(port);
|
|
||||||
};
|
|
||||||
@@ -1,181 +1,93 @@
|
|||||||
import "./shim.js";
|
import "./shim.ts";
|
||||||
|
|
||||||
import { ccDest, Commands, ControlCommands } from "./datatypes.js";
|
import { Commands } from "./datatypes.js";
|
||||||
|
import { Session } from "./server.js";
|
||||||
import { XqBytesEnc } from "./func_replacements.js";
|
import { XqBytesEnc } from "./func_replacements.js";
|
||||||
import { hexdump } from "./hexdump.js";
|
import { hexdump } from "./hexdump.js";
|
||||||
import { Session } from "./session.js";
|
|
||||||
import { u16_swap } from "./utils.js";
|
import { u16_swap } from "./utils.js";
|
||||||
|
|
||||||
const str2byte = (s: string): number[] => {
|
const str2byte = (s: string): number[] => {
|
||||||
return Array.from(s).map((_, i) => s.charCodeAt(i));
|
return Array.from(s).map((_, i) => s.charCodeAt(i));
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeDataReadWrite = (session: Session, command: number, data: DataView | null): DataView => {
|
const CmdSndProcHdr = (start: number, cmd: number, len: number, dest: number): DataView => {
|
||||||
const DRW_HEADER_LEN = 0x10;
|
len = len + 4; // hdr size?
|
||||||
const TOKEN_LEN = 0x4;
|
let cmdHeader = new DataView(new Uint8Array(8).buffer);
|
||||||
const CHANNEL = 0;
|
cmdHeader.writeU16(u16_swap(start));
|
||||||
const START_CMD = 0x110a;
|
cmdHeader.add(2).writeU16(u16_swap(cmd));
|
||||||
|
cmdHeader.add(4).writeU16(u16_swap(len));
|
||||||
let pkt_len = DRW_HEADER_LEN + TOKEN_LEN;
|
cmdHeader.add(6).writeU16(u16_swap(dest));
|
||||||
let payload_len = TOKEN_LEN;
|
return cmdHeader;
|
||||||
let bufCopy: Uint8Array | null = null;
|
|
||||||
if (data && data.byteLength > 4) {
|
|
||||||
bufCopy = new Uint8Array(data.buffer);
|
|
||||||
const bufDV = new DataView(bufCopy.buffer);
|
|
||||||
// this mutates the buffer, don't want to mutate the caller
|
|
||||||
XqBytesEnc(bufDV, bufDV.byteLength, 4);
|
|
||||||
pkt_len += bufDV.byteLength;
|
|
||||||
payload_len += bufDV.byteLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = new DataView(new Uint8Array(pkt_len).buffer);
|
|
||||||
ret.add(0).writeU16(Commands.Drw);
|
|
||||||
ret.add(2).writeU16(pkt_len - 4); // -4 as we ignore the [0xf1, 0xd0, len, len]
|
|
||||||
ret.add(4).writeU8(0xd1); // ?
|
|
||||||
ret.add(5).writeU8(CHANNEL);
|
|
||||||
ret.add(6).writeU16(session.outgoingCommandId);
|
|
||||||
ret.add(8).writeU16(START_CMD);
|
|
||||||
ret.add(10).writeU16(command);
|
|
||||||
ret.add(12).writeU16(u16_swap(payload_len));
|
|
||||||
ret.add(14).writeU16(ccDest[command]);
|
|
||||||
ret.add(16).writeByteArray(session.ticket);
|
|
||||||
if (data && data.byteLength > 4) {
|
|
||||||
ret.add(20).writeByteArray(bufCopy);
|
|
||||||
}
|
|
||||||
|
|
||||||
session.outgoingCommandId++;
|
|
||||||
return ret;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SendIRToggle = (session: Session): DataView => {
|
const DrwHdr = (cmd: number, len: number, d1_or_d2: 0xd1 | 0xd2, m_chan: number, pkt_id: number): DataView => {
|
||||||
return makeDataReadWrite(session, ControlCommands.IRToggle, null);
|
let retret = new DataView(new Uint8Array(len + 4).buffer);
|
||||||
};
|
retret.writeU16(cmd);
|
||||||
|
retret.add(2).writeU16(len); // buflen -4?
|
||||||
export const SendDevStatus = (session: Session): DataView => {
|
retret.add(4).writeU8(d1_or_d2);
|
||||||
return makeDataReadWrite(session, ControlCommands.DevStatus, null);
|
retret.add(5).writeU8(m_chan); // chan? hardcoded
|
||||||
};
|
retret.add(6).writeU16(pkt_id);
|
||||||
|
return retret;
|
||||||
export const SendWifiSettings = (session: Session): DataView => {
|
|
||||||
return makeDataReadWrite(session, ControlCommands.WifiSettings, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendListWifi = (session: Session): DataView => {
|
|
||||||
return makeDataReadWrite(session, ControlCommands.ListWifi, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendStopVideo = (session: Session): DataView => {
|
|
||||||
return makeDataReadWrite(session, ControlCommands.StopVideo, null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SendStartVideo = (session: Session): DataView => {
|
export const SendStartVideo = (session: Session): DataView => {
|
||||||
return makeDataReadWrite(session, ControlCommands.StartVideo, null);
|
// TODO: extract SendUsrChk
|
||||||
|
let buf = new DataView(new Uint8Array(0x18).buffer);
|
||||||
|
// console.log(hexdump(DrwHdr(0xf1d0, 0x0114, 0xd1, 0, pkt_id).buffer));
|
||||||
|
|
||||||
|
let bytes = [
|
||||||
|
0xf1,
|
||||||
|
0xd0,
|
||||||
|
0x01, // len? lower values= no response, larger values = 1 frame then kicked
|
||||||
|
0x14, // len
|
||||||
|
0xd1, // ?
|
||||||
|
0x00, // chan
|
||||||
|
session.outgoingCommandId >> 8,
|
||||||
|
session.outgoingCommandId,
|
||||||
|
0x11,
|
||||||
|
0x0a,
|
||||||
|
0x10,
|
||||||
|
0x30,
|
||||||
|
0x08,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
session.ticket[0],
|
||||||
|
session.ticket[1],
|
||||||
|
session.ticket[2],
|
||||||
|
session.ticket[3],
|
||||||
|
0x01,
|
||||||
|
0x01,
|
||||||
|
0x01,
|
||||||
|
0x01,
|
||||||
|
];
|
||||||
|
buf.writeByteArray(bytes);
|
||||||
|
return buf;
|
||||||
};
|
};
|
||||||
|
export const SendUsrChk = (username: string, password: string, pkt_id: number): DataView => {
|
||||||
export const getVideoKey = (session: Session): void => {
|
// type is char account[0x20]; char password[0x80];
|
||||||
// this is not useful at all
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
// payload len??
|
|
||||||
const payload = [0x0, i]; //, 0x0, 0x0, 0x0, 0x0];
|
|
||||||
const dv = new DataView(new Uint8Array(payload).buffer);
|
|
||||||
session.send(makeDataReadWrite(session, ControlCommands.VideoParamGet, dv));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendVideoResolution = (session: Session, resol: 1 | 2 | 3 | 4): DataView[] => {
|
|
||||||
// seems like 0x1 = resolution, and is specified by ID not by size
|
|
||||||
// unclear what 0x2-0xf achieve - they report back as '0' always -- ignored?
|
|
||||||
const pairs = {
|
|
||||||
1: [
|
|
||||||
// 320 x 240
|
|
||||||
[0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
|
|
||||||
//[0x7, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0],
|
|
||||||
],
|
|
||||||
2: [
|
|
||||||
// 640x480
|
|
||||||
[0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0],
|
|
||||||
//[0x7, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0],
|
|
||||||
],
|
|
||||||
3: [
|
|
||||||
// also 640x480 on the X5 -- hwat now?
|
|
||||||
[0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0],
|
|
||||||
//[0x7, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0],
|
|
||||||
],
|
|
||||||
4: [
|
|
||||||
// also 640x480 on the X5 -- hwat now?
|
|
||||||
[0x1, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0],
|
|
||||||
//[0x7, 0x0, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x0],
|
|
||||||
],
|
|
||||||
// maybe the 0x7 = bitrate??
|
|
||||||
};
|
|
||||||
|
|
||||||
return pairs[resol].map((payload: number[]) => {
|
|
||||||
const dv = new DataView(new Uint8Array(payload).buffer);
|
|
||||||
return makeDataReadWrite(session, ControlCommands.VideoParamSet, dv);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendReboot = (session: Session): DataView => {
|
|
||||||
let dv = null;
|
|
||||||
return makeDataReadWrite(session, ControlCommands.Reboot, dv);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendWifiDetails = (
|
|
||||||
session: Session,
|
|
||||||
ssid: string,
|
|
||||||
password: string,
|
|
||||||
channel: number,
|
|
||||||
dhcp: boolean,
|
|
||||||
): DataView => {
|
|
||||||
if (!dhcp) {
|
|
||||||
throw new Error("only DHCP is supported");
|
|
||||||
}
|
|
||||||
let buf = new Uint8Array(0x108).fill(0);
|
|
||||||
let cmd_payload = new DataView(buf.buffer);
|
|
||||||
let mask_reversed = "0.255.255.255";
|
|
||||||
// unclear which is which ))
|
|
||||||
let m_ip = "0.0.0.0";
|
|
||||||
let m_gw = "0.0.0.0";
|
|
||||||
let m_dns1 = "0.0.0.0";
|
|
||||||
let m_dns2 = "0.0.0.0";
|
|
||||||
|
|
||||||
// tag_wifiParams in types/all.h
|
|
||||||
cmd_payload.add(0x0c).writeU8(channel);
|
|
||||||
cmd_payload.add(0x10).writeU8(0); // TODO: AUTH
|
|
||||||
cmd_payload.add(0x14).writeU8(1); // DHCP
|
|
||||||
cmd_payload.add(0x18).writeByteArray(str2byte(ssid));
|
|
||||||
cmd_payload.add(0x38).writeByteArray(str2byte(password));
|
|
||||||
cmd_payload.add(0xb8).writeByteArray(str2byte(m_ip));
|
|
||||||
cmd_payload.add(0xc8).writeByteArray(str2byte(mask_reversed));
|
|
||||||
cmd_payload.add(0xd8).writeByteArray(str2byte(m_gw));
|
|
||||||
cmd_payload.add(0xe8).writeByteArray(str2byte(m_dns1));
|
|
||||||
cmd_payload.add(0xf8).writeByteArray(str2byte(m_dns2));
|
|
||||||
|
|
||||||
const ret = makeDataReadWrite(session, ControlCommands.WifiSettingsSet, cmd_payload);
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendUsrChk = (session: Session, username: string, password: string): DataView => {
|
|
||||||
let buf = new Uint8Array(0x20 + 0x80);
|
let buf = new Uint8Array(0x20 + 0x80);
|
||||||
buf.fill(0);
|
buf.fill(0);
|
||||||
let cmd_payload = new DataView(buf.buffer);
|
let cmd_payload = new DataView(buf.buffer);
|
||||||
// type is char account[0x20]; char password[0x80];
|
|
||||||
cmd_payload.writeByteArray(str2byte(username));
|
cmd_payload.writeByteArray(str2byte(username));
|
||||||
cmd_payload.add(0x20).writeByteArray(str2byte(password));
|
cmd_payload.add(0x20).writeByteArray(str2byte(password));
|
||||||
return makeDataReadWrite(session, ControlCommands.ConnectUser, cmd_payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const create_LanSearchExt = (): DataView => {
|
const start = 0xa11;
|
||||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
const dest = 0xff;
|
||||||
outbuf.writeU16(Commands.LanSearchExt);
|
const cmd = 0x1020;
|
||||||
outbuf.add(2).writeU16(0x0);
|
const len = buf.byteLength;
|
||||||
return outbuf;
|
let cmdHeader = CmdSndProcHdr(start, cmd, len, dest);
|
||||||
};
|
let ret = new DataView(new Uint8Array(12 + len).buffer);
|
||||||
|
ret.writeByteArray(new Uint8Array(cmdHeader.buffer));
|
||||||
|
XqBytesEnc(cmd_payload, 0x20 + 0x80, 4);
|
||||||
|
ret.add(12).writeByteArray(new Uint8Array(cmd_payload.buffer));
|
||||||
|
|
||||||
export const create_LanSearch = (): DataView => {
|
// need to encapsulate this into create_Drw(outbuf, 0xd1, param4?, svar1?,
|
||||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
// copy_len, inbuf); seems like param4/svar1 are overflowing == maybe '0xa'
|
||||||
outbuf.writeU16(Commands.LanSearch);
|
// and '0x2010'??
|
||||||
outbuf.add(2).writeU16(0x0);
|
let retret = DrwHdr(0xf1d0, 8 + 12 + len - 4, 0xd1, 0, pkt_id);
|
||||||
return outbuf;
|
retret.add(8).writeByteArray(new Uint8Array(ret.buffer));
|
||||||
|
return retret;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const create_P2pRdy = (inbuf: DataView): DataView => {
|
export const create_P2pRdy = (inbuf: DataView): DataView => {
|
||||||
@@ -186,30 +98,3 @@ export const create_P2pRdy = (inbuf: DataView): DataView => {
|
|||||||
outbuf.add(4).writeByteArray(new Uint8Array(inbuf.readByteArray(P2PRDY_SIZE).buffer));
|
outbuf.add(4).writeByteArray(new Uint8Array(inbuf.readByteArray(P2PRDY_SIZE).buffer));
|
||||||
return outbuf;
|
return outbuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const create_P2pAlive = (): DataView => {
|
|
||||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
|
||||||
outbuf.writeU16(Commands.P2PAlive);
|
|
||||||
outbuf.add(2).writeU16(0);
|
|
||||||
return outbuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const create_P2pClose = (): DataView => {
|
|
||||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
|
||||||
outbuf.writeU16(Commands.Close);
|
|
||||||
outbuf.add(2).writeU16(0);
|
|
||||||
return outbuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DevSerial = { prefix: string; serial: string; suffix: string; serialU64: bigint; devId: string };
|
|
||||||
export const parse_PunchPkt = (dv: DataView): DevSerial => {
|
|
||||||
const punchCmd = dv.readU16();
|
|
||||||
const len = dv.add(2).readU16();
|
|
||||||
const prefix = dv.add(4).readString(4);
|
|
||||||
const serialU64 = dv.add(8).readU64();
|
|
||||||
const serial = serialU64.toString();
|
|
||||||
const suffix = dv.add(16).readString(len - 16 + 4); // 16 = offset, +4 header
|
|
||||||
const devId = prefix + serial + suffix;
|
|
||||||
|
|
||||||
return { prefix, serial, suffix, serialU64, devId };
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { isatty } from "node:tty";
|
|
||||||
import { addColors, config, createLogger, format, transports as wtransports } from "winston";
|
|
||||||
|
|
||||||
const myFormat = format.printf(({ level, message, timestamp }) => {
|
|
||||||
return `${timestamp} [${level}] ${message}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const transports = {
|
|
||||||
console: new wtransports.Console(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export let logger = undefined;
|
|
||||||
|
|
||||||
export const buildLogger = (level: string, colorize: boolean | undefined) => {
|
|
||||||
let use_color = colorize === undefined ? isatty(1) : colorize;
|
|
||||||
const fmt = use_color
|
|
||||||
? format.combine(format.colorize(), format.timestamp(), myFormat)
|
|
||||||
: format.combine(format.timestamp(), myFormat);
|
|
||||||
logger = createLogger({
|
|
||||||
levels: { ...config.syslog.levels, trace: 10 },
|
|
||||||
level,
|
|
||||||
format: fmt,
|
|
||||||
transports: [transports.console],
|
|
||||||
});
|
|
||||||
addColors({ trace: "white" });
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { createSocket, RemoteInfo } from "node:dgram";
|
|
||||||
export const mockServer = (onMessage: (msg: DataView) => Uint8Array[]) => {
|
|
||||||
const sock = createSocket("udp4");
|
|
||||||
const SEND_PORT = 32108;
|
|
||||||
sock.bind(SEND_PORT);
|
|
||||||
sock.on("message", (msg, rinfo: RemoteInfo) => {
|
|
||||||
const dv = new DataView(new Uint8Array(msg).buffer);
|
|
||||||
onMessage(dv).forEach((out) => {
|
|
||||||
sock.send(out, rinfo.port, rinfo.address);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export type opt = {
|
|
||||||
discovery_ip: string;
|
|
||||||
attempt_to_fix_packet_loss: boolean;
|
|
||||||
};
|
|
||||||
Generated
+69
-717
File diff suppressed because it is too large
Load Diff
+2
-11
@@ -1,23 +1,14 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha tests",
|
"test": "mocha tests"
|
||||||
"tsc": "tsc",
|
|
||||||
"build": "esbuild cmd/bin.ts --bundle --platform=node --outfile=dist/bin.cjs --target=node12 --loader:.gz=binary --loader:.html=text"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/yargs": "^17.0.32",
|
|
||||||
"esbuild": "^0.20.2",
|
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"winston": "^3.13.0",
|
"beamcoder": "^0.7.1"
|
||||||
"yaml": "^2.4.2",
|
|
||||||
"yargs": "^17.7.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { RemoteInfo } from "dgram";
|
|
||||||
|
|
||||||
import { config } from "./settings.js";
|
|
||||||
import { discoverDevices } from "./discovery.js";
|
|
||||||
import { WifiListItem } from "./handlers.js";
|
|
||||||
import { DevSerial, SendListWifi } from "./impl.js";
|
|
||||||
import { Handlers, makeSession, Session, configureWifi } from "./session.js";
|
|
||||||
import { logger } from "./logger.js";
|
|
||||||
|
|
||||||
export const pair = ({ ssid, password }: { ssid: string; password: string }) => {
|
|
||||||
logger.info(`Will configure any devices found to join ${ssid}`);
|
|
||||||
let sessions: Record<string, Session> = {};
|
|
||||||
|
|
||||||
let devEv = discoverDevices(config.discovery_ips);
|
|
||||||
if (password == "") {
|
|
||||||
throw new Error("You must set a non-zero-length password");
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLogin = (s: Session) => {
|
|
||||||
logger.info(`Scanning for Wifi networks on ${s.devName} -- this may time out`);
|
|
||||||
|
|
||||||
// configureWifi(ssid, password, 0)(s);
|
|
||||||
s.send(SendListWifi(s));
|
|
||||||
};
|
|
||||||
|
|
||||||
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
|
||||||
if (dev.devId in sessions) {
|
|
||||||
logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`);
|
|
||||||
const s = makeSession(Handlers, dev, rinfo, onLogin, 10000);
|
|
||||||
let configured = {};
|
|
||||||
|
|
||||||
s.eventEmitter.on("disconnect", () => {
|
|
||||||
logger.info(`Camera ${dev.devId} disconnected`);
|
|
||||||
if (configured[dev.devId]) {
|
|
||||||
logger.info("Press CONTROL+C if you're done setting up your cameras");
|
|
||||||
}
|
|
||||||
delete sessions[dev.devId];
|
|
||||||
delete configured[dev.devId];
|
|
||||||
});
|
|
||||||
sessions[dev.devId] = s;
|
|
||||||
|
|
||||||
s.eventEmitter.on("ListWifi", (items: WifiListItem[]) => {
|
|
||||||
const matches = items.filter((i) => i.ssid == ssid);
|
|
||||||
if (matches.length == 0) {
|
|
||||||
logger.error(`Camera could not find SSID '${ssid}'`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (configured[dev.devId]) {
|
|
||||||
logger.info(`Got two answers from camera, ignoring second`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const match = matches[0];
|
|
||||||
logger.info(`Configuring camera ${s.devName} on ${JSON.stringify(match)}`);
|
|
||||||
configureWifi(ssid, password, match.channel)(s);
|
|
||||||
configured[dev.devId] = true;
|
|
||||||
logger.info(`WiFi config for camera ${s.devName} is done`);
|
|
||||||
logger.info(`Camera should reboot now`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 247 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 939 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB |
@@ -273,69 +273,3 @@ AvcLIB = src/object_jni.cpp, line 653, SystemCmd:[p2pID=BATC609531EXLVS]SystemCm
|
|||||||
AvcLIB = src/object_jni.cpp, line 653, SystemCmd:[p2pID=BATC609531EXLVS]SystemCmd=0x1040
|
AvcLIB = src/object_jni.cpp, line 653, SystemCmd:[p2pID=BATC609531EXLVS]SystemCmd=0x1040
|
||||||
SystemCmd 0x1008 ret: 12
|
SystemCmd 0x1008 ret: 12
|
||||||
SystemCmd 0x1040 ret: 92
|
SystemCmd 0x1040 ret: 92
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
battery max = 3650 while transmitting
|
|
||||||
battery min = 3200 (powered off)
|
|
||||||
bat 3480 -> 3200 while transmitting via wifi = 106 seconds
|
|
||||||
|
|
||||||
----
|
|
||||||
XqStrDec param1 SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG => strlen'd => 112
|
|
||||||
=> 4;139.155.68.77;119.45.114.92;162.62.63.154;3.132.215.40
|
|
||||||
112 /2 = 56
|
|
||||||
buf = 57
|
|
||||||
res = 56 char + \0
|
|
||||||
|
|
||||||
codetable = AAABACADAEAFAGAHAIAJAKALAMANAOAPAQARASATAUAVAWAXAYAZBABBBCBDBEBFBGBHBIBJBKBLBMBNBOBPBQBRBSBTBUBVBWBXBYBZCACBCCCDCECFCGCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCWCXCYCZDADBDCDDDEDFDGDHDIDJDKDLDMDNDODPDQDRDSDTDUDVDWDXDYDZEAEBECEDEEEFEGEHEIEJEKELEMENEOEPEQERESETEUEVEWEXEYEZFAFBFCFDFEFFFGFHFIFJFKFLFMFNFOFPFQFRFSFTFUFVFWFXFYFZGAGBGCGDGEGFGGGHGIGJGKGLGMGNGOGPGQGRGSGTGUGVGWGXGYGZHAHBHCHDHEHFHGHHHIHJHKHLHMHNHOHPHQHRHSHTHUHVHWHXHYHZIAIBICIDIEIFIGIHIIIJIKILIMINIOIPIQIRISITIUIVIWIXIYIZJAJBJCJDJEJFJGJHJIJJJKJLJMJNJOJPJQJRJSJTJUJVJWJXJYJZKAKBKCKDKEKFKGKHKIKJKKKLKMKNKOKPKQKRKSKTKUKVKWKXKYKZLALBLCLDLELFLGLHLILJLKLLLMLNLOLPLQLRLSLTLULVLWLXLYLZMAMBMCMDMEMFMGMHMIMJMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZNANBNCNDNENFNGNHNINJNKNLNMNNNONPNQNRNSNTNUNVNWNXNYNZOAOBOCODOEOFOGOHOIOJOKOLOMONOOOPOQOROSOTOUOVOWOXOYOZPAPBPCPDPEPFPGPHPIPJPKPLPMPNPOPPPQPRPSPTPUPVPWPXPYPZQAQBQCQDQEQFQGQHQIQJQKQLQMQNQOQPQQQRQSQTQUQVQWQXQYQZRARBRCRDRERFRGRHRIRJRKRLRMRNRORPRQRRRSRTRURVRWRXRYRZSASBSCSDSESFSGSHSISJSKSLSMSNSOSPSQSRSSSTSUSVSWSXSYSZTATBTCTDTETFTGTHTITJTKTLTMTNTOTPTQTRTSTTTUTVTWTXTYTZUAUBUCUDUEUFUGUHUIUJUKULUMUNUOUPUQURUSUTUUUVUWUXUYUZVAVBVCVDVEVFVGVHVIVJVKVLVMVNVOVPVQVRVSVTVUVVVWVXVYVZWAWBWCWDWEWFWGWHWIWJWKWLWMWNWOWPWQWRWSWTWUWVWWWXWYWZXAXBXCXDXEXFXGXHXIXJXKXLXMXNXOXPXQXRXSXTXUXVXWXXXYXZYAYBYCYDYEYFYGYHYIYJYKYLYMYNYOYPYQYRYSYTYUYVYWYXYYYZZAZBZCZDZEZFZGZHZIZJZKZLZMZNZOZPZQZRZSZTZUZVZWZXZYZZ
|
|
||||||
=
|
|
||||||
for i in string.ascii_uppercase:
|
|
||||||
for j in string.ascii_uppercase:
|
|
||||||
print(f'{i}{j}', end='')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
what is this??
|
|
||||||
|
|
||||||
0000 24 02 00 00 01 0a 12 00 02 1c ff ff 00 00 00 00 $...............
|
|
||||||
0010 01 00 00 00 65 64 61 31 38 34 34 64 30 30 34 64 ....eda1844d004d
|
|
||||||
0020 33 36 34 33 61 62 66 64 66 62 36 63 38 62 34 32 3643abfdfb6c8b42
|
|
||||||
0030 35 36 30 33 00 00 00 00 00 00 00 00 00 00 00 00 5603............
|
|
||||||
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0050 00 00 00 00 01 00 00 00 09 00 00 00 78 00 00 00 ............x...
|
|
||||||
0060 61 36 65 34 36 34 37 38 34 35 33 63 39 61 65 61 a6e46478453c9aea
|
|
||||||
0070 63 61 35 61 66 36 32 34 00 00 00 00 00 00 00 00 ca5af624........
|
|
||||||
0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
00a0 31 61 64 37 35 61 65 37 32 65 30 35 64 63 66 34 1ad75ae72e05dcf4
|
|
||||||
00b0 64 61 64 62 64 31 37 61 00 00 00 00 00 00 00 00 dadbd17a........
|
|
||||||
00c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
00e0 42 41 54 43 36 30 39 35 38 30 48 56 44 43 53 00 BATC609580HVDCS.
|
|
||||||
00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0120 02 00 00 00 41 49 7a 61 53 79 42 2d 62 6f 78 4f ....AIzaSyB-boxO
|
|
||||||
0130 47 35 6e 36 41 62 4b 4d 4c 41 4f 77 6d 6d 31 50 G5n6AbKMLAOwmm1P
|
|
||||||
0140 5a 7a 71 50 6b 79 5a 6a 4d 77 63 00 00 00 00 00 ZzqPkyZjMwc.....
|
|
||||||
0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
01a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
01b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
01c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
01d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
01e0 00 00 00 00 00 00 00 00 41 49 7a 61 53 79 42 2d ........AIzaSyB-
|
|
||||||
01f0 62 6f 78 4f 47 35 6e 36 41 62 4b 4d 4c 41 4f 77 boxOG5n6AbKMLAOw
|
|
||||||
0200 6d 6d 31 50 5a 7a 71 50 6b 79 5a 6a 4d 77 63 00 mm1PZzqPkyZjMwc.
|
|
||||||
0210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|
||||||
0220 00 00 00 00 00 00 00 00 ........
|
|
||||||
|
|
||||||
-- this looks like google keys: AIzaSyB
|
|
||||||
|
|
||||||
|
|
||||||
most useful parsing at IpcByte2ObjectParser and IlnkUtils
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import string
|
|
||||||
data = [
|
|
||||||
0xf1, 0xd0, 0x01, 0x18, 0xd1, 0x00, 0x00, 0x02, 0x11, 0x0a, 0x01, 0x60, 0x0c, 0x01, 0x00, 0x00,
|
|
||||||
0x4c, 0x31, 0x67, 0x4e, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x72, 0x6a, 0x78, 0x6f, 0x64, 0x75, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x72, 0x74, 0x71, 0x64, 0x73, 0x62, 0x73, 0x60,
|
|
||||||
0x71, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x33, 0x34, 0x34, 0x2f, 0x33, 0x34,
|
|
||||||
0x34, 0x2f, 0x33, 0x34, 0x34, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
|
||||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
||||||
]
|
|
||||||
def dec(d):
|
|
||||||
ret = []
|
|
||||||
for b in d[0x14:]:
|
|
||||||
if b % 2:
|
|
||||||
ret.append(b - 1)
|
|
||||||
else:
|
|
||||||
ret.append(b + 1)
|
|
||||||
ret = d[:0x14] + ret[-4:] + ret[:-4]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def hexdump(dump):
|
|
||||||
print(" " * 10, end="")
|
|
||||||
for i in range(0, 0xF+1):
|
|
||||||
print(f"{i:2X}", end=" ")
|
|
||||||
|
|
||||||
print(" ", end="")
|
|
||||||
for i in range(0, 0xF+1):
|
|
||||||
print(f"{i:X}", end="")
|
|
||||||
print()
|
|
||||||
|
|
||||||
for i in range(0, len(dump), 0x10):
|
|
||||||
line = dump[i:i+0x10]
|
|
||||||
print(f"{i:08x}", end=" ")
|
|
||||||
for b in line:
|
|
||||||
print(f"{b:02x}", end=" ")
|
|
||||||
|
|
||||||
if len(line) < 16:
|
|
||||||
print(" " * (16 - len(line)), end="")
|
|
||||||
print(" ", end="")
|
|
||||||
for b in line:
|
|
||||||
rep = "."
|
|
||||||
if chr(b) in string.digits or chr(b) in string.ascii_letters:
|
|
||||||
rep = chr(b)
|
|
||||||
print(rep, end="")
|
|
||||||
|
|
||||||
print("")
|
|
||||||
|
|
||||||
_dec = dec(data)
|
|
||||||
hexdump(_dec[0x14:])
|
|
||||||
#print([hex(n) for n in _dec])
|
|
||||||
print("for test values, DEC")
|
|
||||||
print("".join([f"{n:02x}" for n in _dec]))
|
|
||||||
print("for test values, enc")
|
|
||||||
print("".join([f"{n:02x}" for n in data]))
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import string
|
|
||||||
ct = []
|
|
||||||
for i in string.ascii_uppercase:
|
|
||||||
for j in string.ascii_uppercase:
|
|
||||||
ct.append(f'{i}{j}')
|
|
||||||
|
|
||||||
insec = 'SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG'
|
|
||||||
insec = 'PFLXLSTBLKHYLPLRHUEHIEEGEEARLQPLIHIAEKAOSTLVEOSQPDHZLNPAICIFEREJLKEMENHUHXIBEPEEEHEIEL'
|
|
||||||
insec = 'EKTDROREHXHURHRKRMCXEEKPRNKKUZPNLXNYNOHYAONRNUNWRJSQGZNXGUNTIHKIJYEHPAKBHTKEKGKDLKJVKHKFERGSGIEIHUGLEDGOGQGNEEGFGRGP'
|
|
||||||
out = '4;139.155.68.77;119.45.114.92;162.62.63.154;3.132.215.40'
|
|
||||||
|
|
||||||
dec = ''
|
|
||||||
for i in range(0, len(insec), 2):
|
|
||||||
cur = insec[i:i+2]
|
|
||||||
idx = ct.index(cur)
|
|
||||||
mod_cnt = idx // 0x5e
|
|
||||||
_sum = (idx + (mod_cnt * -0x5e) + 0x20) & 0x7f
|
|
||||||
dec += chr(_sum)
|
|
||||||
|
|
||||||
print(dec)
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import dgram from "node:dgram";
|
||||||
|
import { createWriteStream } from "node:fs";
|
||||||
|
import { create_LanSearch } from "./func_replacements.js";
|
||||||
|
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||||
|
import { handle_P2PAlive, handle_PunchPkt, handle_P2PRdy, handle_Drw, notImpl, noop } from "./handlers.js";
|
||||||
|
import { hexdump } from "./hexdump.js";
|
||||||
|
import EventEmitter from "node:events";
|
||||||
|
|
||||||
|
export type Session = {
|
||||||
|
send: (msg: DataView) => void;
|
||||||
|
broadcast: (msg: DataView) => void;
|
||||||
|
outgoingCommandId: number;
|
||||||
|
ticket: number[];
|
||||||
|
eventEmitter: EventEmitter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PacketHandler = (session: Session, dv: DataView) => void;
|
||||||
|
|
||||||
|
type opt = {
|
||||||
|
debug: boolean;
|
||||||
|
ansi: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type msgCb = (session: Session, msg: Buffer, rinfo: any, options: opt) => void;
|
||||||
|
type connCb = (session: Session) => void;
|
||||||
|
|
||||||
|
const makeSession = (cb: msgCb, connCb: connCb, options?: opt): Session => {
|
||||||
|
const sock = dgram.createSocket("udp4");
|
||||||
|
|
||||||
|
sock.on("error", (err) => {
|
||||||
|
console.error(`sock error:\n${err.stack}`);
|
||||||
|
sock.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
sock.on("message", (msg, rinfo) => cb(session, msg, rinfo, options));
|
||||||
|
|
||||||
|
sock.on("listening", () => {
|
||||||
|
const address = sock.address();
|
||||||
|
console.log(`sock listening ${address.address}:${address.port}`);
|
||||||
|
sock.setBroadcast(true);
|
||||||
|
connCb(session);
|
||||||
|
});
|
||||||
|
|
||||||
|
const RECV_PORT = 49512; // important?
|
||||||
|
const DST_IP = "192.168.1.1";
|
||||||
|
const BCAST_IP = "192.168.1.255";
|
||||||
|
const SEND_PORT = 32108;
|
||||||
|
sock.bind(RECV_PORT);
|
||||||
|
|
||||||
|
const session: Session = {
|
||||||
|
outgoingCommandId: 0,
|
||||||
|
ticket: [0, 0, 0, 0],
|
||||||
|
eventEmitter: new EventEmitter(),
|
||||||
|
send: (msg: DataView) => {
|
||||||
|
const raw = msg.readU16();
|
||||||
|
const cmd = CommandsByValue[raw];
|
||||||
|
if (options.debug) {
|
||||||
|
console.log(`>> ${cmd}`);
|
||||||
|
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 0 }));
|
||||||
|
}
|
||||||
|
if (raw == Commands.Drw) {
|
||||||
|
// not sure why cmd == Commands.Drw does not work
|
||||||
|
session.outgoingCommandId++;
|
||||||
|
}
|
||||||
|
sock.send(new Uint8Array(msg.buffer), SEND_PORT, DST_IP);
|
||||||
|
},
|
||||||
|
broadcast: (msg: DataView) => sock.send(new Uint8Array(msg.buffer), SEND_PORT, BCAST_IP),
|
||||||
|
};
|
||||||
|
return session;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Handlers: Record<keyof typeof Commands, PacketHandler> = {
|
||||||
|
PunchPkt: handle_PunchPkt,
|
||||||
|
|
||||||
|
Close: notImpl,
|
||||||
|
LanSearchExt: notImpl,
|
||||||
|
LanSearch: notImpl,
|
||||||
|
P2PAlive: handle_P2PAlive,
|
||||||
|
P2PAliveAck: notImpl,
|
||||||
|
Hello: notImpl,
|
||||||
|
P2pRdy: handle_P2PRdy,
|
||||||
|
P2pReq: notImpl,
|
||||||
|
LstReq: notImpl,
|
||||||
|
DrwAck: noop,
|
||||||
|
Drw: handle_Drw,
|
||||||
|
|
||||||
|
// From CSession_CtrlPkt_Proc, incomplete
|
||||||
|
PunchTo: notImpl,
|
||||||
|
HelloAck: notImpl,
|
||||||
|
RlyTo: notImpl,
|
||||||
|
DevLgnAck: notImpl,
|
||||||
|
P2PReqAck: notImpl,
|
||||||
|
ListenReqAck: notImpl,
|
||||||
|
RlyHelloAck: notImpl, // always
|
||||||
|
RlyHelloAck2: notImpl, // if len >1??
|
||||||
|
};
|
||||||
|
|
||||||
|
const s = makeSession(
|
||||||
|
(session, msg, _, options) => {
|
||||||
|
const ab = new Uint8Array(msg).buffer;
|
||||||
|
const dv = new DataView(ab);
|
||||||
|
const cmd = CommandsByValue[dv.readU16()];
|
||||||
|
if (options.debug) {
|
||||||
|
console.log(`<< ${cmd}`);
|
||||||
|
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 1 }));
|
||||||
|
}
|
||||||
|
Handlers[cmd](session, dv);
|
||||||
|
},
|
||||||
|
(session) => {
|
||||||
|
const int = setInterval(() => {
|
||||||
|
let buf = new DataView(new Uint8Array(4).buffer);
|
||||||
|
create_LanSearch(buf);
|
||||||
|
session.broadcast(buf);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
{ debug: false, ansi: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
let cur_image_index = 0;
|
||||||
|
const audioFd = createWriteStream(`captures/audio.pcm`);
|
||||||
|
s.eventEmitter.on("frame", (frame: Buffer) => {
|
||||||
|
const fname = `captures/${cur_image_index.toString().padStart(4, "0")}.jpg`;
|
||||||
|
let cur_image = createWriteStream(fname);
|
||||||
|
cur_image_index++;
|
||||||
|
cur_image.write(frame);
|
||||||
|
cur_image.close();
|
||||||
|
console.log("got an entire frame", frame.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
s.eventEmitter.on("audio", (frame: Buffer) => {
|
||||||
|
audioFd.write(frame);
|
||||||
|
});
|
||||||
|
|
||||||
|
s.eventEmitter.on("connect", (name: string) => {
|
||||||
|
console.log(`Connected to ${name}`);
|
||||||
|
});
|
||||||
-214
@@ -1,214 +0,0 @@
|
|||||||
import { createSocket, RemoteInfo } from "node:dgram";
|
|
||||||
import EventEmitter from "node:events";
|
|
||||||
|
|
||||||
import { decode, encode } from "./encode.js";
|
|
||||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
|
||||||
import {
|
|
||||||
handle_Close,
|
|
||||||
handle_Drw,
|
|
||||||
handle_DrwAck,
|
|
||||||
handle_P2PAlive,
|
|
||||||
handle_P2PRdy,
|
|
||||||
makeP2pRdy,
|
|
||||||
notImpl,
|
|
||||||
noop,
|
|
||||||
} from "./handlers.js";
|
|
||||||
import { create_P2pAlive, DevSerial, SendStartVideo, SendVideoResolution, SendWifiDetails } from "./impl.js";
|
|
||||||
import { logger } from "./logger.js";
|
|
||||||
|
|
||||||
export type Session = {
|
|
||||||
send: (msg: DataView) => void;
|
|
||||||
ackDrw: (id: number) => void;
|
|
||||||
unackedDrw: { [id: number]: { sent_ts: number; data: DataView } };
|
|
||||||
outgoingCommandId: number;
|
|
||||||
ticket: number[];
|
|
||||||
eventEmitter: EventEmitter;
|
|
||||||
dst_ip: string;
|
|
||||||
lastReceivedPacket: number;
|
|
||||||
connected: boolean;
|
|
||||||
devName: string;
|
|
||||||
timers: ReturnType<typeof setInterval>[];
|
|
||||||
curImage: Buffer[];
|
|
||||||
rcvSeqId: number;
|
|
||||||
frame_is_bad: boolean;
|
|
||||||
frame_was_fixed: boolean;
|
|
||||||
started: boolean;
|
|
||||||
encoded: boolean;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PacketHandler = (session: Session, dv: DataView, rinfo: RemoteInfo) => void;
|
|
||||||
|
|
||||||
type msgCb = (
|
|
||||||
session: Session,
|
|
||||||
handlers: Record<keyof typeof Commands, PacketHandler>,
|
|
||||||
msg: Buffer,
|
|
||||||
rinfo: RemoteInfo,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
const handleIncoming: msgCb = (session, handlers, msg, rinfo) => {
|
|
||||||
const ab = new Uint8Array(msg).buffer;
|
|
||||||
let dv = new DataView(ab);
|
|
||||||
let firstByte = dv.readU8();
|
|
||||||
if (firstByte != 0xf1) {
|
|
||||||
dv = decode(dv);
|
|
||||||
firstByte = dv.readU8();
|
|
||||||
if (firstByte == 0xf1) {
|
|
||||||
// decoded into a legal command
|
|
||||||
session.encoded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const raw = dv.readU16();
|
|
||||||
const cmd = CommandsByValue[raw];
|
|
||||||
logger.log("trace", `<< ${cmd}`);
|
|
||||||
handlers[cmd](session, dv, rinfo);
|
|
||||||
if (raw != Commands.P2PAlive && raw != Commands.P2PAliveAck) {
|
|
||||||
session.lastReceivedPacket = Date.now();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const makeSession = (
|
|
||||||
handlers: Record<keyof typeof Commands, PacketHandler>,
|
|
||||||
dev: DevSerial,
|
|
||||||
ra: RemoteInfo,
|
|
||||||
onLogin: (s: Session) => void,
|
|
||||||
timeoutMs: number,
|
|
||||||
): Session => {
|
|
||||||
let unackedDrw = {};
|
|
||||||
const sock = createSocket("udp4");
|
|
||||||
|
|
||||||
sock.on("error", (err) => {
|
|
||||||
console.error(`sock error:\n${err.stack}`);
|
|
||||||
sock.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.on("message", (msg, rinfo) => handleIncoming(session, handlers, msg, rinfo));
|
|
||||||
|
|
||||||
sock.on("listening", () => {
|
|
||||||
const buf = makeP2pRdy(dev);
|
|
||||||
session.send(buf);
|
|
||||||
session.started = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.bind();
|
|
||||||
const sessTimer = setInterval(() => {
|
|
||||||
const delta = Date.now() - session.lastReceivedPacket;
|
|
||||||
if (session.started) {
|
|
||||||
if (delta > 600) {
|
|
||||||
let buf = create_P2pAlive();
|
|
||||||
session.send(buf);
|
|
||||||
}
|
|
||||||
if (delta > timeoutMs) {
|
|
||||||
logger.warning(`Camera ${session.devName} timed out`);
|
|
||||||
session.eventEmitter.emit("disconnect");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 400);
|
|
||||||
|
|
||||||
const resendTimer = setInterval(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [key, value] of Object.entries(session.unackedDrw)) {
|
|
||||||
const { sent_ts, data } = value;
|
|
||||||
if (now - sent_ts > 100) {
|
|
||||||
const pkt_id = data.add(6).readU16();
|
|
||||||
logger.debug(`Resending packet ${pkt_id} as ${session.outgoingCommandId}`);
|
|
||||||
data.add(6).writeU16(session.outgoingCommandId);
|
|
||||||
session.outgoingCommandId++;
|
|
||||||
delete session.unackedDrw[key];
|
|
||||||
session.send(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const session: Session = {
|
|
||||||
outgoingCommandId: 0,
|
|
||||||
ticket: [0, 0, 0, 0],
|
|
||||||
lastReceivedPacket: 0,
|
|
||||||
eventEmitter: new EventEmitter(),
|
|
||||||
connected: true,
|
|
||||||
timers: [sessTimer, resendTimer],
|
|
||||||
devName: dev.devId,
|
|
||||||
started: false,
|
|
||||||
send: (msg: DataView) => {
|
|
||||||
const raw = msg.readU16();
|
|
||||||
const cmd = CommandsByValue[raw];
|
|
||||||
// send command
|
|
||||||
if (raw == 0xf1d0 && msg.add(4).readU8() == 0xd1) {
|
|
||||||
const packet_id = msg.add(6).readU16();
|
|
||||||
logger.debug(`Sending Drw Packet with id ${packet_id}`);
|
|
||||||
unackedDrw[packet_id] = { sent_ts: Date.now(), data: msg };
|
|
||||||
}
|
|
||||||
logger.log("trace", `>> ${cmd}`);
|
|
||||||
if (session.encoded) {
|
|
||||||
msg = encode(msg);
|
|
||||||
}
|
|
||||||
sock.send(new Uint8Array(msg.buffer), ra.port, session.dst_ip);
|
|
||||||
},
|
|
||||||
ackDrw: (id: number) => {
|
|
||||||
logger.debug(`Removing ${id} from pending`);
|
|
||||||
delete unackedDrw[id];
|
|
||||||
},
|
|
||||||
dst_ip: ra.address,
|
|
||||||
curImage: [],
|
|
||||||
rcvSeqId: 0,
|
|
||||||
frame_is_bad: false,
|
|
||||||
frame_was_fixed: false,
|
|
||||||
encoded: false,
|
|
||||||
unackedDrw,
|
|
||||||
close: () => {
|
|
||||||
session.eventEmitter.emit("disconnect");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
session.eventEmitter.on("disconnect", () => {
|
|
||||||
logger.info(`Disconnected from camera ${session.devName} at ${session.dst_ip}`);
|
|
||||||
session.dst_ip = "0.0.0.0";
|
|
||||||
session.connected = false;
|
|
||||||
session.timers.forEach((x) => clearInterval(x));
|
|
||||||
session.timers = [];
|
|
||||||
sock.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
session.eventEmitter.on("login", () => {
|
|
||||||
logger.info(`Logging in to camera ${session.devName}`);
|
|
||||||
onLogin(session);
|
|
||||||
});
|
|
||||||
return session;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const configureWifi = (ssid: string, password: string, channel: number) => {
|
|
||||||
return (s: Session) => {
|
|
||||||
[SendWifiDetails(s, ssid, password, channel, true)].forEach(s.send);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const startVideoStream = (s: Session) => {
|
|
||||||
[
|
|
||||||
...SendVideoResolution(s, 2), // 640x480
|
|
||||||
SendStartVideo(s),
|
|
||||||
].forEach(s.send);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Handlers: Record<keyof typeof Commands, PacketHandler> = {
|
|
||||||
PunchPkt: notImpl,
|
|
||||||
P2PAlive: handle_P2PAlive,
|
|
||||||
P2pRdy: handle_P2PRdy,
|
|
||||||
DrwAck: handle_DrwAck,
|
|
||||||
Drw: handle_Drw,
|
|
||||||
Close: handle_Close,
|
|
||||||
|
|
||||||
P2PAliveAck: noop,
|
|
||||||
LanSearchExt: notImpl,
|
|
||||||
LanSearch: notImpl,
|
|
||||||
Hello: notImpl,
|
|
||||||
P2pReq: notImpl,
|
|
||||||
LstReq: notImpl,
|
|
||||||
PunchTo: notImpl,
|
|
||||||
HelloAck: notImpl,
|
|
||||||
RlyTo: notImpl,
|
|
||||||
DevLgnAck: notImpl,
|
|
||||||
P2PReqAck: notImpl,
|
|
||||||
ListenReqAck: notImpl,
|
|
||||||
RlyHelloAck: notImpl, // always
|
|
||||||
RlyHelloAck2: notImpl, // if len >1??
|
|
||||||
};
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import { parse } from "yaml";
|
|
||||||
|
|
||||||
interface HttpServerConfig {
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoggingConfig {
|
|
||||||
level: string;
|
|
||||||
use_color?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CameraConfig {
|
|
||||||
alias?: string;
|
|
||||||
rotate?: number;
|
|
||||||
mirror?: boolean;
|
|
||||||
audio?: boolean;
|
|
||||||
fix_packet_loss?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AppConfig {
|
|
||||||
http_server: HttpServerConfig;
|
|
||||||
logging: LoggingConfig;
|
|
||||||
cameras: Record<string, CameraConfig>;
|
|
||||||
discovery_ips: string[];
|
|
||||||
blacklisted_ips: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const DefaultConfig: AppConfig = {
|
|
||||||
http_server: { port: 5000 },
|
|
||||||
logging: { level: "info" },
|
|
||||||
cameras: {},
|
|
||||||
discovery_ips: ["192.168.1.255"],
|
|
||||||
blacklisted_ips: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = DefaultConfig;
|
|
||||||
|
|
||||||
export const loadConfig = (path: string) => {
|
|
||||||
const data = fs.readFileSync(path, { encoding: "utf-8" });
|
|
||||||
config = parse(data) as AppConfig;
|
|
||||||
config = { ...DefaultConfig, ...config };
|
|
||||||
};
|
|
||||||
|
|
||||||
export { config };
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
DataView.prototype.add = function (offset) {
|
DataView.prototype.add = function (offset) {
|
||||||
return new DataView(this.buffer, offset + this.byteOffset);
|
return new DataView(this.buffer, offset);
|
||||||
};
|
};
|
||||||
DataView.prototype.writeU8 = function (val) {
|
DataView.prototype.writeU8 = function (val) {
|
||||||
return this.setUint8(0, val);
|
return this.setUint8(0, val);
|
||||||
@@ -19,15 +19,9 @@ DataView.prototype.readU8 = function () {
|
|||||||
DataView.prototype.readU16 = function () {
|
DataView.prototype.readU16 = function () {
|
||||||
return this.getUint16(0);
|
return this.getUint16(0);
|
||||||
};
|
};
|
||||||
DataView.prototype.readU16LE = function () {
|
|
||||||
return this.getUint16(0, true);
|
|
||||||
};
|
|
||||||
DataView.prototype.readU32 = function () {
|
DataView.prototype.readU32 = function () {
|
||||||
return this.getUint32(0);
|
return this.getUint32(0);
|
||||||
};
|
};
|
||||||
DataView.prototype.readU32LE = function () {
|
|
||||||
return this.getUint32(0, true);
|
|
||||||
};
|
|
||||||
DataView.prototype.readU64 = function () {
|
DataView.prototype.readU64 = function () {
|
||||||
return this.getBigUint64(0);
|
return this.getBigUint64(0);
|
||||||
};
|
};
|
||||||
@@ -50,45 +44,23 @@ DataView.prototype.readByteArray = function (len) {
|
|||||||
};
|
};
|
||||||
DataView.prototype.readString = function (len) {
|
DataView.prototype.readString = function (len) {
|
||||||
const ba = this.readByteArray(len);
|
const ba = this.readByteArray(len);
|
||||||
const s = String.fromCharCode.apply(null, new Uint8Array(ba.buffer));
|
return String.fromCharCode.apply(null, new Uint8Array(ba.buffer));
|
||||||
const nullByte = s.indexOf("\0");
|
|
||||||
if (nullByte !== -1) return s.substring(0, nullByte);
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
DataView.prototype.writeString = function (str) {
|
|
||||||
const bytes = [...str].map((_, i) => str.charCodeAt(i));
|
|
||||||
return this.writeByteArray(bytes);
|
|
||||||
};
|
|
||||||
DataView.prototype.startsWith = function (arr) {
|
|
||||||
if (this.byteLength < arr.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
if (this.add(i).readU8() != arr[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface DataView {
|
interface DataView {
|
||||||
add(offset: number): DataView;
|
add(offset: number): DataView;
|
||||||
readByteArray(len: number): DataView;
|
readByteArray(len: number): DataView;
|
||||||
writeString(str: string): void;
|
|
||||||
readString(len: number): string;
|
readString(len: number): string;
|
||||||
readU16(): number;
|
readU16(): number;
|
||||||
readU16LE(): number;
|
|
||||||
readU32(): number;
|
readU32(): number;
|
||||||
readU32LE(): number;
|
readU64(): number;
|
||||||
readU64(): bigint;
|
|
||||||
readU8(): number;
|
readU8(): number;
|
||||||
writeByteArray(arr: Uint8Array | number[]): void;
|
writeByteArray(arr: Uint8Array | number[]): undefined;
|
||||||
writeU16(n: number): void;
|
writeU16(n: number): undefined;
|
||||||
writeU32(n: number): void;
|
writeU32(n: number): undefined;
|
||||||
writeU64(n: bigint): void;
|
writeU64(n: number): undefined;
|
||||||
writeU8(n: number): void;
|
writeU8(n: number): undefined;
|
||||||
startsWith(arr: number[]): boolean;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default global;
|
export default global;
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import assert from "assert";
|
|
||||||
import { buildLogger } from "../logger.js";
|
|
||||||
|
|
||||||
import { decode, encode } from "../encode.js";
|
|
||||||
|
|
||||||
const hstrToU8 = (hs) => new Uint8Array(hs.match(/../g).map((h) => parseInt(h, 16)));
|
|
||||||
describe("encode/decode", () => {
|
|
||||||
it("decodes example", () => {
|
|
||||||
buildLogger("trace");
|
|
||||||
const pkt = "2ccb6293bf2321ed0ad7ea318106e0d5a28d800233207369";
|
|
||||||
const expected = "f141001444474f4100000000000e3f854e44424e44000000";
|
|
||||||
const pktbuf = hstrToU8(pkt);
|
|
||||||
const expbuf = hstrToU8(expected);
|
|
||||||
|
|
||||||
assert.deepEqual(decode(new DataView(pktbuf.buffer)), new DataView(expbuf.buffer));
|
|
||||||
});
|
|
||||||
it("encodes example", () => {
|
|
||||||
buildLogger("trace");
|
|
||||||
const pkt = "f141001444474f4100000000000e3f854e44424e44000000";
|
|
||||||
const expected = "2ccb6293bf2321ed0ad7ea318106e0d5a28d800233207369";
|
|
||||||
const pktbuf = hstrToU8(pkt);
|
|
||||||
const expbuf = hstrToU8(expected);
|
|
||||||
|
|
||||||
assert.deepEqual(encode(new DataView(pktbuf.buffer)), new DataView(expbuf.buffer));
|
|
||||||
});
|
|
||||||
it("roundtrips encode/decode", () => {
|
|
||||||
buildLogger("trace");
|
|
||||||
const pkt = "f141001444474f4100000000000e3f854e44424e44000000";
|
|
||||||
const pktbuf = hstrToU8(pkt);
|
|
||||||
const encoded = encode(new DataView(pktbuf.buffer));
|
|
||||||
assert.deepEqual(decode(encoded), new DataView(pktbuf.buffer));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
+10
-165
@@ -1,13 +1,11 @@
|
|||||||
// vim: nowrap
|
|
||||||
import "../shim.ts";
|
import "../shim.ts";
|
||||||
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
|
||||||
import { XqBytesDec, XqBytesEnc } from "../func_replacements.js";
|
import { XqBytesDec, XqBytesEnc } from "../func_replacements.js";
|
||||||
import { makeP2pRdy, parseDevStatusAck, parseListWifi } from "../handlers.js";
|
import { createResponseForControlCommand } from "../handlers.js";
|
||||||
import { hexdump } from "../hexdump.js";
|
import { hexdump } from "../hexdump.js";
|
||||||
import { parse_PunchPkt, SendDevStatus, SendStartVideo, SendUsrChk, SendWifiDetails } from "../impl.ts";
|
import { SendUsrChk } from "../impl.ts";
|
||||||
import { buildLogger } from "../logger.js";
|
|
||||||
import { placeholderTypes, sprintf } from "../utils.js";
|
import { placeholderTypes, sprintf } from "../utils.js";
|
||||||
|
|
||||||
describe("debug_tools", () => {
|
describe("debug_tools", () => {
|
||||||
@@ -108,13 +106,6 @@ describe("module", () => {
|
|||||||
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
||||||
assert.deepEqual(new Uint8Array(in_buf.buffer), long_enc_bytes);
|
assert.deepEqual(new Uint8Array(in_buf.buffer), long_enc_bytes);
|
||||||
});
|
});
|
||||||
/* TODO
|
|
||||||
it("decrypts offset dataviews", () => {
|
|
||||||
const in_buf = new DataView(simple_enc_bytes.buffer.slice(0));
|
|
||||||
XqBytesDec(in_buf, simple_enc_bytes.byteLength, 4); // this mutates in_buf
|
|
||||||
assert.deepEqual(new Uint8Array(in_buf.buffer), simple_dec_bytes);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
it("reverts Enc with Dec", () => {
|
it("reverts Enc with Dec", () => {
|
||||||
const in_buf = new DataView(long_dec_bytes.buffer.slice(0));
|
const in_buf = new DataView(long_dec_bytes.buffer.slice(0));
|
||||||
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
||||||
@@ -125,169 +116,23 @@ describe("module", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hstrToBA = (hs) => new Uint8Array(hs.match(/../g).map((h) => parseInt(h, 16))).buffer;
|
const hstrToBA = (hs) => new Uint8Array(hs.match(/../g).map((h) => parseInt(h, 16))).buffer;
|
||||||
const BATohstr = (ba) => [...new Uint8Array(ba.buffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
||||||
describe("parse packet", () => {
|
|
||||||
it("parses PunchPkt", () => {
|
|
||||||
const in_pkt_str = "f14100144241544400000000000262ca574f4e4a4d000000";
|
|
||||||
const pkt = new DataView(hstrToBA(in_pkt_str));
|
|
||||||
const expected = {
|
|
||||||
prefix: "BATD",
|
|
||||||
serial: "156362",
|
|
||||||
suffix: "WONJM",
|
|
||||||
serialU64: BigInt(156362),
|
|
||||||
devId: "BATD156362WONJM",
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.deepEqual(parse_PunchPkt(pkt), expected);
|
|
||||||
});
|
|
||||||
{
|
|
||||||
const in_pkt_str = "f14100145848410000000000000003e24b4d4d4542000000";
|
|
||||||
const pkt = new DataView(hstrToBA(in_pkt_str));
|
|
||||||
it("parses PunchPkt when prefix is 3 letters long", () => {
|
|
||||||
const expected = {
|
|
||||||
prefix: "XHA",
|
|
||||||
serial: "994",
|
|
||||||
suffix: "KMMEB",
|
|
||||||
serialU64: BigInt(994),
|
|
||||||
devId: "XHA994KMMEB",
|
|
||||||
};
|
|
||||||
assert.deepEqual(parse_PunchPkt(pkt), expected);
|
|
||||||
});
|
|
||||||
// https://github.com/DavidVentura/cam-reverse/issues/17#issuecomment-2094819873
|
|
||||||
it("replies properly to PunchPkt with 3-letters-long prefix", () => {
|
|
||||||
const dev = parse_PunchPkt(pkt);
|
|
||||||
const p2prdy = makeP2pRdy(dev);
|
|
||||||
let p2pstr = BATohstr(p2prdy);
|
|
||||||
|
|
||||||
assert.deepEqual(in_pkt_str.slice(8), p2pstr.slice(8));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
it("parses wifiscan chan0", () => {
|
|
||||||
buildLogger("warning");
|
|
||||||
const in_pkt_str =
|
|
||||||
"f1d00238d1000009110a03612c020100060000002f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101a741a15822a1010101010101b2fefefe650101010101010101010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ab41a158605c010101010101b6fefefe650101010101010101010101" +
|
|
||||||
"404253422f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101a741a158605c010101010101c3fefefe650101010101010101010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101af41a15822a1010101010101c8fefefe650101010101010101010101" +
|
|
||||||
"404253422f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101933aac0b5330010101010101b4fefefe650101010101010101010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101973aac0b5330010101010101b4fefefe650101010101010101010101" +
|
|
||||||
"40425342"; // incomplete entry
|
|
||||||
let pkt = new DataView(hstrToBA(in_pkt_str));
|
|
||||||
let payload_len = pkt.add(0xc).readU16LE();
|
|
||||||
XqBytesDec(pkt.add(20), payload_len - 4, 4); // this mutates pkt
|
|
||||||
const expected = [
|
|
||||||
{ channel: 0, dbm0: 4294967219, dbm1: 100, mac: "a6:40:a0:59:23:a0", mode: 0, security: 0, ssid: "ACRC.NET" },
|
|
||||||
{ channel: 0, dbm0: 4294967223, dbm1: 100, mac: "aa:40:a0:59:61:5d", mode: 0, security: 0, ssid: "ACRC.Guest" },
|
|
||||||
{ channel: 0, dbm0: 4294967234, dbm1: 100, mac: "a6:40:a0:59:61:5d", mode: 0, security: 0, ssid: "ACRC.NET" },
|
|
||||||
{ channel: 0, dbm0: 4294967241, dbm1: 100, mac: "ae:40:a0:59:23:a0", mode: 0, security: 0, ssid: "ACRC.Guest" },
|
|
||||||
{ channel: 0, dbm0: 4294967221, dbm1: 100, mac: "92:3b:ad:0a:52:31", mode: 0, security: 0, ssid: "ACRC.NET" },
|
|
||||||
{ channel: 0, dbm0: 4294967221, dbm1: 100, mac: "96:3b:ad:0a:52:31", mode: 0, security: 0, ssid: "ACRC.Guest" },
|
|
||||||
];
|
|
||||||
|
|
||||||
assert.deepEqual(parseListWifi(pkt), expected);
|
|
||||||
});
|
|
||||||
it("parses wifiscan chan2", () => {
|
|
||||||
buildLogger("warning");
|
|
||||||
const in_pkt_str =
|
|
||||||
"f1d00404d100000d110a0361f80300000b0000002f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101603735316031340101010101c701010165010101010101010301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101606035316031340101010101c7010101650101010101010103010101" + // frame
|
|
||||||
"404253422f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101603735316031340101010101c2010101650101010101010103010101" +
|
|
||||||
"01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101676460373736320101010101b1010101650101010101010103010101" +
|
|
||||||
"404253422f4f44550101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101383332636065310101010101af010101650101010101010103010101" +
|
|
||||||
"01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101393764606465350101010101af010101650101010101010103010101" +
|
|
||||||
"01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101326460603964670101010101ac010101650101010101010103010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101606435316031340101010101c7010101650101010101010103010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101606035316031340101010101c2010101650101010101010103010101" +
|
|
||||||
"01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101643562323360620101010101b3010101650101010101010103010101" +
|
|
||||||
"404253422f4674647275010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101383732636065310101010101af010101650101010101010103010101" +
|
|
||||||
"40425342"; // incomplete
|
|
||||||
let pkt = new DataView(hstrToBA(in_pkt_str));
|
|
||||||
let payload_len = pkt.add(0xc).readU16LE();
|
|
||||||
XqBytesDec(pkt.add(20), payload_len - 4, 4); // this mutates pkt
|
|
||||||
const expected = [
|
|
||||||
{ ssid: "ACRC.NET", mac: "61:36:34:30:61:30", security: 0, dbm0: 198, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "", mac: "61:61:34:30:61:30", security: 0, dbm0: 198, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "ACRC.NET", mac: "61:36:34:30:61:30", security: 0, dbm0: 195, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "", mac: "66:65:61:36:36:37", security: 0, dbm0: 176, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "ACRC.NET", mac: "39:32:33:62:61:64", security: 0, dbm0: 174, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "", mac: "38:36:65:61:65:64", security: 0, dbm0: 174, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "", mac: "33:65:61:61:38:65", security: 0, dbm0: 173, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "ACRC.Guest", mac: "61:65:34:30:61:30", security: 0, dbm0: 198, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "ACRC.Guest", mac: "61:61:34:30:61:30", security: 0, dbm0: 195, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "", mac: "65:34:63:33:32:61", security: 0, dbm0: 178, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
{ ssid: "ACRC.Guest", mac: "39:36:33:62:61:64", security: 0, dbm0: 174, dbm1: 100, mode: 0, channel: 2 },
|
|
||||||
];
|
|
||||||
|
|
||||||
assert.deepEqual(parseListWifi(pkt), expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("make packet", () => {
|
describe("make packet", () => {
|
||||||
it("builds a good SendUsrChk", () => {
|
it("builds a good SendUsrChk", () => {
|
||||||
const expected_str =
|
const expected_str =
|
||||||
"f1d000b0d1000000110a2010a400ff00000000006f01010101010101010101010101010101010101010101010101010160656c686f01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010160656c68";
|
"f1d000b0d1000000110a2010a400ff00000000006f01010101010101010101010101010101010101010101010101010160656c686f01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010160656c68";
|
||||||
const expected = hstrToBA(expected_str);
|
const expected = hstrToBA(expected_str);
|
||||||
const sess = { outgoingCommandId: 0, ticket: [0, 0, 0, 0] };
|
assert.deepEqual(SendUsrChk("admin", "admin").buffer, expected);
|
||||||
assert.deepEqual(SendUsrChk(sess, "admin", "admin").buffer, expected);
|
|
||||||
});
|
});
|
||||||
it("builds a good SendStartVideo", () => {
|
it("builds a good SendStartVideo", () => {
|
||||||
const _expected_str = "f1d00010d1000000110a10300400000001020304";
|
const input_pkt_str = "f1d00018d1000000110a20110c00ff000000000064504737fe010101";
|
||||||
|
// token-in = 0x64 0x50 0x47 0x37
|
||||||
|
const _expected_str = "f1d00114d1000000110a1030080100006551463601010101";
|
||||||
|
// output is 0x3010; 'start video'; hardcoded but shouldnt
|
||||||
const expected = hstrToBA(_expected_str);
|
const expected = hstrToBA(_expected_str);
|
||||||
|
|
||||||
const sess = { outgoingCommandId: 0, ticket: [1, 2, 3, 4] };
|
const sess = { outgoingCommandId: 0, ticket: [0, 0, 0, 0] };
|
||||||
const got = SendStartVideo(sess);
|
const got = createResponseForControlCommand(sess, new DataView(hstrToBA(input_pkt_str)));
|
||||||
assert.deepEqual(got.buffer, expected);
|
assert.deepEqual(got.buffer, expected);
|
||||||
});
|
assert.deepEqual(sess.ticket, [0x65, 0x51, 0x46, 0x36]);
|
||||||
it("builds a good SendDevStatus", () => {
|
|
||||||
const sess = { outgoingCommandId: 0, ticket: [1, 2, 3, 4] };
|
|
||||||
const _expected_str = "f1d00010d1000000110a08100400000001020304";
|
|
||||||
const expected = hstrToBA(_expected_str);
|
|
||||||
|
|
||||||
const got = SendDevStatus(sess);
|
|
||||||
assert.deepEqual(got.buffer, expected);
|
|
||||||
});
|
|
||||||
it("builds a good WifiSettingsSet", () => {
|
|
||||||
const sess = { outgoingCommandId: 2, ticket: [1, 2, 3, 4] };
|
|
||||||
const _expected_str =
|
|
||||||
"f1d00118d1000002110a01600c01000001020304" + // drw header + ticket
|
|
||||||
"0101010101010101010101010101010100010101" + // all zeroes, but DHCP u32
|
|
||||||
// set to 1
|
|
||||||
"726a786f64750101010101010101010101010101010101010101010101010101" + // ssid
|
|
||||||
"7274716473627360710101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" + // pass
|
|
||||||
"312f312f312f31010101010101010101" + // 0.0.0.0
|
|
||||||
"312f3334342f3334342f333434010101" + // 0.255.255.255
|
|
||||||
"312f312f312f31010101010101010101" + // 0.0.0.0
|
|
||||||
"312f312f312f31010101010101010101" + // 0.0.0.0
|
|
||||||
"312f312f312f31010101010101010101" + // 0.0.0.0
|
|
||||||
"01010101"; // 0000
|
|
||||||
const expected = hstrToBA(_expected_str);
|
|
||||||
|
|
||||||
const got = SendWifiDetails(sess, "skynet", "supercrap", 0, true);
|
|
||||||
assert.deepEqual(got.buffer, expected);
|
|
||||||
});
|
|
||||||
it("builds a good WifiSettingsSet - with channel", () => {
|
|
||||||
const sess = { outgoingCommandId: 0xc, ticket: [0x30, 0x35, 0x74, 0x72] };
|
|
||||||
const _expected_str =
|
|
||||||
"f1d00118d100000c110a01600c010000303574720101010101010101030101010101010100010101404253422f4f445501010101010101010101010101010101010101010101010167606a6471607272010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101312f312f312f31010101010101010101312f3334342f3334342f333434010101312f312f312f31010101010101010101312f312f312f31010101010101010101312f312f312f3101010101010101010101010101";
|
|
||||||
const expected = hstrToBA(_expected_str);
|
|
||||||
|
|
||||||
const got = SendWifiDetails(sess, "ACRC.NET", "fakepass", 2, true);
|
|
||||||
assert.deepEqual(got.buffer, expected);
|
|
||||||
});
|
|
||||||
it("parses devstatusack", () => {
|
|
||||||
const pkt = new DataView(
|
|
||||||
hstrToBA(
|
|
||||||
"f1d0008cd1000009110a08118000000000000000190e010101010101fefefefebefefefe01000001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010100010101010001030101010101010101010101010101fefefefefe010101010101010134030300",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let payload_len = pkt.add(0xc).readU16LE();
|
|
||||||
XqBytesDec(pkt.add(20), payload_len - 4, 0); // this mutates pkt
|
|
||||||
const got = parseDevStatusAck(pkt);
|
|
||||||
const expected = {
|
|
||||||
battery_mV: 0,
|
|
||||||
charging: false,
|
|
||||||
dbm: -256,
|
|
||||||
swver: "0.0.15.24",
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.deepEqual(expected, got);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { mockServer } from "../mock_server.js";
|
|
||||||
import { discoverDevices } from "../discovery.js";
|
|
||||||
import { buildLogger } from "../logger.js";
|
|
||||||
import { Commands } from "../datatypes.js";
|
|
||||||
|
|
||||||
import assert from "assert";
|
|
||||||
|
|
||||||
const hstrToU8 = (hs) => new Uint8Array(hs.match(/../g).map((h) => parseInt(h, 16)));
|
|
||||||
describe("integration", () => {
|
|
||||||
it("discovers a device", () => {
|
|
||||||
// LanSearch (server) -> PunchPkt (camera) -> discovered serial
|
|
||||||
buildLogger("trace");
|
|
||||||
const EXPECTED_SERIAL = "BATD156362WONJM";
|
|
||||||
const punchPkt = "f14100144241544400000000000262ca574f4e4a4d000000";
|
|
||||||
const mockSock = mockServer((msg) => {
|
|
||||||
const cmd = msg.readU16();
|
|
||||||
if (cmd == Commands.LanSearch) {
|
|
||||||
const buf = hstrToU8(punchPkt);
|
|
||||||
return [buf];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
const ev = discoverDevices(["127.0.0.1"]);
|
|
||||||
ev.on("discover", (rinfo, dev) => {
|
|
||||||
assert.deepEqual(dev.devId, EXPECTED_SERIAL);
|
|
||||||
ev.emit("close");
|
|
||||||
mockSock.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// TODO
|
|
||||||
it("emits login event upon logging in", () => {
|
|
||||||
// LanSearch (server) -> PunchPkt (camera)
|
|
||||||
// vvv need to call makeSession vvv
|
|
||||||
// P2pRdy (server) -> P2pRdy (camera)
|
|
||||||
// Drw<Login> (server) -> [DrwAck, Drw<LoginAck>]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
+2
-8
@@ -1,16 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
"include": ["*.ts"],
|
"include": ["*.ts"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"module": "es6",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"preserveConstEnums": true,
|
"moduleResolution": "node"
|
||||||
"noEmit": true,
|
|
||||||
"rootDir": "./",
|
|
||||||
"strict": false,
|
|
||||||
"sourceMap": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-19
@@ -2,7 +2,6 @@ struct avparamset_t {
|
|||||||
uint32_t paramType;
|
uint32_t paramType;
|
||||||
uint32_t paramValue;
|
uint32_t paramValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct datetime_t {
|
struct datetime_t {
|
||||||
uint32_t now; // the code to _write_ these uses long ))
|
uint32_t now; // the code to _write_ these uses long ))
|
||||||
uint32_t tz;
|
uint32_t tz;
|
||||||
@@ -100,21 +99,3 @@ struct stream_head_t {
|
|||||||
short sample;
|
short sample;
|
||||||
short index;
|
short index;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct tag_wifiParams { /* PlaceHolder Structure */
|
|
||||||
int enable;
|
|
||||||
int noidea; // wifi status?
|
|
||||||
int mode;
|
|
||||||
int chan;
|
|
||||||
int auth;
|
|
||||||
int dhcp;
|
|
||||||
char ssid[32];
|
|
||||||
char psk[128];
|
|
||||||
char ip[16];
|
|
||||||
char mask[16];
|
|
||||||
char gw[16];
|
|
||||||
char dns1[16];
|
|
||||||
char dns2[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export const sprintf = (str, values) => {
|
|||||||
.join("");
|
.join("");
|
||||||
return s + str.slice(lastScanned);
|
return s + str.slice(lastScanned);
|
||||||
};
|
};
|
||||||
export const u32_swap = (x) =>
|
|
||||||
((x & 0xff000000) >> 24) | ((x & 0xff0000) >> 8) | ((x & 0xff00) << 8) | ((x & 0xff) << 24);
|
|
||||||
export const u16_swap = (x) => ((x & 0xff00) >> 8) | ((x & 0x00ff) << 8);
|
export const u16_swap = (x) => ((x & 0xff00) >> 8) | ((x & 0x00ff) << 8);
|
||||||
export const swap_endianness_u16 = (ptr) => {
|
export const swap_endianness_u16 = (ptr) => {
|
||||||
const bytes = ptr.readU16();
|
const bytes = ptr.readU16();
|
||||||
|
|||||||
Reference in New Issue
Block a user