From 2a5043298e02c96478d18b2ebb52511f60d0befe Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sat, 28 Dec 2024 00:12:23 +0000 Subject: [PATCH] Fix parsing svg content --- .../oasis_mini/pyoasismini/__init__.py | 6 ++-- .../oasis_mini/pyoasismini/utils.py | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/custom_components/oasis_mini/pyoasismini/__init__.py b/custom_components/oasis_mini/pyoasismini/__init__.py index 7550f8d..248262a 100644 --- a/custom_components/oasis_mini/pyoasismini/__init__.py +++ b/custom_components/oasis_mini/pyoasismini/__init__.py @@ -1,5 +1,7 @@ """Oasis Mini API client.""" +from __future__ import annotations + import asyncio import logging from typing import Any, Awaitable, Final @@ -9,7 +11,7 @@ from aiohttp import ClientResponseError, ClientSession import async_timeout from .const import TRACKS -from .utils import _bit_to_bool +from .utils import _bit_to_bool, decrypt_svg_content _LOGGER = logging.getLogger(__name__) @@ -33,7 +35,6 @@ AUTOPLAY_MAP = { "4": "30 minutes", } - LED_EFFECTS: Final[dict[str, str]] = { "0": "Solid", "1": "Rainbow", @@ -112,6 +113,7 @@ class OasisMini: """Return the drawing progress percent.""" if not (self.track and (svg_content := self.track.get("svg_content"))): return None + svg_content = decrypt_svg_content(svg_content) paths = svg_content.split("L") total = self.track.get("reduced_svg_content", {}).get("1", len(paths)) percent = (100 * self.progress) / total diff --git a/custom_components/oasis_mini/pyoasismini/utils.py b/custom_components/oasis_mini/pyoasismini/utils.py index ec9c37e..667bf93 100644 --- a/custom_components/oasis_mini/pyoasismini/utils.py +++ b/custom_components/oasis_mini/pyoasismini/utils.py @@ -1,11 +1,17 @@ """Oasis Mini utils.""" +from __future__ import annotations + +import base64 import logging import math from xml.etree.ElementTree import Element, SubElement, tostring +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + _LOGGER = logging.getLogger(__name__) +APP_KEY = "5joW8W4Usk4xUXu5bIIgGiHloQmzMZUMgz6NWQnNI04=" BACKGROUND_FILL = ("#CCC9C4", "#28292E") COLOR_DARK = ("#28292E", "#F4F5F8") @@ -25,6 +31,7 @@ def draw_svg(track: dict, progress: int, model_id: str) -> str | None: if track and (svg_content := track.get("svg_content")): try: if progress is not None: + svg_content = decrypt_svg_content(svg_content) paths = svg_content.split("L") total = track.get("reduced_svg_content", {}).get(model_id, len(paths)) percent = min((100 * progress) / total, 100) @@ -137,3 +144,28 @@ def draw_svg(track: dict, progress: int, model_id: str) -> str | None: except Exception as e: _LOGGER.exception(e) return None + + +def decrypt_svg_content(svg_content: dict[str, str]): + """Decrypt SVG content using AES CBC mode.""" + if decrypted := svg_content.get("decrypted"): + return decrypted + + # decode base64-encoded data + key = base64.b64decode(APP_KEY) + iv = base64.b64decode(svg_content["iv"]) + ciphertext = base64.b64decode(svg_content["content"]) + + # create the cipher and decrypt the ciphertext + cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) + decryptor = cipher.decryptor() + decrypted = decryptor.update(ciphertext) + decryptor.finalize() + + # remove PKCS7 padding + pad_len = decrypted[-1] + decrypted = decrypted[:-pad_len].decode("utf-8") + + # save decrypted data so we don't have to do this each time + svg_content["decrypted"] = decrypted + + return decrypted