eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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