first commit
This commit is contained in:
85
utils/analyze-idv1.py
Executable file
85
utils/analyze-idv1.py
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
from rich import print
|
||||
|
||||
RE_NUM = re.compile("/[0-9]+$")
|
||||
|
||||
def load(name):
|
||||
# print(f"loading {name}..")
|
||||
return json.load(open(name))["data"]
|
||||
|
||||
def idv1_type(idv1):
|
||||
if not idv1.endswith("/0"):
|
||||
idv1 = RE_NUM.sub("/:id", idv1)
|
||||
if idv1.startswith("/scenes/"):
|
||||
idv1 = "/scenes/:id"
|
||||
return idv1
|
||||
|
||||
|
||||
def analyze_unique_ids(res):
|
||||
ids = set()
|
||||
|
||||
for obj in res:
|
||||
if "id_v1" in obj:
|
||||
rtype = obj["type"]
|
||||
idv1 = idv1_type(obj["id_v1"])
|
||||
ids.add((idv1, rtype))
|
||||
|
||||
for idv1, rtype in sorted(ids):
|
||||
print(f"{idv1:20} {rtype:30}")
|
||||
|
||||
|
||||
def analyze_idv1_percent(res):
|
||||
ids = dict()
|
||||
|
||||
for obj in res:
|
||||
rtype = obj["type"]
|
||||
if rtype not in ids:
|
||||
ids[rtype] = [0, 0]
|
||||
|
||||
cnt = ids[rtype]
|
||||
cnt[0] += 1
|
||||
if "id_v1" in obj:
|
||||
cnt[1] += 1
|
||||
|
||||
for name, (total, idv1) in sorted(ids.items()):
|
||||
pct = (idv1 / total) * 100.0
|
||||
print(f"{name:30} {pct:6.2f}% [{idv1:5} / {total:5}]")
|
||||
|
||||
|
||||
def analyze_idv1_mapping(res):
|
||||
ids = dict()
|
||||
|
||||
for obj in res:
|
||||
rtype = obj["type"]
|
||||
if rtype not in ids:
|
||||
ids[rtype] = set()
|
||||
|
||||
if "id_v1" in obj:
|
||||
idv1 = idv1_type(obj["id_v1"])
|
||||
else:
|
||||
idv1 = "<none>"
|
||||
|
||||
ids[rtype].add(idv1)
|
||||
|
||||
for name, types in sorted(ids.items()):
|
||||
if types == {"<none>"}:
|
||||
continue
|
||||
print(f"{name:30} {' '.join(sorted(types, reverse=True))}")
|
||||
|
||||
|
||||
def main(args):
|
||||
objs = []
|
||||
for name in args:
|
||||
objs.extend(load(name))
|
||||
|
||||
analyze_unique_ids(objs)
|
||||
analyze_idv1_percent(objs)
|
||||
analyze_idv1_mapping(objs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
52
utils/attribute-bruteforce.py
Executable file
52
utils/attribute-bruteforce.py
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
|
||||
## Delay between each query, in seconds (adjust as needed)
|
||||
##
|
||||
## WARNING: Too small values might overload the zigbee network and/or the
|
||||
## coordinator.
|
||||
DELAY = 0.3
|
||||
|
||||
TMPL = {
|
||||
"topic":"DUMMY/set",
|
||||
"payload":{
|
||||
"read":{
|
||||
"cluster":64516,
|
||||
"attributes":[],
|
||||
"options":{
|
||||
"timeout": 500.0,
|
||||
"manufacturerCode":4107
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def note(msg):
|
||||
print(f">>> {msg} <<<", file=sys.stderr)
|
||||
|
||||
def main(argv0, *args):
|
||||
if len(args) != 1:
|
||||
print(f"usage: {argv0} <topic>")
|
||||
return 1
|
||||
|
||||
topic = f"{args[0]}/set"
|
||||
TMPL["topic"] = topic
|
||||
note(f"Using topic {topic}")
|
||||
|
||||
for cluster in [0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04]:
|
||||
note(f"Brute forcing attributes in cluster {cluster:04X}")
|
||||
TMPL["payload"]["read"]["cluster"] = cluster
|
||||
for x in range(64):
|
||||
TMPL["payload"]["read"]["attributes"] = [x]
|
||||
print(json.dumps(TMPL))
|
||||
sys.stdout.flush()
|
||||
time.sleep(DELAY)
|
||||
|
||||
note("DONE")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(*sys.argv))
|
||||
18
utils/capture-event-stream.sh
Executable file
18
utils/capture-event-stream.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ue
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "usage: $0 <hue-key> <bridge-addr>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEY="${1}"
|
||||
IP="${2}"
|
||||
|
||||
exec curl -N --http2 \
|
||||
-H "Accept:text/event-stream" \
|
||||
-s \
|
||||
-k \
|
||||
-H"hue-application-key: ${KEY}" \
|
||||
"https://${IP}/eventstream/clip/v2"
|
||||
74
utils/extract-error-sample.py
Executable file
74
utils/extract-error-sample.py
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import re
|
||||
import enum
|
||||
import json
|
||||
|
||||
RE_LOG = re.compile("\s*[0-9]{4}-[0-9]{2}-[0-9]{2}.+(ERROR|INFO |TRACE|DEBUG|WARN ) ([a-z0-9_::-]+)\s+> (.+)")
|
||||
|
||||
RE_FAILED_TO_PARSE = re.compile("\[(.+)\] Failed to parse \(non-critical\) z2m bridge message on \[(.+)\]:$")
|
||||
|
||||
class Mode(enum.Enum):
|
||||
Idle = 0
|
||||
IgnoreObj = 1
|
||||
IgnoreLogo = 2
|
||||
Sample = 3
|
||||
SampleStart = 4
|
||||
|
||||
mode = Mode.Idle
|
||||
|
||||
res = []
|
||||
buf = None
|
||||
topic = None
|
||||
|
||||
BAR = " ==================================================================="
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.rstrip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
match mode:
|
||||
case Mode.Idle:
|
||||
if line == "{":
|
||||
mode = Mode.IgnoreObj
|
||||
|
||||
if line == BAR:
|
||||
mode = Mode.IgnoreLogo
|
||||
|
||||
if (log := RE_LOG.match(line)):
|
||||
if (err := RE_FAILED_TO_PARSE.match(log.group(3))):
|
||||
mode = Mode.SampleStart
|
||||
buf = ""
|
||||
topic = err.group(2)
|
||||
|
||||
case Mode.IgnoreObj:
|
||||
if line == "}":
|
||||
mode = Mode.Idle
|
||||
|
||||
case Mode.IgnoreLogo:
|
||||
if line == BAR:
|
||||
mode = Mode.Idle
|
||||
|
||||
case Mode.SampleStart:
|
||||
if (log := RE_LOG.match(line)):
|
||||
mode = Mode.Sample
|
||||
buf = log.group(3)
|
||||
|
||||
case Mode.Sample:
|
||||
if (log := RE_LOG.match(line)) or line == BAR:
|
||||
try:
|
||||
val = json.loads(buf)
|
||||
res.append((topic, val))
|
||||
except:
|
||||
print(f"FAILED TO PARSE JSON: {buf}")
|
||||
mode = Mode.IgnoreLogo if line == BAR else Mode.Idle
|
||||
buf = None
|
||||
topic = None
|
||||
else:
|
||||
buf += line
|
||||
|
||||
for topic, data in res:
|
||||
print(json.dumps({"topic": topic, "payload": data}))
|
||||
24
utils/filter-zigbee.jq
Executable file
24
utils/filter-zigbee.jq
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env -S jq -c --unbuffered -f
|
||||
|
||||
### convert json file:
|
||||
#
|
||||
# tshark -r zigbee-2025-01-27_08.57.08-hue.pcap -lq -T ek -x | utils/pcap-extract-zigbee.jq > /tmp/sample.json
|
||||
#
|
||||
|
||||
### live capture
|
||||
#
|
||||
# tshark -i lo -lq -T ek -x 'udp port 1337' | utils/pcap-extract-zigbee.jq
|
||||
#
|
||||
|
||||
.layers
|
||||
| select(.zbee_aps["zbee_aps_zbee_aps_cluster_raw"])
|
||||
| {
|
||||
time: .frame["frame_frame_time_epoch"],
|
||||
index: .frame["frame_frame_number"] | tonumber,
|
||||
src_mac: (.zbee_nwk["zbee_nwk_zbee_sec_src64_raw"] // .wpan["wpan_wpan_addr64_raw"]),
|
||||
src: (.zbee_nwk["zbee_nwk_zbee_nwk_src_raw"] // .wpan["wpan_wpan_addr16_raw"]),
|
||||
dst: (.zbee_nwk["zbee_nwk_zbee_nwk_dst_raw"] // .wpan["wpan_wpan_dst16_raw"]),
|
||||
cluster: .zbee_aps["zbee_aps_zbee_aps_cluster_raw"],
|
||||
cmd: .zbee_zcl["zbee_zcl_zbee_zcl_cmd_id_raw"],
|
||||
data: .zbee_zcl_raw
|
||||
}
|
||||
16
utils/live-zigbee.sh
Executable file
16
utils/live-zigbee.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# this script expects a zigbee sniffer to be running,
|
||||
# which dumps zigbee packets to udp on localhost
|
||||
#
|
||||
# one example is zsmartsystems zigbee sniffer:
|
||||
#
|
||||
# git clone https://github.com/zsmartsystems/com.zsmartsystems.zigbee.sniffer.git
|
||||
# cd com.zsmartsystems.zigbee.sniffer
|
||||
#
|
||||
# java -jar com.zsmartsystems.zigbee.sniffer-1.0.2.jar -p /dev/serial/by-id/${ZIGBEE_ADAPTER} -c $ZIGBEE_CHANNEL -b 115200 -flow software -r 1337
|
||||
#
|
||||
|
||||
BASEDIR="$(realpath -- $(dirname $0))"
|
||||
|
||||
tshark -ni lo -lq -T ek -x | stdbuf -i0 -o0 ${BASEDIR}/filter-zigbee.jq | cargo run --example=ez-parse
|
||||
12
utils/pcap-extract-zigbee.sh
Executable file
12
utils/pcap-extract-zigbee.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
BASEDIR="$(realpath -- $(dirname $0))"
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "usage: $0 <input.pcap>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for name in "$@"; do
|
||||
tshark -r "${name}" -lq -T ek -x | "${BASEDIR}"/filter-zigbee.jq
|
||||
done
|
||||
Reference in New Issue
Block a user