187 lines
5.8 KiB
Python
187 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import time
|
|
import sys
|
|
import socket
|
|
import threading
|
|
import select
|
|
import datetime
|
|
from pathlib import Path
|
|
import os
|
|
import struct
|
|
import secrets
|
|
import nacl
|
|
import nacl.hash
|
|
from nacl.encoding import RawEncoder
|
|
import configparser
|
|
|
|
from nacl.signing import SigningKey
|
|
|
|
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
|
|
|
|
class ZListener(ServiceListener):
|
|
def __init__(self, client):
|
|
self.client = client;
|
|
|
|
def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
print(f"Service {name} updated")
|
|
self.client.on_services_changed()
|
|
|
|
def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
print(f"Service {name} removed")
|
|
self.client.on_services_changed()
|
|
|
|
def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
info = zc.get_service_info(type_, name)
|
|
print(f"Service {name} added, service info: {info}")
|
|
self.client.host_addresses[name] = (socket.inet_ntoa(info.addresses[0]), info.port)
|
|
self.client.on_services_changed()
|
|
|
|
class Config():
|
|
def __init__(self, pathname):
|
|
config = configparser.ConfigParser(allow_unnamed_section=True)
|
|
config.read(pathname)
|
|
self.config = config
|
|
self._ota_private_key = None
|
|
|
|
def ble_private_key(self):
|
|
# FIXME unimplemented
|
|
pathname = self.config.get(configparser.UNNAMED_SECTION, 'BlePrivateKeyFile')
|
|
|
|
def ble_peer_key(self):
|
|
# FIXME unimplemented
|
|
pathname = self.config.get(configparser.UNNAMED_SECTION, 'BlePeerKeyFile')
|
|
|
|
def ota_private_key(self):
|
|
if not self._ota_private_key:
|
|
pathname = self.config.get(configparser.UNNAMED_SECTION, 'OtaPrivateKeyFile')
|
|
with open(pathname, "rb") as f:
|
|
self._ota_private_key = SigningKey(f.read(), encoder=RawEncoder)
|
|
|
|
return self._ota_private_key
|
|
|
|
def output_path(self):
|
|
pathname = Path(self.config.get(configparser.UNNAMED_SECTION, 'OutputPath'))
|
|
pathname.mkdir(0o777, False, True)
|
|
return pathname
|
|
|
|
class Client():
|
|
def __init__(self, config):
|
|
self.selected_service_name = None
|
|
self.bytes_recorded = 0
|
|
self.private_key = config.ota_private_key()
|
|
name = f"{int(time.time())}.raw"
|
|
self._output_pathname = config.output_path() / name
|
|
|
|
self.started = False
|
|
self.host_addresses = {}
|
|
zeroconf = Zeroconf()
|
|
ServiceBrowser(zeroconf, "_keihin._udp.local.", ZListener(self))
|
|
|
|
def knows_service(self, servicename):
|
|
return not (not (self.host_addresses.get(servicename, False)))
|
|
|
|
def output_pathname(self):
|
|
return self._output_pathname
|
|
|
|
def recording_thread_main(self):
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
addr = self.host_addresses[self.selected_service_name]
|
|
print(self.selected_service_name)
|
|
print(addr)
|
|
sock.connect(addr)
|
|
sock.setblocking(0)
|
|
interval = 500
|
|
|
|
self.bytes_recorded = 0
|
|
|
|
with open(self.output_pathname(), "ab") as outf:
|
|
while self.started:
|
|
print("sending subscribe")
|
|
b = self.session_key + bytes([interval >> 8, interval & 0xff, 0x54, 0x11, 0, 25] )
|
|
sock.send(b)
|
|
timeout = time.time() + 60
|
|
while self.started and time.time() < timeout:
|
|
ready = select.select([sock], [], [], timeout - time.time())
|
|
if ready[0]:
|
|
data = sock.recv(4000)
|
|
self.bytes_recorded = self.bytes_recorded + len(data)
|
|
outf.write(data)
|
|
outf.flush()
|
|
sock.close()
|
|
print("recording thread terminated")
|
|
|
|
def select_service_name(self, name):
|
|
if name == None:
|
|
self.started = False
|
|
self.selected_service_name = name
|
|
self.on_services_changed()
|
|
|
|
def register_session(self):
|
|
session_key = secrets.token_bytes(32)
|
|
sock = socket.create_connection(self.host_addresses[self.selected_service_name])
|
|
sig = self.private_key.sign(session_key, encoder=RawEncoder)
|
|
|
|
sock.send(b"KEY0" + session_key + sig.signature)
|
|
response = sock.recv(200)
|
|
print(response.decode(errors="replace"))
|
|
sock.close()
|
|
return session_key
|
|
|
|
def start_recording(self):
|
|
if self.selected_service_name != None:
|
|
self.session_key = self.register_session()
|
|
self.started = True
|
|
print("starting record")
|
|
peer_thread = threading.Thread(target=self.recording_thread_main, daemon = True)
|
|
peer_thread.start()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def stop_recording(self):
|
|
print("stopping record")
|
|
self.started = False
|
|
|
|
def on_services_changed(self):
|
|
# override this in subclass if you want to know when
|
|
# service instances appear/disappear
|
|
return None
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import pprint
|
|
|
|
exists = False
|
|
|
|
conf_file = sys.argv[1]
|
|
name = sys.argv[2]
|
|
timeout = int(sys.argv[3])
|
|
|
|
fqname = f"{name}._keihin._udp.local."
|
|
|
|
class MyClient(Client):
|
|
def on_services_changed(self):
|
|
print("services changed: now known ")
|
|
pprint.pp(self.host_addresses)
|
|
if self.host_addresses.get(fqname, False):
|
|
print(f"found {fqname}")
|
|
|
|
config = Config(conf_file)
|
|
client = MyClient(config)
|
|
|
|
while not exists:
|
|
time.sleep(2)
|
|
exists = client.knows_service(fqname)
|
|
client.select_service_name(fqname)
|
|
|
|
print("record")
|
|
start_time = time.time()
|
|
client.start_recording()
|
|
while time.time() < start_time + timeout:
|
|
time.sleep(1)
|
|
print(f"{client.bytes_recorded} bytes received")
|
|
|
|
client.stop_recording()
|
|
print("fin")
|