blob: c33dcbc7f4f806034142bf6312cbced23c5900b8 [file]
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import re
import sys
import typing
from abc import ABCMeta
from collections.abc import Sequence
from dataclasses import dataclass
from enum import IntEnum
from typing import Optional
import fuchsia_controller_py as fc
# These can be updated to use TypeAlias when python is updated to 3.10+
TXID_Type = int
Ordinal = int
FidlMessage = tuple[
bytes, Sequence[fc.BaseHandle] | Sequence[tuple[int, int, int, int, int]]
]
# The number of bytes in a FIDL header.
FIDL_HEADER_SIZE = 8
# The number of bytes in a FIDL ordinal.
FIDL_ORDINAL_SIZE = 8
FIDL_EPITAPH_ORDINAL = 0xFFFFFFFFFFFFFFFF
class FidlMeta(ABCMeta):
def __new__(
meta_cls,
name: str,
bases: tuple[type, ...],
classdict: dict[str, typing.Any],
required_class_variables: list[tuple[str, type]] | None = None,
) -> "FidlMeta":
if required_class_variables is None:
required_class_variables = []
def _check_required_class_variables(cls: type["FidlMeta"]) -> None:
# These class variables are always required.
if required_class_variables is not None:
required_class_variables.extend([("__fidl_kind__", str)])
missing_class_variables = []
for var, ty in required_class_variables:
if not hasattr(cls, var) or not isinstance(
getattr(cls, var), ty
):
missing_class_variables.append(
(var, type(getattr(cls, var, None)))
)
if len(missing_class_variables) > 0:
raise NotImplementedError(
f"Class {cls} missing (or wrong type) required\nclass variables:\n {missing_class_variables}"
)
# If it exists, run the class-defined __init_subclass__ after the injected one.
if "__init_subclass__" in classdict:
class_defined_init_subclass = classdict["__init_subclass__"]
def wrapper(cls: type["FidlMeta"]) -> None:
_check_required_class_variables(cls)
class_defined_init_subclass(cls)
classdict["__init_subclass__"] = wrapper
else:
classdict["__init_subclass__"] = _check_required_class_variables
return super().__new__(meta_cls, name, bases, classdict)
class FidlProtocolMarker(
str,
metaclass=FidlMeta,
):
...
@dataclass
class MethodInfo:
name: str
request_ident: str
requires_response: bool
empty_response: bool
has_result: bool
response_identifier: str | None
@dataclass
class DomainError:
"""A class used to wrap returning an error from a two-way method."""
error: typing.Any
class StopEventHandler(Exception):
"""An exception used to stop an event handler from continuing."""
class FrameworkError(IntEnum):
UNKNOWN_METHOD = -2
def internal_kind_to_type(internal_kind: str) -> type[FrameworkError]:
# TODO(https://fxbug.dev/42061151): Remove "transport_error".
if internal_kind == "framework_error" or internal_kind == "transport_error":
return FrameworkError
raise RuntimeError(f"Unrecognized internal type: {internal_kind}")
class EpitaphError(fc.ZxStatus):
"""An exception received when an epitaph has been sent on the channel."""
@dataclass
class GenericResult:
fidl_type: str
response: Optional[object] = None
err: Optional[object] = None
framework_err: FrameworkError | None = None
@property
def __fidl_type__(self) -> str:
return self.fidl_type
@property
def __fidl_raw_type__(self) -> str:
return self.fidl_type
def parse_txid(msg: FidlMessage) -> int:
(b, _) = msg
return int.from_bytes(b[0:4], sys.byteorder)
def parse_ordinal(msg: FidlMessage) -> int:
(b, _) = msg
start = FIDL_HEADER_SIZE
end = FIDL_HEADER_SIZE + FIDL_ORDINAL_SIZE
return int.from_bytes(b[start:end], sys.byteorder)
def parse_epitaph_value(msg: FidlMessage) -> int:
(b, _) = msg
start = FIDL_HEADER_SIZE + FIDL_ORDINAL_SIZE
end = start + 4
res = int.from_bytes(b[start:end], sys.byteorder)
# Do two's complement here to get the true value if it's negative.
if res & (1 << 31) != 0:
return res - (1 << 32)
return res
def camel_case_to_snake_case(s: str) -> str:
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
def normalize_identifier(identifier: str) -> str:
"""Takes an identifier and attempts to normalize it.
For the average identifier this shouldn't do anything. This only applies to result types
that have underscores in their names.
Returns: The normalized identifier string (sans-underscores).
"""
if identifier.endswith("_Result") or identifier.endswith("_Response"):
return identifier.replace("_", "")
return identifier