Files
eculocate/client/eculocate.py

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")