@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -12
- package/dist/client/device-registry.d.ts +343 -0
- package/dist/client/eufy-mega.d.ts +872 -0
- package/dist/client/index.d.ts +1 -6
- package/dist/client/map-channels.d.ts +22 -0
- package/dist/client/types.d.ts +383 -0
- package/dist/core/contracts.d.ts +898 -0
- package/dist/core/crypto.d.ts +98 -0
- package/dist/core/index.d.ts +9 -7
- package/dist/core/logger.d.ts +53 -0
- package/dist/core/lz4-block.d.ts +35 -0
- package/dist/core/raw-dp-hex.d.ts +32 -0
- package/dist/core/raw-dp-writer.d.ts +83 -0
- package/dist/core/store.d.ts +43 -0
- package/dist/core/types.d.ts +169 -0
- package/dist/core/util.d.ts +78 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.js +25526 -14
- package/dist/index.js.map +7 -1
- package/dist/model/capabilities/access.d.ts +127 -0
- package/dist/model/capabilities/arming.d.ts +201 -0
- package/dist/model/capabilities/audio.d.ts +154 -0
- package/dist/model/capabilities/battery.d.ts +363 -0
- package/dist/model/capabilities/camera.d.ts +564 -0
- package/dist/model/capabilities/co.d.ts +40 -0
- package/dist/model/capabilities/contact.d.ts +133 -0
- package/dist/model/capabilities/doorbell.d.ts +345 -0
- package/dist/model/capabilities/dp-catalog.d.ts +38 -0
- package/dist/model/capabilities/index.d.ts +561 -0
- package/dist/model/capabilities/info.d.ts +28 -0
- package/dist/model/capabilities/keypad.d.ts +61 -0
- package/dist/model/capabilities/leak.d.ts +43 -0
- package/dist/model/capabilities/light.d.ts +174 -0
- package/dist/model/capabilities/locate.d.ts +63 -0
- package/dist/model/capabilities/lock.d.ts +242 -0
- package/dist/model/capabilities/manifest.d.ts +107 -0
- package/dist/model/capabilities/members.d.ts +647 -0
- package/dist/model/capabilities/motion.d.ts +377 -0
- package/dist/model/capabilities/person-detection.d.ts +8 -0
- package/dist/model/capabilities/ptz.d.ts +289 -0
- package/dist/model/capabilities/rtsp.d.ts +221 -0
- package/dist/model/capabilities/siren.d.ts +218 -0
- package/dist/model/capabilities/smart-light.d.ts +172 -0
- package/dist/model/capabilities/smoke.d.ts +40 -0
- package/dist/model/capabilities/snapshot.d.ts +6 -0
- package/dist/model/capabilities/storage.d.ts +11 -0
- package/dist/model/capabilities/suction.d.ts +104 -0
- package/dist/model/capabilities/types.d.ts +484 -0
- package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
- package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
- package/dist/model/capabilities/video.d.ts +6 -0
- package/dist/model/classify.d.ts +78 -0
- package/dist/model/clean-record-detail.d.ts +65 -0
- package/dist/model/clean-records.d.ts +69 -0
- package/dist/model/device-family.d.ts +73 -0
- package/dist/model/device-types.d.ts +123 -0
- package/dist/model/device.d.ts +265 -0
- package/dist/model/index.d.ts +29 -4
- package/dist/model/infer.d.ts +23 -0
- package/dist/model/inspect.d.ts +61 -0
- package/dist/model/life-params.d.ts +21 -0
- package/dist/model/map-pixels.d.ts +70 -0
- package/dist/model/param-dictionary.d.ts +28 -0
- package/dist/model/param-namespace.d.ts +21 -0
- package/dist/model/proto-read.d.ts +53 -0
- package/dist/model/push-events.d.ts +147 -0
- package/dist/model/registry.d.ts +54 -0
- package/dist/model/types.d.ts +301 -0
- package/dist/model/vacuum-map-store.d.ts +92 -0
- package/dist/model/vacuum-map.d.ts +286 -0
- package/dist/model/vacuum-scenes.d.ts +76 -0
- package/dist/model/vacuum-schedules.d.ts +85 -0
- package/dist/transport/dp-preset.d.ts +102 -0
- package/dist/transport/ff09.d.ts +444 -0
- package/dist/transport/ffmpeg.d.ts +86 -0
- package/dist/transport/http/decodeImageV1.d.ts +20 -0
- package/dist/transport/http/decodeImageV2.d.ts +19 -0
- package/dist/transport/http/index.d.ts +5 -0
- package/dist/transport/http/light-catalog.d.ts +62 -0
- package/dist/transport/http/media-download.d.ts +14 -0
- package/dist/transport/http/mega-client.d.ts +514 -0
- package/dist/transport/http/phone-model.d.ts +21 -0
- package/dist/transport/index.d.ts +10 -7
- package/dist/transport/mqtt/app-client-id.d.ts +16 -0
- package/dist/transport/mqtt/availability.d.ts +14 -0
- package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
- package/dist/transport/mqtt/biz-stream.d.ts +98 -0
- package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
- package/dist/transport/mqtt/clean-codec.d.ts +14 -0
- package/dist/transport/mqtt/command-router.d.ts +285 -0
- package/dist/transport/mqtt/dp-codec.d.ts +58 -0
- package/dist/transport/mqtt/dp-color.d.ts +14 -0
- package/dist/transport/mqtt/engine.d.ts +16 -0
- package/dist/transport/mqtt/index.d.ts +5 -0
- package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
- package/dist/transport/mqtt/topics.d.ts +80 -0
- package/dist/transport/p2p/adts.d.ts +91 -0
- package/dist/transport/p2p/annexb.d.ts +124 -0
- package/dist/transport/p2p/codec.d.ts +166 -0
- package/dist/transport/p2p/command-router.d.ts +655 -0
- package/dist/transport/p2p/commands.d.ts +550 -0
- package/dist/transport/p2p/envelope.d.ts +46 -0
- package/dist/transport/p2p/fmp4.d.ts +89 -0
- package/dist/transport/p2p/fragment-recording.d.ts +33 -0
- package/dist/transport/p2p/index.d.ts +13 -0
- package/dist/transport/p2p/lan-ip.d.ts +25 -0
- package/dist/transport/p2p/live-stream.d.ts +223 -0
- package/dist/transport/p2p/live-trace.d.ts +123 -0
- package/dist/transport/p2p/media.d.ts +105 -0
- package/dist/transport/p2p/p2p-session.d.ts +620 -0
- package/dist/transport/p2p/readable-egress.d.ts +27 -0
- package/dist/transport/p2p/session-manager.d.ts +154 -0
- package/dist/transport/p2p/shared-live-source.d.ts +431 -0
- package/dist/transport/p2p/talkback.d.ts +187 -0
- package/dist/transport/p2p/video.d.ts +150 -0
- package/dist/transport/p2p/write-commands.d.ts +21 -0
- package/dist/transport/protobuf.d.ts +5 -0
- package/dist/transport/push/fcm.d.ts +23 -0
- package/dist/transport/push/index.d.ts +6 -0
- package/dist/transport/push/message-tags.d.ts +26 -0
- package/dist/transport/push/parser.d.ts +27 -0
- package/dist/transport/push/proto.d.ts +11 -0
- package/dist/transport/push/push-client.d.ts +64 -0
- package/dist/transport/push/store.d.ts +23 -0
- package/dist/transport/push/types.d.ts +180 -0
- package/dist/transport/raw-dp.d.ts +6 -0
- package/dist/transport/stored-image-cache.d.ts +23 -0
- package/dist/transport/tuya/account.d.ts +44 -0
- package/dist/transport/tuya/client.d.ts +89 -0
- package/dist/transport/tuya/command-router.d.ts +79 -0
- package/dist/transport/tuya/dp-codec.d.ts +56 -0
- package/dist/transport/tuya/index.d.ts +28 -0
- package/dist/transport/tuya/request.d.ts +157 -0
- package/dist/transport/tuya/sign.d.ts +64 -0
- package/package.json +12 -13
- package/dist/client/index.js +0 -2
- package/dist/client/index.js.map +0 -1
- package/dist/core/index.js +0 -2
- package/dist/core/index.js.map +0 -1
- package/dist/model/index.js +0 -2
- package/dist/model/index.js.map +0 -1
- package/dist/transport/index.js +0 -2
- package/dist/transport/index.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure MQTT ("smqtt") — Anker AIoT broker, mutual-TLS.
|
|
3
|
+
*
|
|
4
|
+
* Broker: mqtts://aiot-mqtt-{region}.anker.com:8883. Credentials come from the
|
|
5
|
+
* mega API `get_user_mqtt_info` call: a per-user client certificate + RSA key,
|
|
6
|
+
* and a server CA (the `aws_root_ca1_pem` field, despite the name, is a GoDaddy
|
|
7
|
+
* root). The default clientId is the cert CN = `{user_id}-{app_name}`.
|
|
8
|
+
*
|
|
9
|
+
* Topic vocabulary and the per-line credential scope live in `./topics.ts` — publish to `/req`
|
|
10
|
+
* (cloud→device commands), subscribe to whichever inbound legs that line uses. Granted for eufy_mega +
|
|
11
|
+
* eufy_home devices; eufy_security (cameras) are DENIED here (they use P2P) — see src/types.ts
|
|
12
|
+
* classifyDevice.
|
|
13
|
+
*/
|
|
14
|
+
import { EventEmitter } from "node:events";
|
|
15
|
+
import type { EufyDevice, RealtimeTransport } from "../../core/types.js";
|
|
16
|
+
import { type Logger } from "../../core/logger.js";
|
|
17
|
+
/**
|
|
18
|
+
* Per-user mTLS credentials as returned by get_user_mqtt_info.
|
|
19
|
+
*
|
|
20
|
+
* Broker credentials as the cloud returns them — internal transport detail.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export interface SecureMqttCredentials {
|
|
24
|
+
/** Broker host, e.g. aiot-mqtt-eu.anker.com (port assumed 8883). */
|
|
25
|
+
endpoint_addr: string;
|
|
26
|
+
endpoint_port?: number;
|
|
27
|
+
/** PEM client certificate (CN = {user_id}-{app_name}). */
|
|
28
|
+
certificate_pem: string;
|
|
29
|
+
/** PEM client private key. */
|
|
30
|
+
private_key: string;
|
|
31
|
+
/** PEM server CA (field is misleadingly named aws_root_ca1_pem — it's a GoDaddy root). */
|
|
32
|
+
aws_root_ca1_pem: string;
|
|
33
|
+
/** Default clientId (cert CN = thing_name). */
|
|
34
|
+
thing_name?: string;
|
|
35
|
+
/** App scope the cert is granted for (the `{app_name}` topic segment) — e.g. `eufy_mega`. */
|
|
36
|
+
app_name?: string;
|
|
37
|
+
/** The account/user id the cert is bound to. */
|
|
38
|
+
user_id?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Construction options for the internal broker client.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export interface SecureMqttOptions {
|
|
45
|
+
credentials: SecureMqttCredentials;
|
|
46
|
+
clientId?: string;
|
|
47
|
+
/** Dial this IP directly instead of resolving `credentials.endpoint_addr` via DNS — the broker
|
|
48
|
+
* hostname fronts multiple independent backend instances that do NOT share subscribe/publish
|
|
49
|
+
* routing (see `transport/mqtt/broker-discovery.ts`); pin to the instance a device's session was
|
|
50
|
+
* confirmed to be on, found via {@link discoverReachableInstance}. */
|
|
51
|
+
instanceIp?: string;
|
|
52
|
+
/**
|
|
53
|
+
* mqtt.js's own `reconnectPeriod` (ms) — auto-reconnect on an unexpected drop. Default `5000`,
|
|
54
|
+
* right for the one long-lived connection (the facade's auto-started `this.transport`). A **one-shot**
|
|
55
|
+
* connection (opened for a single command, torn down right after — see `MqttCommandRouter.ensureSecurityMqttFor`)
|
|
56
|
+
* must pass `0` here: with a nonzero period, a connect that never establishes (or drops right after)
|
|
57
|
+
* leaves the underlying mqtt.js client retrying against that instance forever, orphaned in the
|
|
58
|
+
* background — {@link SecureMqtt.connect} rejecting/resolving doesn't stop it, only `end()` does, and
|
|
59
|
+
* a one-shot caller has no reason to ever call `end()` again after its one command is done.
|
|
60
|
+
*/
|
|
61
|
+
reconnectPeriod?: number;
|
|
62
|
+
/** Diagnostics sink. Omit for silence. */
|
|
63
|
+
logger?: Logger;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The broker client. Internal transport; a host reaches appliances through the capability surface.
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export declare class SecureMqtt extends EventEmitter implements RealtimeTransport {
|
|
70
|
+
readonly kind: "smqtt";
|
|
71
|
+
private client?;
|
|
72
|
+
private readonly o;
|
|
73
|
+
private readonly logger;
|
|
74
|
+
constructor(opts: SecureMqttOptions);
|
|
75
|
+
get id(): string;
|
|
76
|
+
/**
|
|
77
|
+
* Open the broker connection, resolving once it is established. Pinned to a broker instance's IP, or
|
|
78
|
+
* to the plain hostname; only the former needs its own TLS shape, see `./bare-ip-tls.ts`.
|
|
79
|
+
*/
|
|
80
|
+
connect(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Subscribe every inbound leg this device's line uses (see `topics.ts` — one `/res` for most lines,
|
|
83
|
+
* four topics for `eufy_life`).
|
|
84
|
+
*
|
|
85
|
+
* The grants are INSPECTED, not assumed: AWS IoT answers a policy-denied filter with a
|
|
86
|
+
* `SUBACK_FAILURE` (`0x80`) grant rather than failing the SUBSCRIBE, so subscribing with a credential
|
|
87
|
+
* whose scope doesn't cover the topic looks identical to success and then delivers nothing. A denied
|
|
88
|
+
* topic is reported via `error` naming the credential scope; only an all-denied device throws, so a
|
|
89
|
+
* line that grants its state channel but refuses (say) the OTA leg still works.
|
|
90
|
+
*/
|
|
91
|
+
subscribeDevice(device: EufyDevice): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Publish a raw payload to an MQTT topic (the command leg — `cmd/{app}/{pn}/{sn}/req`). The `body`
|
|
94
|
+
* is a pre-built envelope the caller supplies (the command router builds it). QoS 1 by default (the
|
|
95
|
+
* broker acks). This is the outbound leg — commands, alongside the receive-only `/res` subscriptions.
|
|
96
|
+
*
|
|
97
|
+
* NOTE: which cert scope is used does NOT gate publishing to a security device's topic — confirmed
|
|
98
|
+
* live, an `eufy_mega`-scoped cert and an `eufy_security`-scoped cert get the identical grant/deny
|
|
99
|
+
* pattern. What actually matters is landing on the broker instance that currently holds the device's
|
|
100
|
+
* session (`ensureSecurityMqttFor`/`broker-discovery.ts` — the NLB fronts several instances that
|
|
101
|
+
* don't share subscribe/publish routing).
|
|
102
|
+
*/
|
|
103
|
+
publish(topic: string, body: string | Buffer, opts?: {
|
|
104
|
+
qos?: 0 | 1 | 2;
|
|
105
|
+
}): Promise<void>;
|
|
106
|
+
disconnect(): Promise<void>;
|
|
107
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anker secure-MQTT topic vocabulary + the per-line credential scope.
|
|
3
|
+
*
|
|
4
|
+
* Two facts about this broker drive everything here, both CONFIRMED live (2026-07-28) against a real
|
|
5
|
+
* `eufy_life` light and cross-read from the disassembled V6 app
|
|
6
|
+
* (`com.anker.esiotkit.security_device.mqtt.SecurityMqttConstant`, `mqtt_serve.dart`):
|
|
7
|
+
*
|
|
8
|
+
* - **The device→app leg is not `/res` for every line.** `eufy_life` reports land on `/app/res`;
|
|
9
|
+
* the app subscribes four topics per light device and `/res` carries nothing. Other lines use
|
|
10
|
+
* `/res` alone.
|
|
11
|
+
* - **The topic space is partitioned by credential.** `get_user_mqtt_info` returns a DIFFERENT
|
|
12
|
+
* certificate per `app-name`, and the AWS IoT policy granting `eufy_life/...` is attached only to
|
|
13
|
+
* the `eufy_life` cert. Subscribing to a light's topics with the default credential is *silently*
|
|
14
|
+
* denied — the broker answers SUBACK with a 128 grant rather than rejecting, which is why this
|
|
15
|
+
* produced total silence instead of an error.
|
|
16
|
+
*
|
|
17
|
+
* Scope is derived from `EufyDevice.category` — a device-record field, not a capability — so this
|
|
18
|
+
* stays on the transport side of the capability↔transport boundary.
|
|
19
|
+
*/
|
|
20
|
+
import type { EufyDevice } from "../../core/types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Which `get_user_mqtt_info` credential a device's realtime traffic rides on. `"default"` is the
|
|
23
|
+
* headerless credential (observed `eufy_mega`-scoped) that serves every non-life MQTT device.
|
|
24
|
+
*
|
|
25
|
+
* A scope exists only where a DISTINCT credential does. `eufy_life` is one because its topic space is
|
|
26
|
+
* denied to the default credential. The Clean line (`eufy_home`) is NOT: its
|
|
27
|
+
* `cmd/eufy_home/<model>/<sn>/res` subscribe is confirmed against a T2351 on the
|
|
28
|
+
* default credential, which is the only one `get_user_mqtt_info` was ever asked for. Giving it a scope
|
|
29
|
+
* of its own would open a second connection under the SAME `thing_name` — AWS IoT treats a duplicate
|
|
30
|
+
* client id as a takeover and evicts the incumbent, so both lines would flap.
|
|
31
|
+
*/
|
|
32
|
+
export type MqttScope = "default" | "eufy_life";
|
|
33
|
+
/**
|
|
34
|
+
* The credential scope a device's topics are granted under. Derived from `EufyDevice.category` — a
|
|
35
|
+
* device record field, not a capability — so this stays on the transport side of the boundary.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mqttScopeFor(device: EufyDevice): MqttScope;
|
|
38
|
+
/**
|
|
39
|
+
* The `app-name` request header value for a scope, or `undefined` for the default (headerless)
|
|
40
|
+
* credential — matching the optional argument of `getUserMqttInfo`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mqttAppName(scope: MqttScope): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Build a topic for a device: `cmd/<prefix>/<model>/<sn>/<req|res>`. `leg` is required — the two
|
|
45
|
+
* directions are one word apart and publishing to the wrong one fails silently. The prefix is
|
|
46
|
+
* derived by the module-local `topicPrefix` — `eufy_home` for clean-line devices, `device.category` elsewhere.
|
|
47
|
+
*/
|
|
48
|
+
export declare function secureTopic(device: EufyDevice, leg: "req" | "res"): string;
|
|
49
|
+
/**
|
|
50
|
+
* Every topic to subscribe for a device's inbound traffic.
|
|
51
|
+
*
|
|
52
|
+
* `eufy_life` (smart lights) gets the app's full four-topic set: `/app/res` (the state channel),
|
|
53
|
+
* `/res` (present in the app's subscribe list but observed to carry nothing on this line),
|
|
54
|
+
* `synq/…/state_info` (online/offline), and `/app/ota/res` (OTA progress).
|
|
55
|
+
*
|
|
56
|
+
* Clean-line devices (vacuum/mower) subscribe four topics on the `eufy_home` prefix:
|
|
57
|
+
* - `cmd/…/res` — device→app DP reports and command replies (confirmed live on T2351)
|
|
58
|
+
* - `biz/…/res` — cloud→app business-layer responses (TopicManager.getBizReqTopic())
|
|
59
|
+
* - `biz/…/req` — cloud ACKs for app→cloud business requests (subscribe for ACKs)
|
|
60
|
+
* - `dt/…/param_info` — device-twin parameter push
|
|
61
|
+
*
|
|
62
|
+
* Every other line subscribes `/res` alone.
|
|
63
|
+
*/
|
|
64
|
+
export declare function subscribeTopics(device: EufyDevice): readonly string[];
|
|
65
|
+
/** A parsed inbound topic. `tail` is everything after the serial (`res`, `app/res`, `state_info`, …). */
|
|
66
|
+
export interface ParsedTopic {
|
|
67
|
+
root: string;
|
|
68
|
+
category: string;
|
|
69
|
+
model: string;
|
|
70
|
+
sn: string;
|
|
71
|
+
tail: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Split an inbound topic into its parts. All roots this broker uses (`cmd/…`, `synq/…`, `biz/…`,
|
|
75
|
+
* `dt/…`) put the serial at index 3 regardless of how deep the tail runs, so the serial is read
|
|
76
|
+
* positionally rather than from the end — `…/<sn>/app/res` and `…/<sn>/app/ota/res` would otherwise
|
|
77
|
+
* yield the tail segment as the device id. Returns `undefined` on any shape this doesn't recognise,
|
|
78
|
+
* so an unparsed topic leaves `deviceSn` unset instead of carrying a guess.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseSecureTopic(topic: string): ParsedTopic | undefined;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADTS (Audio Data Transport Stream) helpers — the framing the device expects on the audio path a
|
|
3
|
+
* host pushes toward a camera. A host hands over an arbitrarily-chunked byte stream (an encoder's
|
|
4
|
+
* stdout, a file read), so frame boundaries have to be RECOVERED from the stream rather than assumed
|
|
5
|
+
* to align with the chunks; this is the single place that scans for them.
|
|
6
|
+
*
|
|
7
|
+
* An ADTS frame is a 7-byte header (9 with CRC) followed by the raw audio payload, and the header's
|
|
8
|
+
* own `frameLength` field spans header + payload — so the scan is self-delimiting once synced.
|
|
9
|
+
* Header layout, MSB-first, as the V6 app writes it (`media/player/audio/AacEncode.java:43-51`):
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* byte 0 1111 1111 syncword high
|
|
13
|
+
* byte 1 1111 iilp syncword low · i=MPEG version+layer · p=1 ⇒ NO CRC (2 fewer header bytes)
|
|
14
|
+
* byte 2 ppff ffpc p=profile-1 · f=sampling-frequency index · c=channel-config high bit
|
|
15
|
+
* byte 3 cc.. LLL c=channel-config low 2 bits · L=frameLength bits 12..11
|
|
16
|
+
* byte 4 LLLL LLLL frameLength bits 10..3
|
|
17
|
+
* byte 5 LLLb bbbb frameLength bits 2..0 · b=buffer fullness high
|
|
18
|
+
* byte 6 bbbb bbnn buffer fullness low · n=frames-in-block minus one
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The device's parameters are fixed and verified against the app's own encoder configuration
|
|
22
|
+
* (`AacEncode.java:23-41`, `player/audio/BDBAudioConfig.java:6-66`): AAC-LC, 16 kHz, mono. One AAC-LC
|
|
23
|
+
* frame is 1024 samples, so at 16 kHz each frame carries exactly 64 ms of audio — the cadence the
|
|
24
|
+
* send side paces on.
|
|
25
|
+
*
|
|
26
|
+
* @module p2p/adts
|
|
27
|
+
*/
|
|
28
|
+
/** Samples per AAC-LC frame — fixed by the codec, not by these parameters. */
|
|
29
|
+
export declare const AAC_SAMPLES_PER_FRAME = 1024;
|
|
30
|
+
/** Sample rate the device's audio path runs at. */
|
|
31
|
+
export declare const AAC_SAMPLE_RATE = 16000;
|
|
32
|
+
/**
|
|
33
|
+
* Duration one frame represents, in milliseconds — `1024 / 16000`, exactly 64 ms. The send side both
|
|
34
|
+
* paces on this and steps its frame timestamp by it.
|
|
35
|
+
*/
|
|
36
|
+
export declare const AAC_FRAME_MS: number;
|
|
37
|
+
/**
|
|
38
|
+
* Largest frame the device accepts. The app drops anything longer before it reaches the wire
|
|
39
|
+
* (`media/recorder/recorder/BDBRawAudioRecorder.java:98`), so a longer frame is a caller bug rather
|
|
40
|
+
* than something to split or truncate.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_AUDIO_FRAME_BYTES = 640;
|
|
43
|
+
/** Parsed fields of one ADTS header, with the payload it introduces. */
|
|
44
|
+
export interface AdtsHeader {
|
|
45
|
+
/** Header + payload length, as declared by the header itself. */
|
|
46
|
+
frameLength: number;
|
|
47
|
+
/** Bytes of header ahead of the payload — 7 without a CRC, 9 with one. */
|
|
48
|
+
headerLength: number;
|
|
49
|
+
/** Profile field, `1` for AAC-LC. */
|
|
50
|
+
profile: number;
|
|
51
|
+
/** Index into the ADTS sampling-frequency table, `8` for 16 kHz. */
|
|
52
|
+
frequencyIndex: number;
|
|
53
|
+
/** Channel configuration, `1` for mono. */
|
|
54
|
+
channels: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read an ADTS header at `offset`, or `undefined` when the bytes there are not a plausible header —
|
|
58
|
+
* no syncword, a `frameLength` shorter than its own header, or not enough bytes to decide. Says
|
|
59
|
+
* nothing about whether the frame's payload has arrived yet; that is the scanner's job.
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseAdtsHeader(buf: Buffer, offset?: number): AdtsHeader | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Whether a header describes the audio parameters the device's path is fixed at — AAC-LC, 16 kHz,
|
|
64
|
+
* mono. A stream at any other rate or channel count is rejected rather than resampled: the device has
|
|
65
|
+
* no way to be told otherwise, so passing it through would produce audio at the wrong pitch and speed.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isSupportedAdts(h: AdtsHeader): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Human-readable reason a header is unsupported, for the error a caller sees. Reports the decoded
|
|
70
|
+
* values so a mis-configured encoder is obvious from the message alone.
|
|
71
|
+
*/
|
|
72
|
+
export declare function describeAdts(h: AdtsHeader): string;
|
|
73
|
+
/**
|
|
74
|
+
* Incremental ADTS frame scanner. Feed it byte chunks in arrival order and it yields whole frames,
|
|
75
|
+
* holding a partial frame across calls — which is the whole point: a host's chunk boundaries have no
|
|
76
|
+
* relationship to frame boundaries, and a naive per-chunk parse would either split frames or resync
|
|
77
|
+
* mid-payload and emit garbage.
|
|
78
|
+
*
|
|
79
|
+
* A byte run that does not begin with a syncword is skipped one byte at a time until one is found, so
|
|
80
|
+
* a stream that starts mid-frame (or carries an encoder's preamble) recovers instead of failing.
|
|
81
|
+
*/
|
|
82
|
+
export declare class AdtsFrameReader {
|
|
83
|
+
private buffered;
|
|
84
|
+
/**
|
|
85
|
+
* Append `chunk` and return every complete frame now available, each a standalone buffer that still
|
|
86
|
+
* carries its own ADTS header (the device expects the header on the wire).
|
|
87
|
+
*/
|
|
88
|
+
push(chunk: Buffer): Buffer[];
|
|
89
|
+
/** Bytes held back awaiting the rest of their frame — non-zero mid-stream, zero on a clean boundary. */
|
|
90
|
+
get pending(): number;
|
|
91
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annex-B elementary-stream helpers — the single NAL-sniffing source of truth for the P2P media
|
|
3
|
+
* pipeline. Both the live source (codec tag on every frame) and the fMP4 muxer (parameter sets for
|
|
4
|
+
* the init segment) read the stream through here, so the start-code scan + NAL-type decode lives in
|
|
5
|
+
* exactly one place.
|
|
6
|
+
*
|
|
7
|
+
* H.264 (Annex-B): NAL type = `byte & 0x1f`; SPS = 7, PPS = 8, IDR = 5.
|
|
8
|
+
* H.265/HEVC (Annex-B): NAL type = `(byte >> 1) & 0x3f`; VPS = 32, SPS = 33, PPS = 34, IDR = 19/20.
|
|
9
|
+
*
|
|
10
|
+
* @module p2p/annexb
|
|
11
|
+
*/
|
|
12
|
+
import type { VideoCodec } from "../../core/contracts.js";
|
|
13
|
+
/** Split an Annex-B access unit into its individual NAL bodies (start codes stripped). */
|
|
14
|
+
export declare function splitAnnexbNals(buf: Buffer): Buffer[];
|
|
15
|
+
/**
|
|
16
|
+
* Sniff the codec of an Annex-B access unit by its parameter-set NAL units. Returns `undefined` when
|
|
17
|
+
* the buffer carries no config NAL (a plain delta frame) — the caller should carry the last-known
|
|
18
|
+
* codec rather than guess. Only the first 64 bytes are scanned (config NALs lead the access unit).
|
|
19
|
+
*/
|
|
20
|
+
export declare function sniffAnnexbCodec(buf: Buffer): VideoCodec | undefined;
|
|
21
|
+
/** Parameter sets extracted from a keyframe access unit — the input to an fMP4 init segment. */
|
|
22
|
+
export interface ParamSets {
|
|
23
|
+
codec: VideoCodec;
|
|
24
|
+
/** H.264 SPS / H.265 SPS NAL bodies (start-code stripped), in stream order. */
|
|
25
|
+
sps: Buffer[];
|
|
26
|
+
/** H.264 PPS / H.265 PPS NAL bodies. */
|
|
27
|
+
pps: Buffer[];
|
|
28
|
+
/** H.265 VPS NAL bodies (empty for H.264). */
|
|
29
|
+
vps: Buffer[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract the SPS/PPS (and, for H.265, VPS) parameter-set NAL bodies from a keyframe access unit.
|
|
33
|
+
* Each returned buffer is the raw NAL (start-code stripped), ready to embed in an `avcC` / `hvcC`
|
|
34
|
+
* decoder-config record. Returns `undefined` if no parameter sets are present.
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractParamSets(buf: Buffer): ParamSets | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The parameter sets in force after `buf`, folding what it announces into `current`.
|
|
39
|
+
*
|
|
40
|
+
* A camera commonly announces SPS/PPS ONCE, with the first keyframe of a stream, so anything that will
|
|
41
|
+
* later hand a burst to a decoder has to watch EVERY unit go past — including ones it discards.
|
|
42
|
+
*
|
|
43
|
+
* Folds per kind rather than replacing wholesale, because a decoder retains the last set it was given of
|
|
44
|
+
* EACH kind: a unit announcing an SPS alone re-states that SPS and says nothing about the PPS, so
|
|
45
|
+
* replacing the whole record would drop a PPS that is still in force. A codec change replaces
|
|
46
|
+
* everything — sets from another codec describe a different bitstream.
|
|
47
|
+
*
|
|
48
|
+
* Cheap on the overwhelmingly common case: {@link extractParamSets} answers from a bounded head scan when
|
|
49
|
+
* a unit carries no config NAL, so an ordinary delta frame costs no full-buffer walk.
|
|
50
|
+
*/
|
|
51
|
+
export declare function updatedParamSets(buf: Buffer, current: ParamSets | undefined): ParamSets | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Re-emit `sets` as Annex-B NALs immediately ahead of `annexb`, so a unit whose parameter sets were
|
|
54
|
+
* sent earlier in the stream becomes decodable on its own.
|
|
55
|
+
*
|
|
56
|
+
* A decoder reads parameter sets in stream order, so they are emitted VPS → SPS → PPS: a PPS ahead of
|
|
57
|
+
* the SPS it references is as useless as none at all. Sets carrying no NALs return the input unchanged
|
|
58
|
+
* rather than an equal copy.
|
|
59
|
+
*
|
|
60
|
+
* Emitting a duplicate set is harmless — a decoder overwrites the entry with the same id — which is why
|
|
61
|
+
* this needs no knowledge of what the unit already carries; {@link extractParamSets} answers what a
|
|
62
|
+
* unit carries already.
|
|
63
|
+
*/
|
|
64
|
+
export declare function prefixParamSets(annexb: Buffer, sets: ParamSets): Buffer;
|
|
65
|
+
/**
|
|
66
|
+
* Whether an Annex-B access unit contains an IDR (keyframe) NAL. Cheap scan used to key-align the
|
|
67
|
+
* ring buffer / fragment boundaries when the frame header's keyframe flag isn't authoritative.
|
|
68
|
+
*/
|
|
69
|
+
export declare function hasIdr(buf: Buffer, codec: VideoCodec): boolean;
|
|
70
|
+
/** A width/height pair in luma samples. */
|
|
71
|
+
export interface Size {
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What a parameter set says a picture's dimensions are — both of them.
|
|
77
|
+
*
|
|
78
|
+
* A stream states TWO sizes and a consumer needs whichever matches what it holds. {@link CodedGeometry.width}
|
|
79
|
+
* and `height` are the DISPLAY size, the picture as a viewer should see it. {@link CodedGeometry.coded} is the
|
|
80
|
+
* size it is actually coded at, rounded up to the macroblock (H.264) or CTU (H.265) grid, and
|
|
81
|
+
* {@link CodedGeometry.crop} is the window between them, already scaled from the chroma units the syntax
|
|
82
|
+
* states them in into luma samples.
|
|
83
|
+
*
|
|
84
|
+
* The distinction is not a refinement: 1080 is not a multiple of 16, so the commonest geometry there is codes
|
|
85
|
+
* 1088 rows and crops 8 away. A caller that muxes or measures wants the display size; a caller DECODING frames
|
|
86
|
+
* itself gets the coded size back from its decoder and needs the window to crop with — one that assumes the two
|
|
87
|
+
* are the same emits eight rows of encoder padding and calls the result 1088 tall.
|
|
88
|
+
*/
|
|
89
|
+
export interface CodedGeometry extends Size {
|
|
90
|
+
/** The size the picture is coded at — macroblock- or CTU-aligned, and never smaller than the display size. */
|
|
91
|
+
coded: Size;
|
|
92
|
+
/** The crop (H.264) or conformance (H.265) window, in LUMA samples. */
|
|
93
|
+
crop: {
|
|
94
|
+
left: number;
|
|
95
|
+
top: number;
|
|
96
|
+
right: number;
|
|
97
|
+
bottom: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Profiles whose SPS carries the chroma, bit-depth and scaling-matrix fields (H.264 Annex A).
|
|
102
|
+
*
|
|
103
|
+
* `144` is the 2005-edition High 4:4:4 that `244` replaced. It is absent from the current standard's list but
|
|
104
|
+
* present in streams, and a set read without its branch lands mid-element — so it is carried here rather
|
|
105
|
+
* than left to misparse into a plausible geometry.
|
|
106
|
+
*/
|
|
107
|
+
export declare const H264_CHROMA_PROFILES: Set<number>;
|
|
108
|
+
/**
|
|
109
|
+
* The picture geometry a decoder will produce from the parameter sets in force, or `undefined` when no
|
|
110
|
+
* SPS could be read.
|
|
111
|
+
*
|
|
112
|
+
* This is the authority on a live stream's geometry. A frame header states the geometry at capture start,
|
|
113
|
+
* and a source that reconfigures mid-session leaves it contradicting the bytes it is sending — so a
|
|
114
|
+
* consumer that rebuilt a decoder from the header would size it for a picture the stream is not carrying.
|
|
115
|
+
* The SPS is what the picture actually is.
|
|
116
|
+
*
|
|
117
|
+
* The crop and conformance offsets are part of the answer rather than a refinement of it: 1080 is not a
|
|
118
|
+
* multiple of the 16-sample macroblock, so a 1080p H.264 stream codes 1088 rows and crops 8 away. A read
|
|
119
|
+
* that stopped at the coded size would be wrong by exactly that on the commonest geometry there is.
|
|
120
|
+
*
|
|
121
|
+
* Answers from the LAST SPS of the set, which is the one in force. AV1 is not parsed — the SDK decodes no
|
|
122
|
+
* AV1 sequence header, and a size from another codec's syntax would be a fabrication.
|
|
123
|
+
*/
|
|
124
|
+
export declare function codedGeometry(sets: ParamSets): CodedGeometry | undefined;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export interface Address {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
}
|
|
5
|
+
/** Magic word at the start of every P2P data frame. */
|
|
6
|
+
export declare const MAGIC_WORD = "XZYH";
|
|
7
|
+
/** UDP message types (1st byte always 0xf1). */
|
|
8
|
+
export declare const RequestMessageType: {
|
|
9
|
+
readonly LOCAL_LOOKUP: Buffer<ArrayBuffer>;
|
|
10
|
+
readonly LOOKUP_WITH_KEY: Buffer<ArrayBuffer>;
|
|
11
|
+
readonly LOOKUP_WITH_KEY2: Buffer<ArrayBuffer>;
|
|
12
|
+
readonly CHECK_CAM: Buffer<ArrayBuffer>;
|
|
13
|
+
readonly CHECK_CAM2: Buffer<ArrayBuffer>;
|
|
14
|
+
readonly PING: Buffer<ArrayBuffer>;
|
|
15
|
+
readonly PONG: Buffer<ArrayBuffer>;
|
|
16
|
+
readonly DATA: Buffer<ArrayBuffer>;
|
|
17
|
+
readonly ACK: Buffer<ArrayBuffer>;
|
|
18
|
+
readonly END: Buffer<ArrayBuffer>;
|
|
19
|
+
};
|
|
20
|
+
export declare const ResponseMessageType: {
|
|
21
|
+
readonly LOCAL_LOOKUP_RESP: Buffer<ArrayBuffer>;
|
|
22
|
+
readonly LOOKUP_ADDR: Buffer<ArrayBuffer>;
|
|
23
|
+
readonly LOOKUP_ADDR2: Buffer<ArrayBuffer>;
|
|
24
|
+
readonly CAM_ID: Buffer<ArrayBuffer>;
|
|
25
|
+
readonly TURN_SERVER_CAM_ID: Buffer<ArrayBuffer>;
|
|
26
|
+
readonly PING: Buffer<ArrayBuffer>;
|
|
27
|
+
readonly PONG: Buffer<ArrayBuffer>;
|
|
28
|
+
readonly DATA: Buffer<ArrayBuffer>;
|
|
29
|
+
readonly ACK: Buffer<ArrayBuffer>;
|
|
30
|
+
readonly END: Buffer<ArrayBuffer>;
|
|
31
|
+
};
|
|
32
|
+
/** P2P data-channel types (2nd byte of the data-type header). */
|
|
33
|
+
export declare const P2PDataType: {
|
|
34
|
+
readonly DATA: 0;
|
|
35
|
+
readonly VIDEO: 1;
|
|
36
|
+
readonly CONTROL: 2;
|
|
37
|
+
readonly BINARY: 3;
|
|
38
|
+
};
|
|
39
|
+
export declare const P2PDataTypeHeader: {
|
|
40
|
+
readonly DATA: Buffer<ArrayBuffer>;
|
|
41
|
+
readonly VIDEO: Buffer<ArrayBuffer>;
|
|
42
|
+
readonly CONTROL: Buffer<ArrayBuffer>;
|
|
43
|
+
readonly BINARY: Buffer<ArrayBuffer>;
|
|
44
|
+
};
|
|
45
|
+
/** The fixed-width 16-byte header of every P2P data frame (after "XZYH"). */
|
|
46
|
+
export declare const P2P_DATA_HEADER_BYTES = 16;
|
|
47
|
+
export interface P2PDataFrameHeader {
|
|
48
|
+
commandId: number;
|
|
49
|
+
bytesToRead: number;
|
|
50
|
+
channel: number;
|
|
51
|
+
signCode: number;
|
|
52
|
+
type: number;
|
|
53
|
+
}
|
|
54
|
+
/** Frame a UDP packet: `[msgType:2][payloadLen:2 BE][payload]`. */
|
|
55
|
+
export declare function frameMessage(msgType: Buffer, payload?: Buffer): Buffer;
|
|
56
|
+
/** True if `msg` begins with the given 2-byte message type. */
|
|
57
|
+
export declare function hasHeader(msg: Buffer, type: Buffer): boolean;
|
|
58
|
+
/** Pad a string into a fixed-width (multiple-of-chunk) zero-filled buffer. */
|
|
59
|
+
export declare function stringWithLength(input: string, chunkLength?: number): Buffer;
|
|
60
|
+
/** `EUPRCAM-000000-XXXXX` → the 20-byte buffer used in lookup/check payloads. */
|
|
61
|
+
export declare function p2pDidToBuffer(p2pDid: string): Buffer;
|
|
62
|
+
/**
|
|
63
|
+
* Decode a station's `p2p_conn` / `app_conn` string into the eufy cloud lookup
|
|
64
|
+
* server addresses (port 32100). XOR cipher with a fixed lookup table, seed 0x39.
|
|
65
|
+
*/
|
|
66
|
+
export declare function decodeP2PCloudIPs(data: string): Address[];
|
|
67
|
+
/**
|
|
68
|
+
* The "Level 1" AES-128-ECB key for P2P command/control encryption — derivable
|
|
69
|
+
* from sn + p2p_did alone (no cipher needed). HomeBase control-channel
|
|
70
|
+
* notifications (sensor/alarm events) use exactly this key.
|
|
71
|
+
*/
|
|
72
|
+
export declare function p2pCommandEncryptionKey(stationSn: string, p2pDid: string): string;
|
|
73
|
+
/** AES-128-ECB decrypt of a P2P payload (no padding). */
|
|
74
|
+
export declare function decryptP2PData(data: Buffer, key: Buffer): Buffer;
|
|
75
|
+
/** The `cipher_id` named in a decrypted `CMD_GATEWAYINFO` (1100) payload (uint16 LE @0). */
|
|
76
|
+
export declare function gatewayInfoCipherId(gwPayload: Buffer): number;
|
|
77
|
+
/** Options for {@link eciesUnwrap}. */
|
|
78
|
+
export interface EciesUnwrapOptions {
|
|
79
|
+
/**
|
|
80
|
+
* When true, the envelope carries a trailing 32-byte HMAC tag over `iv ‖ ct` (the video
|
|
81
|
+
* `CMD_VIDEO_FRAME` envelope, which is `ephPub(33) ‖ iv(16) ‖ ct(48) ‖ HMAC(32)`). The tag is
|
|
82
|
+
* verified with `kdf[16:48]` as the MAC key and a mismatch yields `undefined`. When false (the
|
|
83
|
+
* `CMD_GATEWAYINFO` envelope, `ephPub(33) ‖ iv(16) ‖ ct(48)`) there is no HMAC step.
|
|
84
|
+
*/
|
|
85
|
+
verifyHmac?: boolean;
|
|
86
|
+
/** Whether the AES-128-CBC plaintext is PKCS7-padded (video: true). Default false (zero-/no-pad). */
|
|
87
|
+
pkcs7?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The shared ECIES unwrap primitive used by both the gateway and the video paths.
|
|
91
|
+
*
|
|
92
|
+
* `envelope` = `ephemeralPub(33, compressed P-256) ‖ iv(16) ‖ ct(48) [‖ HMAC(32)]`. Derivation:
|
|
93
|
+
* `S = ECDH_X(eccPriv, ephPub)` → `kdf = eufyKDF(S)` → `aesKey = kdf[0:16]`; (optionally verify
|
|
94
|
+
* `HMAC(kdf[16:48], iv‖ct) == tag`) → `AES-128-CBC(aesKey, iv, ct)` → plaintext. Returns the
|
|
95
|
+
* decrypted bytes, or `undefined` on any parse / HMAC / cipher failure (fails closed, never throws).
|
|
96
|
+
*/
|
|
97
|
+
export declare function eciesUnwrap(envelope: Buffer, eccPrivateKeyHex: string, options?: EciesUnwrapOptions): Buffer | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Derive the P2P **level-2** (signCode 2/8) AES-256-GCM session key from a *decrypted*
|
|
100
|
+
* `CMD_GATEWAYINFO` (1100) payload and the station's ECC private key (from `get_ciphers`).
|
|
101
|
+
*
|
|
102
|
+
* Layout of the decrypted payload: `cipher_id(2 LE) ‖ 0000 ‖ ECIES-envelope(129)`, where the
|
|
103
|
+
* envelope is `ephemeralPub(33, compressed P-256) ‖ iv(16) ‖ ciphertext(48)`. ECIES =
|
|
104
|
+
* ECDH(eccPriv, ephemeralPub) → eufyKDF(SHA-256/HMAC, label "ECIES") → AES-128-CBC decrypt →
|
|
105
|
+
* 32-byte session key. Reversed from `libmega_media_sdk.so`; verified to reproduce the live key.
|
|
106
|
+
* Returns the 32-byte key, or undefined if the inputs don't parse.
|
|
107
|
+
*/
|
|
108
|
+
export declare function deriveLevel2KeyFromGatewayInfo(gwPayload: Buffer, eccPrivateKeyHex: string): Buffer | undefined;
|
|
109
|
+
/** Local-lookup payload: two zero bytes. */
|
|
110
|
+
export declare function buildLocalLookupPayload(): Buffer;
|
|
111
|
+
/** Cloud-lookup payload (variant 2): `[p2pDid:20][dskKey][0x00000000]`. */
|
|
112
|
+
export declare function buildLookupWithKeyPayload2(p2pDid: string, dskKey: string): Buffer;
|
|
113
|
+
/**
|
|
114
|
+
* Cloud-lookup payload — the CLASSIC variant (`LOOKUP_WITH_KEY`, 0xf126), reverse-engineered
|
|
115
|
+
* byte-exact against the app's own request (cold-start, phone
|
|
116
|
+
* force-stopped then reopened). Distinct from {@link buildLookupWithKeyPayload2} (`LOOKUP_WITH_KEY2`,
|
|
117
|
+
* 0xf16a) which eufy-sdk already sent: that variant only ever got relay-pool candidates back for a
|
|
118
|
+
* remote (cross-WAN) target; THIS variant is what got the real app a genuine direct-device address.
|
|
119
|
+
*
|
|
120
|
+
* `[p2pDid:20][selfAddr:16][clientVersion:4 = 02 05 01 05][dskKey][0x00000000]`. `selfAddr` is the
|
|
121
|
+
* caller's own observed LAN host:port (the socket's own bound address) — a STUN-like self-report, NOT
|
|
122
|
+
* the target's address. `clientVersion` was `02 05 01 05` in every capture; reproduced verbatim since
|
|
123
|
+
* its exact semantics (and whether it's validated) are unconfirmed.
|
|
124
|
+
*/
|
|
125
|
+
export declare function buildLookupWithKeyPayload(p2pDid: string, selfHost: string, selfPort: number, dskKey: string): Buffer;
|
|
126
|
+
/** CHECK_CAM hole-punch payload: `[p2pDid:20][0x000000]`. */
|
|
127
|
+
export declare function buildCheckCamPayload(p2pDid: string): Buffer;
|
|
128
|
+
/** 10-byte command header: `[dataTypeHeader:2][seq:2 BE]["XZYH"][cmd:2 LE]`. */
|
|
129
|
+
export declare function buildCommandHeader(seqNumber: number, commandType: number, dataTypeHeader?: Buffer): Buffer;
|
|
130
|
+
/** 10-byte empty command body for the given channel (used by CMD_GATEWAYINFO etc.). */
|
|
131
|
+
export declare function buildVoidCommandPayload(channel?: number): Buffer;
|
|
132
|
+
/** AES-128-ECB encrypt (no auto-padding) — P2P control payloads. Inverse of decryptP2PData. */
|
|
133
|
+
export declare function encryptP2PData(data: Buffer, key: Buffer): Buffer;
|
|
134
|
+
/** Zero-pad to a multiple of `blocksize` (eufy P2P pads with 0x00, NOT PKCS7). */
|
|
135
|
+
export declare function paddingP2PData(data: Buffer, blocksize?: number): Buffer;
|
|
136
|
+
/**
|
|
137
|
+
* Command body carrying a string `value` (e.g. CMD_SET_PAYLOAD JSON). Mirrors the
|
|
138
|
+
* app's `buildCommandWithStringTypePayload`: `[len:2 LE][00 00][01 00][channel,encType][00 00][data]`.
|
|
139
|
+
* When `key` is given the data is zero-padded to 16 and AES-128-ECB encrypted, and
|
|
140
|
+
* `encType` is written in the channel word (level-1 encryption = 1).
|
|
141
|
+
*/
|
|
142
|
+
export declare function buildStringCommandPayload(value: string, channel?: number, key?: Buffer, encType?: number): Buffer;
|
|
143
|
+
/**
|
|
144
|
+
* Build an **int+string** command body: `valueSub(u32 LE) ‖ value(u32 LE) ‖ strValue(len-prefixed)`,
|
|
145
|
+
* AES-128-ECB encrypted (level-1) like {@link buildStringCommandPayload}. This is the wire shape the
|
|
146
|
+
* eufy app uses for the floodlight/spotlight manual switch (`CMD_SET_FLOODLIGHT_MANUAL_SWITCH` 1400)
|
|
147
|
+
* on IndoorOutdoor / SoloCam-spotlight / Cam2C/3 families — where `value` = 0/1, `valueSub` = the
|
|
148
|
+
* device channel, and `strValue` = the admin `account_id`. The `strValue` uses the 128-byte-chunk
|
|
149
|
+
* length prefix ({@link stringWithLength}).
|
|
150
|
+
*/
|
|
151
|
+
export declare function buildIntStringCommandPayload(value: number, valueSub: number, strValue: string, channel?: number, key?: Buffer, encType?: number): Buffer;
|
|
152
|
+
/**
|
|
153
|
+
* Build a command body around an ALREADY-encrypted (or plaintext) `data` buffer with an explicit
|
|
154
|
+
* `signCode` — used for level-2 (`signCode 8`, AES-256-GCM) commands like the media-start 1350 the
|
|
155
|
+
* app sends. Same on-wire layout as `buildStringCommandPayload` but the caller supplies the body and
|
|
156
|
+
* the signCode verbatim (no ECB step): `len(u16) ‖ 0000 ‖ 0100 ‖ [channel, signCode] ‖ 0000 ‖ data`.
|
|
157
|
+
*/
|
|
158
|
+
export declare function buildRawCommandPayload(data: Buffer, channel?: number, signCode?: number, magic?: [number, number], streamId?: number): Buffer;
|
|
159
|
+
/** ACK payload for a received DATA frame: `[dataTypeHeader:2][count:2 BE][seqNo:2 BE]`. */
|
|
160
|
+
export declare function buildAckPayload(dataTypeHeader: Buffer, seqNo: number): Buffer;
|
|
161
|
+
/** Parse the 16-byte data-frame header that follows the "XZYH" magic. */
|
|
162
|
+
export declare function parseDataFrameHeader(frame: Buffer): P2PDataFrameHeader;
|
|
163
|
+
/** Parse a LOOKUP_ADDR / LOOKUP_ADDR2 response into a device address. */
|
|
164
|
+
export declare function parseLookupAddr(msg: Buffer): Address;
|
|
165
|
+
/** Read a NUL-terminated UTF-8 string (CMD_NOTIFY_PAYLOAD carries JSON this way). */
|
|
166
|
+
export declare function readNullTerminatedString(data: Buffer, encoding?: BufferEncoding): string;
|