Source code for bluetooth_numbers.utils

"""Module with utility functions for Bluetooth numbers."""
import re
from uuid import UUID

from bluetooth_numbers.exceptions import (
    No16BitIntegerError,
    NonStandardUUIDError,
    WrongOUIFormatError,
)

BASE_UUID: UUID = UUID("00000000-0000-1000-8000-00805F9B34FB")
"""Base UUID defined by the Bluetooth SIG."""

_OUI_RE = re.compile(r"^([0-9A-F]{2})[-:]*([0-9A-F]{2})[-:]*([0-9A-F]{2})$")
_NORMALIZED_OUI_RE = re.compile(r"^[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}$")


[docs]def is_normalized_oui(oui: str) -> bool: """Check whether the argument is a normalized OUI. A normalized OUI is a string with format "XX:YY:ZZ" where XX, YY and ZZ are uppercase hexadecimal digits. Args: oui (str): The string to check. Returns: bool: ``True`` if `oui` is a normalized OUI, ``False`` otherwise. Examples: >>> from bluetooth_numbers.utils import is_normalized_oui >>> is_normalized_oui("98-e7-43") False >>> is_normalized_oui("98:E7:43") True >>> is_normalized_oui("FOOBAR") False """ return bool(_NORMALIZED_OUI_RE.match(oui))
[docs]def normalize_oui(oui: str) -> str: """Normalize an OUI. This changes the letters in decimal digits to uppercase and places a colon between the OUI's bytes. Args: oui (str): The OUI to normalize. Raises: WrongOUIFormatError: If `oui` doesn't have the right format. Returns: str: `oui` as a normalized OUI. Examples: >>> from bluetooth_numbers.utils import normalize_oui >>> normalize_oui("98-e7-43") '98:E7:43' >>> normalize_oui("98e743") '98:E7:43' >>> normalize_oui("FOOBAR") Traceback (most recent call last): bluetooth_numbers.exceptions.WrongOUIFormatError: 'FOOBAR' """ oui_parts = _OUI_RE.match(oui.upper()) if oui_parts: return oui_parts.group(1) + ":" + oui_parts.group(2) + ":" + oui_parts.group(3) raise WrongOUIFormatError(oui)
[docs]def uuid128_to_uuid16(uuid128: UUID) -> int: """Convert a 128-bit standard Bluetooth UUID to a 16-bit UUID. Args: uuid128 (~uuid.UUID): A 128-bit Bluetooth UUID. Raises: NonStandardUUIDError: If `uuid128` is not a 128-bit standard Bluetooth UUID. Returns: int: A 16-bit UUID that is the short UUID of `uuid128`. Example: >>> from bluetooth_numbers.utils import uuid128_to_uuid16, uint16_to_hex >>> from uuid import UUID >>> uint16_to_hex(uuid128_to_uuid16(UUID('00001800-0000-1000-8000-00805f9b34fb'))) '0x1800' """ # noqa: E501 if is_standard_uuid128(uuid128): # Extract the 16-bit UUID return int.from_bytes(uuid128.bytes[2:4], "big") raise NonStandardUUIDError(uuid128)
[docs]def is_standard_uuid128(uuid128: UUID) -> bool: """Check whether a 128-bit Bluetooth UUID is a standard UUID. Args: uuid128 (~uuid.UUID): A 128-bit Bluetooth UUID. Returns: bool: ``True`` if `uuid128` is a standard Bluetooth UUID, ``False`` otherwise. Examples: >>> from bluetooth_numbers.utils import is_standard_uuid128 >>> from uuid import UUID >>> is_standard_uuid128(UUID('00001800-0000-1000-8000-00805f9b34fb')) True >>> is_standard_uuid128(UUID('bfc46884-ea75-416b-8154-29c5d0b0a087')) False .. versionadded:: 1.1.0 """ uuid128_bytearray = bytearray(uuid128.bytes) uuid128_bytearray[2:4] = b"\x00\x00" uuid128_masked = UUID(bytes=bytes(uuid128_bytearray)) return uuid128_masked == BASE_UUID
[docs]def uuid16_to_uuid128(uuid16: int) -> UUID: """Convert a 16-bit UUID to a 128-bit UUID with the Bluetooth base UUID. Args: uuid16 (int): A 16-bit UUID. Raises: No16BitIntegerError: If `uuid16` is not an integer from 0 to 65535. Returns: ~uuid.UUID: A 128-bit UUID that is the full UUID of `uuid16`. Example: >>> from bluetooth_numbers.utils import uuid16_to_uuid128 >>> uuid16_to_uuid128(0x1800) UUID('00001800-0000-1000-8000-00805f9b34fb') """ if not is_uint16(uuid16): raise No16BitIntegerError(uuid16) uuid128 = bytearray(BASE_UUID.bytes) # Insert the 16-bit UUID in the third and fourth bytes of the 128-bit UUID. # For example: # 00000000-0000-1000-8000-00805f9b34fb # \ / # 1800 uuid128[2:4] = uuid16.to_bytes(2, "big") return UUID(bytes=bytes(uuid128))
[docs]def uint16_to_hex(number: int) -> str: """Convert a 16-bit UUID or Company ID to a hexadecimal string. Args: number (int): A 16-bit number. Raises: No16BitIntegerError: If `number` is not an integer from 0 to 65535. Returns: str: A hexadecimal string representation of `number`. Example: >>> from bluetooth_numbers.utils import uint16_to_hex >>> uint16_to_hex(0xFD6F) '0xfd6f' """ if not is_uint16(number): raise No16BitIntegerError(number) return f"{number:#0{6}x}"
[docs]def is_uint16(number: int) -> bool: """Check whether a number is a 16-bit unsigned integer. Args: number (int): The number to check. Returns: bool: ``True`` if `number` is a 16-bit unsigned integer, ``False`` otherwise. Examples: >>> from bluetooth_numbers.utils import is_uint16 >>> is_uint16(0x1800) True >>> is_uint16(-1) False """ return isinstance(number, int) and 0 <= number <= 0xFFFF # noqa: PLR2004