eculocate.py (5955B)
1 #!/usr/bin/env python3 2 3 import time 4 import sys 5 import socket 6 import threading 7 import select 8 import datetime 9 from pathlib import Path 10 import os 11 import struct 12 import secrets 13 import nacl 14 import nacl.hash 15 from nacl.encoding import RawEncoder 16 import configparser 17 18 from nacl.signing import SigningKey 19 20 from zeroconf import ServiceBrowser, ServiceListener, Zeroconf 21 22 class ZListener(ServiceListener): 23 def __init__(self, client): 24 self.client = client; 25 26 def update_service(self, zc: Zeroconf, type_: str, name: str) -> None: 27 print(f"Service {name} updated") 28 self.client.on_services_changed() 29 30 def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None: 31 print(f"Service {name} removed") 32 self.client.on_services_changed() 33 34 def add_service(self, zc: Zeroconf, type_: str, name: str) -> None: 35 info = zc.get_service_info(type_, name) 36 print(f"Service {name} added, service info: {info}") 37 self.client.host_addresses[name] = (socket.inet_ntoa(info.addresses[0]), info.port) 38 self.client.on_services_changed() 39 40 class Config(): 41 def __init__(self, pathname): 42 config = configparser.ConfigParser(allow_unnamed_section=True) 43 config.read(pathname) 44 self.config = config 45 self._ota_private_key = None 46 47 def ble_private_key(self): 48 # FIXME unimplemented 49 pathname = self.config.get(configparser.UNNAMED_SECTION, 'BlePrivateKeyFile') 50 51 def ble_peer_key(self): 52 # FIXME unimplemented 53 pathname = self.config.get(configparser.UNNAMED_SECTION, 'BlePeerKeyFile') 54 55 def ota_private_key(self): 56 if not self._ota_private_key: 57 pathname = self.config.get(configparser.UNNAMED_SECTION, 'OtaPrivateKeyFile') 58 with open(pathname, "rb") as f: 59 self._ota_private_key = SigningKey(f.read(), encoder=RawEncoder) 60 61 return self._ota_private_key 62 63 def output_path(self): 64 pathname = Path(self.config.get(configparser.UNNAMED_SECTION, 'OutputPath')) 65 pathname.mkdir(0o777, False, True) 66 return pathname 67 68 class Client(): 69 def __init__(self, config): 70 self.selected_service_name = None 71 self.bytes_recorded = 0 72 self.private_key = config.ota_private_key() 73 name = f"{int(time.time())}.raw" 74 self._output_pathname = config.output_path() / name 75 76 self.started = False 77 self.host_addresses = {} 78 zeroconf = Zeroconf() 79 ServiceBrowser(zeroconf, "_keihin._udp.local.", ZListener(self)) 80 81 def knows_service(self, servicename): 82 return not (not (self.host_addresses.get(servicename, False))) 83 84 def output_pathname(self): 85 return self._output_pathname 86 87 def recording_thread_main(self): 88 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 89 addr = self.host_addresses[self.selected_service_name] 90 print(self.selected_service_name) 91 print(addr) 92 sock.connect(addr) 93 sock.setblocking(0) 94 interval = 500 95 96 self.bytes_recorded = 0 97 98 with open(self.output_pathname(), "ab") as outf: 99 while self.started: 100 print("sending subscribe") 101 b = self.session_key + bytes([interval >> 8, interval & 0xff, 0x54, 0x11, 0, 25] ) 102 sock.send(b) 103 timeout = time.time() + 60 104 while self.started and time.time() < timeout: 105 ready = select.select([sock], [], [], timeout - time.time()) 106 if ready[0]: 107 data = sock.recv(4000) 108 self.bytes_recorded = self.bytes_recorded + len(data) 109 outf.write(data) 110 outf.flush() 111 sock.close() 112 print("recording thread terminated") 113 114 def select_service_name(self, name): 115 if name == None: 116 self.started = False 117 self.selected_service_name = name 118 self.on_services_changed() 119 120 def register_session(self): 121 session_key = secrets.token_bytes(32) 122 sock = socket.create_connection(self.host_addresses[self.selected_service_name]) 123 sig = self.private_key.sign(session_key, encoder=RawEncoder) 124 125 sock.send(b"KEY0" + session_key + sig.signature) 126 response = sock.recv(200) 127 print(response.decode(errors="replace")) 128 sock.close() 129 return session_key 130 131 def start_recording(self): 132 if self.selected_service_name != None: 133 self.session_key = self.register_session() 134 self.started = True 135 print("starting record") 136 peer_thread = threading.Thread(target=self.recording_thread_main, daemon = True) 137 peer_thread.start() 138 return True 139 else: 140 return False 141 142 def stop_recording(self): 143 print("stopping record") 144 self.started = False 145 146 def on_services_changed(self): 147 # override this in subclass if you want to know when 148 # service instances appear/disappear 149 return None 150 151 152 if __name__ == '__main__': 153 import pprint 154 155 exists = False 156 157 conf_file = sys.argv[1] 158 name = sys.argv[2] 159 timeout = int(sys.argv[3]) 160 161 fqname = f"{name}._keihin._udp.local." 162 163 class MyClient(Client): 164 def on_services_changed(self): 165 print("services changed: now known ") 166 pprint.pp(self.host_addresses) 167 if self.host_addresses.get(fqname, False): 168 print(f"found {fqname}") 169 170 config = Config(conf_file) 171 client = MyClient(config) 172 173 while not exists: 174 time.sleep(2) 175 exists = client.knows_service(fqname) 176 client.select_service_name(fqname) 177 178 print("record") 179 start_time = time.time() 180 client.start_recording() 181 while time.time() < start_time + timeout: 182 time.sleep(1) 183 print(f"{client.bytes_recorded} bytes received") 184 185 client.stop_recording() 186 print("fin")