def prune(self): for msgid in self.keys(): ctime = os.stat(self.path + util.tohex(msgid)).st_ctime diff = time.time() - ctime if diff > config.PRUNE_TIME: self.ignorekey(msgid) print("{} is too old, ignoring".format(util.tohex(msgid)))
def ignorekey(self, key): with open("ignored-keys", "ab") as f: f.write(key) fname = util.tohex(key) if config.PRUNE_DELETE: os.remove(self.path + fname) else: os.rename(self.path + fname, self.path + "archive/" + fname)
def write(self, code, data): cmd = struct.pack( "hBBiii", code, 0x10, 0, # some flags? 0, # unknown len(data), 0, # unknown ) print('write {:02x} {:08x} = {}'.format(code, len(data), tohex(data))) self.usb_write(cmd + data)
def read(self, code, size): cmd = struct.pack( "hBBiii", code, 0x11, 0, # some flags? 0, # unknown size, 0, # unknown ) self.usb_write(cmd) res = self.usb_read(size) print('read {:02x} {:08x} = {}'.format(code, size, tohex(res))) return res
def main(argv): filename = '' text = '' usagetext = 'reader.py [-v]erbose -f <filename>\nreader.py [-v]erbose -t \'<CA FE BA BE...>\'' verbosity = 0 # setup known keys dictionarry by their device id keys = { '\x57\x00\x00\x44': '\xCA\xFE\xBA\xBE\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xCA\xFE\xBA\xBE', '\x00\x00\x00\x00': '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' } try: opts, args = getopt.getopt(argv, "vt:f:", ["text=", "filename="]) except getopt.GetoptError: print usagetext sys.exit(2) for opt, arg in opts: if opt in ("-f", "--filename"): filename = arg text = open(filename, 'r').read() if opt in ("-t", "--text"): text = arg if opt in ("-v"): verbosity = 2 if verbosity > 0: print "filename: ", filename print "text: ", text print "verbosity: ", verbosity capture = bytearray().fromhex(text) if verbosity > 0: print "hex: ", util.tohex(capture) frame = WMBusFrame() frame.parse(capture, keys) frame.log(verbosity)
def main(argv): filename = '' text = '' usagetext = 'reader.py [-v]erbose -f <filename>\nreader.py [-v]erbose -t \'<CA FE BA BE...>\'' verbosity = 0 # setup known keys dictionarry by their device id keys = { '\x57\x00\x00\x44': '\xCA\xFE\xBA\xBE\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xCA\xFE\xBA\xBE', '\x00\x00\x00\x00': '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' } try: opts, args = getopt.getopt(argv,"vt:f:",["text=", "filename="]) except getopt.GetoptError: print usagetext sys.exit(2) for opt, arg in opts: if opt in ("-f", "--filename"): filename = arg text = open(filename, 'r').read() if opt in ("-t", "--text"): text = arg if opt in ("-v"): verbosity = 2 if verbosity > 0: print "verbosity: ", verbosity print "filename: ", filename print "txt: ",text capture = bytearray().fromhex(text) if verbosity > 0: print "hex: ", util.tohex(capture) frame = WMBusFrame() frame.parse(capture, keys) frame.log(verbosity)
def parse(self, arr, keys=None): """ Parses frame contents and initializes object values The first steps of setting up an WMBusFrame should be the initialization of the class and passing the wM-Bus frame as an array to the parse method in order to initialize the object values. Optionally, the parse method takes a keys dictionarry which lists known keys by their device id. E.g. keys = { '\x57\x00\x00\x44': '\xCA\xFE\xBA\xBE\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xCA\xFE\xBA\xBE', '\x00\x00\x00\x00': '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' } """ if len(arr)-1 != arr[0]: print "WARNING: frame length field does not match effective frame length! Decoding might be unreliable. Check your input." print "frame[0]: ", arr[0] print "len(frame)-1: ", len(arr)-1 if (arr is not None and arr[0] >= 11): self.length = arr[0] self.control = arr[1] self.manufacturer = arr[2:4] self.address = arr[4:10] self.control_information = arr[10] self.data = arr[11:] if (self.is_with_long_tl()): self.header = WMBusLongDataHeader() self.header.parse(self.data[0:12]) self.data = self.data[12:] ''' Note that according to the standard, the manufacturer and device id from the transport header have precedence over the frame information ''' #self.manufacturer = header.manufacturer #self.address[0,4] = header.identification #self.address[4] = header.version #self.address[5] = header.device_type elif (self.is_with_short_tl()): self.header = WMBusShortDataHeader() self.header.parse(self.data[0:4]) self.data = self.data[4:] self.data_size = len(self.data) if (keys): devid = ''.join(chr(b) for b in self.get_device_id()) self.key = keys.get(devid, None) # time might come where we should move this into a function if (self.header and self.header.get_encryption_mode() == 5): # data is encrypted. thus, check if a key was specified if (self.key): # setup cipher specs, decrypt and strip padding spec = AES.new(self.key, AES.MODE_CBC, "%s" % self.get_iv()) self.data = bytearray(spec.decrypt("%s" % self.data)) if debug: print "dec: ", util.tohex(self.data) # check whether the first two bytes are 2F if (self.data[0:2] != '\x2F\x2F'): print util.tohex(self.data) raise Exception("Decryption failed") self.data = bytearray(self.data.lstrip('\x2F').rstrip('\x2F')) if debug: print "cut: ", util.tohex(self.data) while len(self.data) > 0: record = WMBusDataRecord() self.data = record.parse(self.data) self.records.append(record) else: print "(%d) " % arr[0] + util.tohex(arr) raise Exception("Invalid frame length")
def log(self, verb): """ Print a log record for that frame The log record consist of the following information - timestamp - device manufacturer, serial, type and version - frame direction, purpose Depending on the verbosity, additional details could be printed - frame header info - transport header info - data records The log method takes three levels of verbosity 0: just single line 1: additionally log frame header and transpor header info 2: additionally log data records """ line = datetime.now().strftime("%b %d %H:%M:%S") + " " line += self.get_manufacturer_short() + " " line += util.tohex(self.get_device_id()) + " " line += self.get_function_code() + " " if self.records: line += 'Records: %d' % len(self.records) if verb >= 1: line += '\n--' line += "\nCI Detail:\t" + util.tohex(self.control_information) + " (" + self.get_ci_detail() + ", " + self.get_function_code() + ")" line += "\nheader:\t\t" + self.header_details() if (self.is_with_long_tl() or self.is_with_short_tl()): line += "\nhas errors:\t%r" % self.header.has_errors() line += "\naccess:\t\t" + self.header.accessibility() if (self.header.configuration): line += "\nconfig word:\t" + util.tohex(self.header.configuration) line += "\nmode:\t\t%d" % self.header.get_encryption_mode() + " (" + self.header.get_encryption_name() + ")" if (self.is_encrypted()): line += "\niv:\t\t" + util.tohex(self.get_iv()) if (self.key): line += "\nkey:\t\t" + util.tohex(self.key) else: line += "\nkey:\t\tWARNING no suitable key configured" line += '\n--' if verb >= 2: for rec in self.records: val = rec.value val.reverse() line += '\nDIFs:\t' + util.tohex(rec.header.dif) line += " (" + rec.header.get_function_field_name() line += ", " + rec.header.get_data_field_name() + ")" line += '\nVIFs:\t' + util.tohex(rec.header.vif) line += " (" + rec.header.get_vif_description() + ")" line += '\nValue:\t' + util.tohex(val) line += '\n--' else: line += 'Data: ' + util.tohex(self.data) ''' line += "v%0.3d" % self.get_device_version() + " " line += self.get_device_type() + " (" + util.tohex(self.address[5]) + ") " ''' print line
def main(argv): samplefile = '' interface = '/dev/ttyUSB3' usagetext = 'scanner.py -hv -i <interface>' verbosity = 0 # setup known keys dictionarry by their device id keys = { '\x57\x00\x00\x44': '\xCA\xFE\xBA\xBE\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xCA\xFE\xBA\xBE', '\x00\x00\x00\x00': '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' } try: opts, args = getopt.getopt(argv,"v:hi:",["interface="]) except getopt.GetoptError: print usagetext sys.exit(2) for opt, arg in opts: if opt == '-h': print usagetext sys.exit() else: if opt in ("-i", "--interface"): interface = arg if opt == "-v": verbosity = 1 if arg == 'v': verbosity = 2 if arg == 'vv': verbosity = 3 while 1: # setup values arr = bytearray() state = 0 frame_length = -1 # connect sniffer device ser = util.connect_sniffer(interface) # sleep for a while in case there is no data available while ser.inWaiting() == 0: time.sleep(2) # data arrived, go and get it while ser.inWaiting() > 0: if (state == 0): ''' let's get the leading two bytes from the serial stream and check whether they match hex FF 03. Do this until we reach the next FF 03 start sequence TODO: - How is the trailing byte checksum calculated? ''' arr.append(ser.read(1)) if (arr[0] == 0xFF): # found 0xFF, let's see whether the following byte is 0x03 arr.append(ser.read(1)) if (arr[0] == 0xFF and len(arr) == 2 and arr[1] == 0x03): # just hit a valid start sequence => enter next state state = 1 else: ''' just hit an invalid start sequence. let's drop the bytes and start over ''' arr = bytearray() elif (state == 1): # let's read the frame length from the next byte arr.append(ser.read(1)) frame_length = arr[2] -1 state = 2 elif (state == 2): ''' in case the payload length is greater than zero bytes, read frame_length bytes from the serial stream ''' if (len(arr)-3 < frame_length): for i in range(frame_length): arr.append(ser.read(1)) if (verbosity >= 3): # print the whole wireless M-Bus frame in hex print util.tohex(arr) # instantiate an wireless m-bus frame based on the data frame = WMBusFrame() frame.parse(arr[2:], keys) # print wM-Bus frame information as log line frame.log(verbosity) # clear array and go to detect the next start sequence arr = bytearray() state = 0
def __setitem__(self, key, value): if self.path == "---null---/": return itempath = self.path + util.tohex(key) with open(itempath, "wb") as f: f.write(value.serialise())
def __getitem__(self, key): itempath = self.path + util.tohex(key) with open(itempath, "rb") as f: data = f.read() return message.from_serialised(data)
def runcmd(command, arguments): if command == "sync": try: if len(arguments) >= 2: client.sync(arguments[0], int(arguments[1])) elif len(arguments) == 1: client.sync(arguments[0]) elif len(arguments) == 0: client.sync("localhost") except ConnectionRefusedError: print("Could not connect to daemon at address") return 1 except (socket.gaierror, OSError): # The gaierror is thrown with an invalid hostname, and the OSError # is thrown with an IP such as 255.255.255.255. print("Malformed address") return 1 except ValueError: print("Invalid port") return 1 except OverflowError: print("Port must be between 0 and 65535") return 1 elif command == "ls": for msgid in known_messages.keys(): print(util.tohex(msgid)) elif command == "help" and len(arguments) == 0: global first_help if first_help: print("Square brackets indicate optional arguments") print("Angle brackets indicate required arguments") print("A default is given for some commands") print("") first_help = False print("sync [IP = localhost] [PORT = 3514]") print("ls") print("help [COMMAND]") print("msg [recipients]") print("attach [recipients] <filename>") print("read <msgid>") print("quit") elif command == "help" and len(arguments) > 0: helpcmd = arguments[0] if helpcmd == "sync": print("Push and pull all your currently known messages to the") print("specified server. With no arguments, sync with own daemon") print("which won't do anything, but is good for debugging.") elif helpcmd == "ls": print("List all known messages by their msgid. Does not filter") print("for if you can actually read them at the moment") elif helpcmd == "help": print("You're using it now.") elif helpcmd == "attach": print("Allows you to insert binary files to the network.") print("Currently there are no restrictions, but obviously") print("inserting a 4GB file will be slow, and in the future") print("nodes may reject large files, or delete them sooner.") elif helpcmd == "read": print("Read the message specified by the msgid.") print("Partial ids are supported, with/without the 0x prefix.") elif helpcmd == "msg": print("Write textual messages in an editor") elif helpcmd == "quit": print("Quits the program, because ^C is too hard") print("This isn't the only quit string, however") elif command == "msg": recipients = arguments if len(recipients) == 0: print("Enter email addresses one by one for recipients") print("These must be the same as GPG knows them as") print("The above is very important, you might want to run") print(" gpg --list-keys") print("To make sure you're using the correct email") print("There is currently no error checking for invalid input") print() print("Enter a blank line to end input") while True: r = input("email: ") if r == "": break recipients.append(r.strip()) data = util.getinput() program = ["gpg", "--encrypt", "--sign"] for r in recipients: program.append("-r") program.append(r) encsign = subprocess.check_output(program, input=data.encode("UTF-8")) newmsg = message.message(encsign) known_messages[newmsg.msgid] = newmsg print("ID: {}".format(util.tohex(newmsg.msgid))) print("Message added to known messages") print("Run a sync against a known node, or wait for the syncd to run") elif command == "attach": if len(arguments) < 2: print("You need to include a recipient and the filename") return 1 recipients = arguments[:-1] fname = arguments[-1] try: with open(fname, "rb") as f: data = f.read() except FileNotFoundError: print("Invalid filename") return 1 program = ["gpg", "--encrypt", "--sign"] for r in recipients: program.append("-r") program.append(r) try: encsign = subprocess.check_output(program, input=data) except subprocess.CalledProcessError: print("Something happened, GPG was unable to encrypt it.") return 1 newmsg = message.message(encsign) known_messages[newmsg.msgid] = newmsg print("ID: {}".format(util.tohex(newmsg.msgid))) print("Attachment added to known messages") print("Run a sync against a known node, or wait for the syncd to run") elif command == "read": if len(arguments) == 0: print("What do you want me to read?") else: foundmsgs = [] wantedhex = arguments[0] if not(wantedhex.startswith("0x")): wantedhex = "0x" + wantedhex wantedhex = wantedhex.encode("ascii") for msgid in known_messages.keys(): msgidhex = util.tohex(msgid) if msgidhex.startswith(wantedhex.decode("ascii")): foundmsgs.append(msgid) if len(foundmsgs) > 1: print("More than one message found, be more specific") elif len(foundmsgs) == 0: print("No messages found") else: try: msg = known_messages[foundmsgs[0]].gpg except ValueError: print("Checksum failure on message") print("The file is most likely corrupt, or there's a bug") return 1 try: decrypted = subprocess.check_output("gpg", input=msg) except subprocess.CalledProcessError: print("Something happened. GPG was unable to decrypt it") return 1 # TODO This is because this wasn't actually addressed to # the user. Maybe ls should filter out these? try: decrypted = decrypted.decode("UTF-8") if interactive: util.writeoutput(decrypted) else: sys.stdout.write(decrypted) except UnicodeDecodeError: # Likely a binary file. if interactive: print("This is a binary file.") print("Enter a filename to save it as") fname = input("fname: ") with open(fname, "wb") as f: f.write(decrypted) else: sys.stdout.buffer.write(decrypted) elif command in (":q", "quit", "exit", "bye"): sys.exit(0) else: print("Unknown command")