#!/usr/bin/env python3 libexec_dir = __LIBEXECDIR__ import select import subprocess import sys def writeall(f, b): written = 0 while written < len(b): written += f.write(b[written:]) def readall(f, length): read = bytearray() while len(read) < length: data = f.read(length - len(read)) if data == b'': raise ConnectionError('Could not satisfy read of %i bytes' % length) read.extend(data) return bytes(read) def readall_u16(f): u16_bytes = readall(f, 2) return (u16_bytes[0] << 8) | u16_bytes[1] def parse_mac(text): parts = text.split(':') if len(parts) != 6: raise ValueError('Invalid MAC format: %s' % text) try: parsed = bytes(int(field, 16) for field in parts) except ValueError: raise ValueError('Invalid MAC format %s' % text) return parsed def format_mac(mac): return ':'.join(mac[i:i+1].hex() for i in range(len(mac))) def format_status(status): if status == 0: return 'available' elif status == 1: return 'unavailable' elif status == 2: return 'offline' else: raise ValueError('Unknown status %i' % status) def send_message(backend, mac, message): encoded = message.encode('utf-8') writeall(backend, b'm' + mac + bytes([len(encoded) >> 8, len(encoded) & 0xff]) + encoded) def send_status_request(backend, mac): writeall(backend, b'r' + mac) def handle_user_command(backend, line): if len(line) > 0 and line[0] == '/': command, _, rest = line.partition(' ') if command == '/msg': # Send message to target mac_str, _, message = rest.partition(' ') mac = parse_mac(mac_str) send_message(backend, mac, message) elif command == '/status': # Request status mac = parse_mac(rest) send_status_request(backend, mac) else: # Display usage print('/msg ; /status ') else: # Display usage print('/msg ; /status ') def eventloop(proc): # Create unbuffered version of stdin unbuf_stdin = open(sys.stdin.buffer.fileno(), 'rb', buffering = 0) # Set up a poll for inputs (but do output blockingly) poll = select.poll() poll.register(proc.stdout, select.POLLIN) poll.register(unbuf_stdin, select.POLLIN) input_buffer = bytearray() running = True while running: for fd, event in poll.poll(): if fd == proc.stdout.fileno() and event & select.POLLIN: event_type = readall(proc.stdout, 1) if event_type == b's': # Status source_mac = readall(proc.stdout, 6) status, = readall(proc.stdout, 1) nick_length, = readall(proc.stdout, 1) nick = readall(proc.stdout, nick_length,) print('%s (%s) ~%s' % (format_mac(source_mac), format_status(status), nick.decode('utf-8'))) elif event_type == b'i': # Msgid for message msgid = readall_u16(proc.stdout) print('(msgid: %i)' % msgid) #debg elif event_type == b'I': # Failed to get msgid for message print('(msgid fail)') #debg elif event_type == b'a': # ACK received source_mac = readall(proc.stdout, 6) msgid = readall_u16(proc.stdout) print('(ack: %s %i)' % (format_mac(source_mac), msgid)) #debg elif event_type == b'A': # ACK not received (and message send failed) print('(ack failed)') #debg elif event_type == b'm': # Message received source_mac = readall(proc.stdout, 6) message_length = readall_u16(proc.stdout) message = readall(proc.stdout, message_length) print('<%s> %s' % (format_mac(source_mac), message.decode('utf-8'))) #debg else: # Not sth we handle yet data = proc.stdout.read(1023) if data == b'': data = b'[!] ' + event_type else: data = b'[!] ' + event_type + data sys.stdout.buffer.write(data) sys.stdout.buffer.flush() elif fd == proc.stdout.fileno() and event & select.POLLHUP: print('Backend exited') running = False elif fd == unbuf_stdin.fileno() and event & select.POLLIN: data = unbuf_stdin.read(1024) input_buffer.extend(data) while True: newline_location = input_buffer.find(b'\n') if newline_location == -1: break line, _, input_buffer = input_buffer.partition(b'\n') handle_user_command(proc.stdin, line.decode('utf-8')) if data == b'': # ^D writeall(proc.stdin, b'q') running = False else: raise Exception('Unreachable') def main(): _, interface, nick = sys.argv proc = subprocess.Popen(['sudo', libexec_dir + '/ethermess-backend', interface], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = sys.stderr, bufsize = 0) # Tell the backend the status and nick status = 0 nick = nick.encode('utf-8') writeall(proc.stdin, bytes([status, len(nick)]) + nick) # Read our MAC mac = readall(proc.stdout, 6) print('Own mac: %s' % format_mac(mac)) eventloop(proc) proc.wait() if __name__ == '__main__': main()