Challenge string calculation
Post date: Nov 21, 2019 4:58:01 PM
The main showstopper in communicating via Microlink is the challenge string that the UPS and PC use to authenticate. After an initial run through a significant part of the message space, the UPS sends messages 0x7e and 0x7f and continues again from message ID 0x00. If the PC does not send a message 0x7e with the right 4 byte answer soon after, UPS communication halts. At this point one needs to wait a long time or reset the UPS by unplugging it completely to be able to communicate again.
After a lot of experimenting and repeating the challenge string sequence it now seems to me that the UPS generates the a 2-byte challenge and puts this in message 0x7e at bytes 8-10 (or maybe 8-12, but 2 bytes seems to work for now). Let's call this password_1. This value changes after every communication reset.
For example:
UPS: 7e 00 00 00 00 00 00 00 00 f6 27 00 00 00 00 00 00 33 30
PC : 7e 0c 04 4d 3e 9b 60 90 59
UPS: 7e 00 00 00 00 00 00 00 00 29 a7 09 24 4d 3e 9b 60 44 b7
The PC then answers the challenge by combining password_1 (f6 27) with other fixed numbers that were announced by the UPS in the previous messages: A 2-byte value taken from message 0x00, an 8 byte value from message 0x00 (the header), the unit's serial number (14 bytes) from message 0x40 and finally password_1. The result of this operation is the 4 byte value (4d 3e 9b 60) captured above.
The operation is straightforward: all the bytes of the above are added together, with a modulo operator avoiding the overflow.
The first 2 bytes of the answer have no effect and are ignored, they also have a random distribution. The last 2 bytes are the result of the modulo addition.
def calculate_challenge(self):
'''
Calculate challenge from actual known ups state
'''
b0 = self.ups_state['header_raw'][3]
b1 = self.ups_state['header_raw'][4]
for header_byte in self.ups_state['header_raw']:
b0 = (b0 + header_byte) % 255
b1 = (b1 + b0) % 255
for serial_nb_byte in self.ups_state['serial_nb_raw']:
b0 = (b0 + serial_nb_byte) % 255
b1 = (b1 + b0) % 255
for pw1_byte in self.ups_state['password_1'][0:2]:
b0 = (b0 + pw1_byte) % 255
b1 = (b1 + b0) % 255
challenge = bytearray([1, 1, b0, b1])
return challenge