NickWaterton / Roomba980-Python

Python program and library to control iRobot Roomba 980 Vacuum Cleaner
MIT License
382 stars 107 forks source link

password.py code explaining #81

Open barmat80 opened 4 years ago

barmat80 commented 4 years ago

Hello Nick. I'm trying to translate your Python code to Java, just for fun, but I'm having problems in understanding this piece of code in password.py file and how to translate it to Java:

if hasattr(str, 'decode'):
                # this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = 'f005efcc3b2900'.decode("hex")
            else:
                #this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = bytes.fromhex('f005efcc3b2900')
            #send socket
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(1000)

            #ssl wrap
            wrappedSocket = ssl.wrap_socket(
                sock, ssl_version=ssl.PROTOCOL_TLSv1_2, ciphers='DEFAULT@SECLEVEL=1')   #ciphers='HIGH:!DH:!aNULL' may work as well
            #connect and send packet
            try:
                wrappedSocket.connect((addr, 8883))
            except Exception as e:
                print("Connection Error %s" % e)
            wrappedSocket.send(packet)

Can you explain it in details?

Another question: Where did you find the documentation about Roomba?

Thank you!

Mattia

NickWaterton commented 4 years ago

@barmat80

There is no Roomba documentation, this is all reverse engineered. I got a lot of my information from https://github.com/koalazak/dorita980

I recommend reading through his (or her) code to see how it works (as well as mine).

To explain the code snippet:

            if hasattr(str, 'decode'):
                # this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = 'f005efcc3b2900'.decode("hex")
            else:
                #this is 0xf0 (mqtt reserved) 0x05(data length)
                # 0xefcc3b2900 (data)
                packet = bytes.fromhex('f005efcc3b2900')

This is just Python gymnastics to encode hex f005efcc3b2900 into packet as a byte string. This is an MQTT control 'magic packet', which requests the Roomba's password be returned, 0f is 'MQTT reserved', 05 is the data length, efcc3b2900 is the actual 'magic packet' that requests the password (5 bytes). We then send the packet to the Roomba MQTT server using an encrypted socket (which uses TLS for encryption, but has no certificate). addr is the Roomba ip address, 8883 is the Roomba MQTT port, we set a timout of 10 seconds so that we don't wait forever if something is wrong:

            #send socket
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(10)

            #ssl wrap
            wrappedSocket = ssl.wrap_socket(
                sock, ssl_version=ssl.PROTOCOL_TLS, ciphers='DEFAULT@SECLEVEL=1')   #ciphers='HIGH:!DH:!aNULL' may work as well
            #connect and send packet
            try:
                wrappedSocket.connect((addr, 8883))
            except Exception as e:
                print("Connection Error %s" % e)
            wrappedSocket.send(packet)

If our encryption is correct (and this can be a pain to get the right TLS verision), Roomba will respond with an MQTT packet containing it's password. Assuming our received data is 37 bytes long or more (ie we got something that looks right), we skip the MQTT control byte, data length, and magic packet - ie first 7 bytes - and decode the next 30 bytes as a text string (from unicode utf-8), ignoring any null termination. this gives us the Roomba MQTT password. It's wrapped in a loop to assemble fragmented packets, and handle the socket closing, but it's basically this:

data = b''
data_received = wrappedSocket.recv(1024)
data += data_received

# NOTE data is 0xf0 (mqtt RESERVED) length (0x23 = 35),
# 0xefcc3b2900 (magic packet), 0xXXXX... (30 bytes of
# password). so 7 bytes, followed by 30 bytes of password
# (total of 37)
password = str(data[7:].decode().rstrip('\x00')) #for i7 - has null termination

So now we should have the Roomba ip address, we know the port (8883), we have the BLID (MQTT login), and now we have the password (MQTT password), so we have everything we need to set up a regular (TLS encrypted) MQTT connection, and start communicating with the Roomba via normal MQTT methods.

Hope this explains how we get the MQTT password from the Roomba.

barmat80 commented 4 years ago

Thank you for explanation!