1# Copyright 2024 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# -----------------------------------------------------------------------------
16# Imports
17# -----------------------------------------------------------------------------
18from __future__ import annotations
19import dataclasses
20import struct
21from typing import List, Type
22from typing_extensions import Self
23
24from bumble import utils
25
26
27# -----------------------------------------------------------------------------
28# Classes
29# -----------------------------------------------------------------------------
30@dataclasses.dataclass
31class Metadata:
32    '''Bluetooth Assigned Numbers, Section 6.12.6 - Metadata LTV structures.
33
34    As Metadata fields may extend, and Spec doesn't forbid duplication, we don't parse
35    Metadata into a key-value style dataclass here. Rather, we encourage users to parse
36    again outside the lib.
37    '''
38
39    class Tag(utils.OpenIntEnum):
40        # fmt: off
41        PREFERRED_AUDIO_CONTEXTS                 = 0x01
42        STREAMING_AUDIO_CONTEXTS                 = 0x02
43        PROGRAM_INFO                             = 0x03
44        LANGUAGE                                 = 0x04
45        CCID_LIST                                = 0x05
46        PARENTAL_RATING                          = 0x06
47        PROGRAM_INFO_URI                         = 0x07
48        AUDIO_ACTIVE_STATE                       = 0x08
49        BROADCAST_AUDIO_IMMEDIATE_RENDERING_FLAG = 0x09
50        ASSISTED_LISTENING_STREAM                = 0x0A
51        BROADCAST_NAME                           = 0x0B
52        EXTENDED_METADATA                        = 0xFE
53        VENDOR_SPECIFIC                          = 0xFF
54
55    @dataclasses.dataclass
56    class Entry:
57        tag: Metadata.Tag
58        data: bytes
59
60        @classmethod
61        def from_bytes(cls: Type[Self], data: bytes) -> Self:
62            return cls(tag=Metadata.Tag(data[0]), data=data[1:])
63
64        def __bytes__(self) -> bytes:
65            return bytes([len(self.data) + 1, self.tag]) + self.data
66
67    entries: List[Entry] = dataclasses.field(default_factory=list)
68
69    @classmethod
70    def from_bytes(cls: Type[Self], data: bytes) -> Self:
71        entries = []
72        offset = 0
73        length = len(data)
74        while offset < length:
75            entry_length = data[offset]
76            offset += 1
77            entries.append(cls.Entry.from_bytes(data[offset : offset + entry_length]))
78            offset += entry_length
79
80        return cls(entries)
81
82    def __bytes__(self) -> bytes:
83        return b''.join([bytes(entry) for entry in self.entries])
84