import enum
import math
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import modes
[docs]class SX3Profile:
"""
Represents the profile for the GATT UUIDs for the S3/X3. Also contains functionality
to encrypt and decrypt BLE payloads, as well as building the authentication payload.
:param key: The hexidecimal string of the encrypted key from Vanmoof servers.
:param user_key_id: Int of the user key id from Vanmoof servers.
"""
def __init__(self, key: str, user_key_id: int) -> None:
self._cipher = Cipher(algorithms.AES(bytes.fromhex(key)), modes.ECB())
self._user_key_id = user_key_id
[docs] def build_authentication_payload(self, nonce: bytes) -> bytes:
"""
Builds the authentication payload given a nonce.
:param nonce: A bytes array that represents the nonce from a challenge response.
"""
encryptor = self._cipher.encryptor()
data = bytearray(16)
data[0:2] = nonce
data = bytearray(encryptor.update(data) + encryptor.finalize())
# Append the user key id
data.extend([0, 0, 0, self._user_key_id])
return bytes(data)
[docs] def decrypt_payload(self, data: bytes) -> bytes:
"""
Decrypts a bluetooth payload.
:param data: A bytes array of data. Must be a multiple of 16 bytes long.
"""
decryptor = self._cipher.decryptor()
return decryptor.update(data) + decryptor.finalize()
[docs] def build_encrypted_payload(self, nonce: bytes, data: bytes) -> bytes:
"""
Encrypts data signed with a nonce. This will build a payload and pad with
zeroes to the nearest multiple of 16 bytes.
:param nonce: A bytes array that represents the nonce from a challenge response.
:param data: A bytes array of data.
"""
encryptor = self._cipher.encryptor()
payload = bytearray(16)
payload[0:2] = nonce
payload[2:] = data
# Pad to the nearest cipher 16 byte block size
for _ in range(math.ceil(len(payload) / 16) * 16 - len(payload)):
payload.append(0)
return bytes(encryptor.update(payload) + encryptor.finalize())
[docs] class Security(enum.Enum):
SERVICE_UUID = "6acc5500-e631-4069-944d-b8ca7598ad50"
CHALLENGE = "6acc5501-e631-4069-944d-b8ca7598ad50"
KEY_INDEX = "6acc5502-e631-4069-944d-b8ca7598ad50"
BACKUP_CODE = "6acc5503-e631-4069-944d-b8ca7598ad50"
BIKE_MESSAGE = "6acc5505-e631-4069-944d-b8ca7598ad50"
[docs] class Defense(enum.Enum):
SERVICE_UUID = "6acc5520-e631-4069-944d-b8ca7598ad50"
LOCK_STATE = "6acc5521-e631-4069-944d-b8ca7598ad50"
UNLOCK_REQUEST = "6acc5522-e631-4069-944d-b8ca7598ad50"
ALARM_STATE = "6acc5523-e631-4069-944d-b8ca7598ad50"
ALARM_MODE = "6acc5524-e631-4069-944d-b8ca7598ad50"
[docs] class Movement(enum.Enum):
SERVICE_UUID = "6acc5530-e631-4069-944d-b8ca7598ad50"
DISTANCE = "6acc5531-e631-4069-944d-b8ca7598ad50"
SPEED = "6acc5532-e631-4069-944d-b8ca7598ad50"
UNIT_SYSTEM = "6acc5533-e631-4069-944d-b8ca7598ad50"
POWER_LEVEL = "6acc5534-e631-4069-944d-b8ca7598ad50"
SPEED_LIMIT = "6acc5535-e631-4069-944d-b8ca7598ad50"
E_SHIFTER_GEAR = "6acc5536-e631-4069-944d-b8ca7598ad50"
E_SHIFTIG_POINTS = "6acc5537-e631-4069-944d-b8ca7598ad50"
E_SHIFTER_MODE = "6acc5538-e631-4069-944d-b8ca7598ad50"
[docs] class BikeInfo(enum.Enum):
SERVICE_UUID = "6acc5540-e631-4069-944d-b8ca7598ad50"
MOTOR_BATTERY_LEVEL = "6acc5541-e631-4069-944d-b8ca7598ad50"
MOTOR_BATTERY_STATE = "6acc5542-e631-4069-944d-b8ca7598ad50"
MODULE_BATTERY_LEVEL = "6acc5543-e631-4069-944d-b8ca7598ad50"
MODULE_BATTERY_STATE = "6acc5544-e631-4069-944d-b8ca7598ad50"
BIKE_FIRMWARE_VERSION = "6acc554a-e631-4069-944d-b8ca7598ad50"
BLE_CHIP_FIRMWARE_VERSION = "6acc554b-e631-4069-944d-b8ca7598ad50"
CONTROLLER_FIRMWARE_VERSION = "6acc554c-e631-4069-944d-b8ca7598ad50"
PCBA_HARDWARE_VERSION = "6acc554d-e631-4069-944d-b8ca7598ad50"
GSM_FIRMWARE_VERSION = "6acc554e-e631-4069-944d-b8ca7598ad50"
E_SHIFTER_FIRMWARE_VERSION = "6acc554f-e631-4069-944d-b8ca7598ad50"
BATTERY_FIRMWARE_VERSION = "6acc5550-e631-4069-944d-b8ca7598ad50"
# data returned seems to be firmware version info?
_UNKNOWN = "6acc5551-e631-4069-944d-b8ca7598ad50"
FRAME_NUMBER = "6acc5552-e631-4069-944d-b8ca7598ad50"
[docs] class BikeState(enum.Enum):
SERVICE_UUID = "6acc5560-e631-4069-944d-b8ca7598ad50"
MODULE_MODE = "6acc5561-e631-4069-944d-b8ca7598ad50"
MODULE_STATE = "6acc5562-e631-4069-944d-b8ca7598ad50"
ERRORS = "6acc5563-e631-4069-944d-b8ca7598ad50"
WHEEL_SIZE = "6acc5564-e631-4069-944d-b8ca7598ad50"
CLOCK = "6acc5567-e631-4069-944d-b8ca7598ad50"
[docs] class Sound(enum.Enum):
SERVICE_UUID = "6acc5570-e631-4069-944d-b8ca7598ad50"
PLAY_SOUND = "6acc5571-e631-4069-944d-b8ca7598ad50"
SOUND_VOLUME = "6acc5572-e631-4069-944d-b8ca7598ad50"
BELL_SOUND = "6acc5574-e631-4069-944d-b8ca7598ad50"
[docs] class Light(enum.Enum):
SERVICE_UUID = "6acc5580-e631-4069-944d-b8ca7598ad50"
LIGHT_MODE = "6acc5581-e631-4069-944d-b8ca7598ad50"
SENSOR = "6acc5584-e631-4069-944d-b8ca7598ad50"