From 9b16c69bb68b979cbd3e9b6913bf342617dec510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Mon, 1 Mar 2021 22:46:46 +0000 Subject: Calculate multirecords checksum based on the raw bytes. The original FreeStyle Libre software is known for incorrectly truncating strings with UTF-8, which causes the multi-record strings not to decode correctly. But since the Libre actually sends these as they are provided, we can calculate the checksum correctly if we do that _before_ replacing the invalid codepoints. This splits the `send_text_command` method into a public and a private interface, with the public returning the expected string, while the private returns the raw bytes. It should probably be changed to always return bytes instead. This change fixes https://github.com/glucometers-tech/glucometerutils/issues/103. --- freestyle_hid/_session.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/freestyle_hid/_session.py b/freestyle_hid/_session.py index 7529ef3..aab1f6c 100644 --- a/freestyle_hid/_session.py +++ b/freestyle_hid/_session.py @@ -81,7 +81,7 @@ _TEXT_REPLY_FORMAT = re.compile( ) _MULTIRECORDS_FORMAT = re.compile( - "^(?P.+\r\n)(?P[0-9]+),(?P[0-9A-F]{8})\r\n$", re.DOTALL + b"^(?P.+\r\n)(?P[0-9]+),(?P[0-9A-F]{8})\r\n$", re.DOTALL ) @@ -191,7 +191,7 @@ class Session: return message - def send_text_command(self, command: bytes) -> str: + def _send_text_command(self, command: bytes) -> bytes: """Send a command to the device that expects a text reply.""" self.send_command(self._text_message_type, command) @@ -224,11 +224,14 @@ class Session: if match.group("status") != b"OK": raise CommandError(repr(message) or "Command failed") + return message + + def send_text_command(self, command: bytes) -> bytes: # If there is anything in the response that is not ASCII-safe, this is # probably in the patient name. The Windows utility does not seem to # validate those, so just replace anything non-ASCII with the correct # unknown codepoint. - return message.decode("ascii", "replace") + return self._send_text_command(command).decode("ascii", "replace") def query_multirecord(self, command: bytes) -> Iterator[Sequence[str]]: """Queries for, and returns, "multirecords" results. @@ -247,18 +250,22 @@ class Session: A CSV reader object that returns a record for each line in the reply buffer. """ - message = self.send_text_command(command) - logging.debug(f"Received multirecord message:\n{message}") - if message == "Log Empty\r\n": + message = self._send_text_command(command) + logging.debug(f"Received multi-record message:\n{message!r}") + if message == b"Log Empty\r\n": return iter(()) match = _MULTIRECORDS_FORMAT.search(message) if not match: - raise CommandError(message) + raise CommandError(repr(message)) records_str = match.group("message") _verify_checksum(records_str, match.group("checksum")) + # Decode here with replacement; the software does not deal with UTF-8 + # correctly, and appears to truncate incorrectly the strings. + records_str = records_str.decode("utf-8", "replace") + logging.debug(f"Received multi-record string: {records_str}") return csv.reader(records_str.split("\r\n")) -- cgit v1.2.3