diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 63a07aa..7c9c36a 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2fc5042..b434e59 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - name: "Setup Python" uses: "actions/setup-python@v3" with: - python-version: "3.9" + python-version: "3.11" - name: "Install build dependencies" run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index adbb94b..7952357 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,8 +13,6 @@ jobs: fail-fast: false matrix: include: - - name: py38 - python-version: 3.8 - name: py39 python-version: 3.9 - name: py310 @@ -22,6 +20,8 @@ jobs: - name: py311 python-version: "3.11" coverage: true + - name: py312 + python-version: "3.12" steps: - uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 023ce43..012a01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Labone Python API Changelog +## Version 2.0.0 + +* Switch to the custom capnp backend `zhinst.comms`. This fixes the stability issues. + ## Version 1.1.0 * Introduce `labone.server` which allows spawning capnp servers based on the diff --git a/README.md b/README.md index e4cccb4..4ad2d65 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ The `labone` package provides a plain asynchronous Python API for [LabOne](https > Since `labone` is not intended for direct usage, we do not offer any support > or external documentation. Please contact [Zurich Instruments](mailto:info@zhinst.com) if you have any questions. +## Internal Documentation + +The internal documentation can be found [here](http://qt-developer-manual-docs-ed588a9f2798190163c2ddd236b7c23ed753a0.pages.zhinst.com/labone_python/index.html). +Due to the early stage there is not public documentation. + ## Contributing See [Contributing](CONTRIBUTING.md) diff --git a/pyproject.toml b/pyproject.toml index 76af405..c8dbb72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] description = "Python API for Zurich Instruments LabOne software." readme = "README.md" license = { text = "Apache 2.0" } -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [ { name = "Zurich Instruments Development Team", email = "info@zhinst.com" }, ] @@ -19,7 +19,6 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -29,9 +28,7 @@ classifiers = [ dependencies = [ "typing_extensions>=4.8.0", "numpy>=1.20", - "pycapnp~=2.0.0b2", - "asyncio-atexit==1.0.1", - "deprecation>=2.1.0", + "zhinst-comms~=0.0.1", ] [project.urls] @@ -53,10 +50,16 @@ exclude = ["/.github", "/docs"] packages = ["src/labone"] [[tool.hatch.envs.test.matrix]] -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12"] [tool.hatch.envs.test] -dependencies = ["coverage[toml]>=6.5", "pytest", "hypothesis", "pytest-asyncio"] +dependencies = [ + "coverage[toml]>=6.5", + "pytest", + "hypothesis", + "pytest-asyncio", + "munch==4.0.0", +] [tool.pytest.ini_options] markers = [ @@ -77,7 +80,7 @@ cov-report = [ cov = ["test-cov", "cov-report"] [[tool.hatch.envs.lint.matrix]] -python = ["3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.9", "3.10", "3.11", "3.12"] [tool.hatch.envs.lint] dependencies = [ @@ -85,13 +88,13 @@ dependencies = [ "mypy>=1.0.0", "ruff>=0.4.0", "numpy>=1.20", - "asyncio-atexit==1.0.1", + "zhinst-comms~=0.0.1", ] [tool.hatch.envs.lint.scripts] typing = "mypy --install-types --non-interactive {args:src/labone}" -style = ["ruff {args:.}", "black --check --diff {args:.}"] -fmt = ["black {args:.}", "ruff --fix {args:.}", "style"] +style = ["ruff check {args:.}", "black --check --diff {args:.}"] +fmt = ["black {args:.}", "ruff check --fix {args:.}", "style"] all = ["style", "typing"] [tool.black] @@ -132,9 +135,6 @@ convention = "google" # Flag errors (`C901`) whenever the complexity level exceeds 15. max-complexity = 15 -[tool.ruff.lint.isort] -known-third-party = ["capnp"] - [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" @@ -169,11 +169,11 @@ exclude_lines = [ "\\.\\.\\.", ] -[[tool.mypy.overrides]] -module = "capnp.*" -ignore_missing_imports = true -follow_imports = "skip" +# [[tool.mypy.overrides]] +# module = "zhinst.*" +# ignore_missing_imports = true +# follow_imports = "skip" -[[tool.mypy.overrides]] -module = "deprecation" -ignore_missing_imports = true +# [[tool.mypy.overrides]] +# module = "deprecation" +# ignore_missing_imports = true diff --git a/src/labone/__init__.py b/src/labone/__init__.py index 090d222..00f3bce 100644 --- a/src/labone/__init__.py +++ b/src/labone/__init__.py @@ -4,12 +4,10 @@ from labone.core import ListNodesFlags from labone.dataserver import DataServer from labone.instrument import Instrument -from labone.server.session import SessionFunctionality __all__ = [ "__version__", "Instrument", "DataServer", "ListNodesFlags", - "SessionFunctionality", ] diff --git a/src/labone/core/__init__.py b/src/labone/core/__init__.py index 889e7bc..fb1464a 100644 --- a/src/labone/core/__init__.py +++ b/src/labone/core/__init__.py @@ -1,13 +1,21 @@ """Subpackage for the core functionality of the LabOne API. -This subpackage manages the communication with the LabOne data server through -capnp. It encapsulates the low level logic of the capnp protocol and provides +This subpackage manages the communication with the LabOne data server. +It encapsulates the low level logic of the underlying protocol and provides a python only interface to the rest of the API. """ -from labone.core.connection_layer import DeviceKernelInfo, ServerInfo, ZIKernelInfo -from labone.core.kernel_session import KernelSession -from labone.core.session import ListNodesFlags, ListNodesInfoFlags, Session +from labone.core.helper import ZIContext +from labone.core.kernel_session import ( + KernelInfo, + KernelSession, + ServerInfo, +) +from labone.core.session import ( + ListNodesFlags, + ListNodesInfoFlags, + Session, +) from labone.core.subscription import ( CircularDataQueue, DataQueue, @@ -17,14 +25,14 @@ __all__ = [ "AnnotatedValue", + "ListNodesFlags", + "ListNodesInfoFlags", "DataQueue", "CircularDataQueue", "DistinctConsecutiveDataQueue", "Session", "KernelSession", - "ListNodesFlags", - "ListNodesInfoFlags", - "ZIKernelInfo", - "DeviceKernelInfo", "ServerInfo", + "KernelInfo", + "ZIContext", ] diff --git a/src/labone/core/connection_layer.py b/src/labone/core/connection_layer.py deleted file mode 100644 index d53e550..0000000 --- a/src/labone/core/connection_layer.py +++ /dev/null @@ -1,567 +0,0 @@ -"""Module for the low level communication (pre capnp) with a LabOne data server. - -This module is able tro create a connection to a LabOne data server and perform -the necessary steps (e.g. handshake) to establish a connection. -""" - -from __future__ import annotations - -import contextlib -import typing as t - -__all__ = [ - "KernelInfo", - "DeviceKernelInfo", - "ZIKernelInfo", - "ServerInfo", - "create_session_client_stream", -] - -import json -import socket -from abc import ABC, abstractmethod -from dataclasses import dataclass, replace -from http import HTTPStatus -from http.client import HTTPConnection - -import capnp -from packaging import version - -from labone.core import errors - -HelloMsgJson = t.Dict[str, str] - - -class KernelInfo(ABC): - """Information about a LabOne kernel. - - Prototype class that defines the interface of a kernel info object. - Needs to be derived from to be used. - """ - - @abstractmethod - def with_capability_version( - self, - capability_version: version.Version | None, - ) -> KernelInfo: - """Create a new kernel info with a specific capability version. - - Args: - capability_version: Capability version that should be used. - - Returns: - New kernel info with the specified capability version. - """ - - @property - @abstractmethod - def name(self) -> str: - """Name of the kernel.""" - - @property - @abstractmethod - def capability_version(self) -> version.Version | None: - """Name of the kernel.""" - - @property - @abstractmethod - def identifier(self) -> str: - """Identifier of the kernel in the upgrade request.""" - - @property - @abstractmethod - def query(self) -> dict[str, str]: - """Additional query that should be used in the upgrade request.""" - - -class DeviceKernelInfo(KernelInfo): - """Kernel info for a specific device kernel. - - Args: - device_id: Identifier of the device. (e.g. dev1234) - interface: Interface of the device. If empty the Data Server will - automatically choose the right interface based on the available - interfaces and a priority list. (default = "") - """ - - def __init__( - self, - device_id: str, - interface: str = "", - capability_version: version.Version | None = None, - ) -> None: - self._device_id = device_id - self._interface = interface - self._capability_version = capability_version - - def with_capability_version( - self, - capability_version: version.Version | None, - ) -> KernelInfo: - """Create a new kernel info with a specific capability version. - - Args: - capability_version: Capability version that should be used. - - Returns: - New kernel info with the specified capability version. - """ - return DeviceKernelInfo(self._device_id, self._interface, capability_version) - - @property - def name(self) -> str: - """Name of the kernel.""" - return self._device_id - - @property - def capability_version(self) -> version.Version | None: - """Name of the kernel.""" - return self._capability_version - - @property - def identifier(self) -> str: - """Identifier of the kernel in the upgrade request.""" - return f"devid/{self._device_id}" - - @property - def query(self) -> dict[str, str]: - """Additional query that should be used in the upgrade request.""" - return {"interface": str(self._interface)} - - @property - def device_id(self) -> str: - """Identifier of the device.""" - return self._device_id - - @property - def interface(self) -> str: - """Interface of the device.""" - return self._interface - - -class ZIKernelInfo(KernelInfo): - """Kernel info for the own kernel of the data server. - - This kernel serves the /zi nodes and lives in the data server itself. - """ - - def __init__(self, capability_version: version.Version | None = None) -> None: - self._capability_version = capability_version - - def with_capability_version( - self, - capability_version: version.Version | None, - ) -> KernelInfo: - """Create a new kernel info with a specific capability version. - - Args: - capability_version: Capability version that should be used. - - Returns: - New kernel info with the specified capability version. - """ - return ZIKernelInfo(capability_version) - - @property - def name(self) -> str: - """Name of the kernel.""" - return "zi" - - @property - def capability_version(self) -> version.Version | None: - """Name of the kernel.""" - return self._capability_version - - @property - def identifier(self) -> str: - """Identifier of the kernel in the upgrade request.""" - return "zi" - - @property - def query(self) -> dict[str, str]: - """Additional query that should be used in the upgrade request.""" - return {} - - -@dataclass(frozen=True) -class ServerInfo: - """Information about a server.""" - - host: str - port: int - hello_msg: HelloMsgJson | None = None - - -# The API level is used by LabOne to provide backwards compatibility. -# It is more or less deprecated in favour of the capability version. -# The HPK only supports the highest API level (6) and it will probably not be -# updated in the future. -API_LEVEL = 6 - -# The minimum capability version that is required by the labone api. -# 1.4.0 is the first version that supports the html interface. -MIN_ORCHESTRATOR_CAPABILITY_VERSION = version.Version("1.4.0") -# The latest known version of the orchestrator capability version. -TESTED_ORCHESTRATOR_CAPABILITY_VERSION = version.Version("1.8.0") -HELLO_MSG_FIXED_LENGTH = 256 - - -def _open_socket(server_info: ServerInfo) -> socket.socket: - """Open a plain socket to a server. - - Args: - server_info: Information about the server to connect. - - Returns: - Socket to the server. - - Raises: - UnavailableError: If the connection to the server could not be established. - """ - try: - sock = socket.create_connection((server_info.host, server_info.port)) - except (ConnectionRefusedError, TimeoutError, socket.timeout, OSError) as e: - msg = ( - "Unable to open connection to the data server at " - f"{server_info.host}:{server_info.port}. " - "Make sure that the server is running, host / port names are correct. " - f"(Reason: {e})" - ) - raise errors.UnavailableError(msg) from e - return sock - - -def _construct_handshake_error_msg(host: str, port: int, info: str) -> str: - """Construct a handshake error message. - - Since a lot of the message is shared between the different handshake errors, - this function is used to construct the error message. - - Args: - host: Hostname of the server. - port: Port of the server. - info: Additional information about the error but at the end of the - error message. - """ - return ( - f"Unable to open connection to the data server at {host}:{port}. " - "This usually indicates a outdated LabOne version. " - "Please update LabOne to the latest version. " - f"Reason: {info}" - ) - - -def _client_handshake( - sock: socket.socket, - *, - check: bool = True, -) -> HelloMsgJson: - """Perform the zi client handshake with the server. - - The handshake is mandatory and is a fixed length json encoded string. - - The structure of the hello message is defined in the hello_msg.capnp - schema. - - If the check flag is set to true, the hello message is checked for - compatibility with the labone api. This ensures that the data server is - compatible with the current version of the labone api. - - Args: - sock: Socket to the server. - check: If true, the hello message is checked for compatibility with the - labone api. If false, the hello message is returned without any - checks. - - Returns: - Received hello message. - - Raises: - UnavailableError: If the server is not compatible with the labone api. - (Only if `check` == True) - """ - raw_hello_msg = sock.recv(HELLO_MSG_FIXED_LENGTH).rstrip(b"\x00") - try: - # The hello message is a json string, so we need to parse it with json - # first and then convert it to a capnp message. This is due to the fact - # that we want to keep the hello message as generic as possible. - hello_msg: HelloMsgJson = json.loads(raw_hello_msg) - except (json.JSONDecodeError, capnp.lib.capnp.KjException) as err: - msg = _construct_handshake_error_msg( # type: ignore [call-arg] - *sock.getpeername(), - f"Invalid JSON during Handshake: {raw_hello_msg.decode()}", - ) - raise errors.UnavailableError(msg) from err - if not check: - return hello_msg - if hello_msg.get("kind") != "orchestrator": # type: ignore [attr-defined] - msg = _construct_handshake_error_msg( # type: ignore [call-arg] - *sock.getpeername(), - f"Invalid server kind: {hello_msg.get('kind')}", - ) - raise errors.UnavailableError(msg) - if hello_msg.get("protocol") != "http": - msg = _construct_handshake_error_msg( # type: ignore [call-arg] - *sock.getpeername(), - f" Invalid protocol: {hello_msg.get('protocol')}", - ) - raise errors.UnavailableError(msg) - - try: - capability_version = version.Version(hello_msg.get("schema", "0.0.0")) - except version.InvalidVersion as err: - msg = _construct_handshake_error_msg( # type: ignore [call-arg] - *sock.getpeername(), - f"Unsupported LabOne Version: {hello_msg.get('l1Ver')}", - ) - raise errors.UnavailableError(msg) from err - if capability_version < MIN_ORCHESTRATOR_CAPABILITY_VERSION: - msg = _construct_handshake_error_msg( # type: ignore [call-arg] - *sock.getpeername(), - f"Unsupported LabOne Version: {hello_msg.get('l1Ver')}", - ) - raise errors.UnavailableError(msg) - if capability_version.major > TESTED_ORCHESTRATOR_CAPABILITY_VERSION.major: - msg = str( - "Unable to open connection to the data server at {host}:{port}. " - "The server is using a newer LabOne version that is incompatible " - "with the version of this api. Please update the latest version " - "of the python package.", - ) - raise errors.UnavailableError(msg) - return hello_msg - - -def _raise_orchestrator_error(code: str, message: str) -> None: - """Raise a labone orchestrator error based on the error message. - - Args: - code: Error code of the error. - message: Error message of the error. - - Raises: - ValueError: If the error code is ok. - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If the error can not be mapped to a known error. - """ - if code == "kernelNotFound": - raise errors.UnavailableError(message) - if code == "illegalDeviceIdentifier": - raise errors.BadRequestError(message) - if code in ["deviceNotFound", "deviceNotVisible"]: - raise errors.UnavailableError(message) - if code == "kernelLaunchFailure": - raise errors.InternalError(message) - if code == "firmwareUpdateRequired": - raise errors.UnavailableError(message) - if code == "interfaceMismatch": - raise errors.UnavailableError(message) - if code == "differentInterfaceInUse": - raise errors.UnavailableError(message) - if code == "deviceInUse": - raise errors.UnavailableError(message) - if code == "unsupportedApiLevel": - raise errors.UnavailableError(message) - if code == "badRequest": - raise errors.BadRequestError(message) - if code == "ok": - msg = "Error expected but status code is ok" - raise ValueError(msg) - raise errors.LabOneCoreError(message) - - -def _raise_connection_error( - response_status: int, - response_info: HelloMsgJson | None, -) -> None: - """Raises a connection error based on the http response. - - Args: - response_status: Status code of the response. - response_info: Result message from the server. - - Raises: - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If the error can not be mapped to a known error. - """ - with contextlib.suppress( - capnp.KjException, - AttributeError, - KeyError, - TypeError, - ): - _raise_orchestrator_error( - response_info["err"].get("code", "unknown"), # type: ignore [union-attr, index] - response_info["err"].get("message", ""), # type: ignore [union-attr, index] - ) - # None existing or malformed result message raise generic error - msg = ( - f"Unexpected HTTP error {HTTPStatus(response_status).name} ({response_status})" - ) - raise errors.LabOneCoreError(msg) - - -def _http_get_info_request( - sock: socket.socket, - kernel_info: KernelInfo, - extra_headers: dict[str, str] | None = None, -) -> tuple[int, KernelInfo, HelloMsgJson | None]: - """Issue a HTTP get kernel info request to the server. - - This data server is expected to respond with a - Result(KernelDescriptor, Error) message from the orchetstrator.capnp. - - Args: - sock: Socket to the server. - kernel_info: Information about the kernel to connect to. - extra_headers: Additional headers that should be used in the request. - - Returns: - Tuple of the status code, the updated KernelInfo and the KernelDescriptor. - - Raises: - UnavailableError: If the kernel was not found or unable to connect. - """ - host, port = sock.getpeername() - connection = HTTPConnection(host, port) - # Set the sock manually to prevent creating a new one - connection.sock = sock - headers = extra_headers if extra_headers else {} - headers["Host"] = f"{host}:{port}" - headers["Accept"] = "application/json" - query = kernel_info.query - query["apiLevel"] = str(API_LEVEL) - query_str = "&".join([f"{k}={v}" for k, v in query.items()]) - url = f"/api/v1/kernel/{kernel_info.identifier}?{query_str}" - - connection.request(method="GET", url=url, headers=headers) - response = connection.getresponse() - # The response from the server is excepted to encoded with json - # At least that what we specified with the `Accept` header. - response_info = ( - json.loads(response.read().decode()) - if response.length and response.length > 0 - else None - ) - if response.status >= HTTPStatus.MULTIPLE_CHOICES: - _raise_connection_error(response.status, response_info) - # Update the capability version of the kernel info - capability_version_raw = response.headers.get("Zhinst-Kernel-Version", None) - kernel_info_extended = kernel_info.with_capability_version( - capability_version=( - version.Version(capability_version_raw) - if capability_version_raw is not None - else None - ), - ) - return ( - response.status, - kernel_info_extended, - response_info, - ) - - -def _protocol_upgrade( - sock: socket.socket, - *, - kernel_info: KernelInfo, -) -> KernelInfo: - """Perform the protocol upgrade to the capnp protocol. - - Send a HTTP get request to the server to upgrade the protocol to capnp. - - Args: - sock: Socket to the server. - kernel_info: Information about the kernel to connect to. - - Returns: - HTTP response from the server. - - Raises: - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If the error can not be mapped to a known error. - """ - response_status, kernel_info_extended, _ = _http_get_info_request( - sock, - kernel_info, - {"Connection": "Upgrade", "Upgrade": "capnp"}, - ) - if response_status != HTTPStatus.SWITCHING_PROTOCOLS: - # The upgrade was not performed - msg = ( - f"Unable to connect to kernel {kernel_info.name}. " - "The kernel is not not compatible with the LabOne API. " - "Please update LabOne to the latest version." - ) - raise errors.UnavailableError( - msg, - ) - return kernel_info_extended - - -def create_session_client_stream( - *, - kernel_info: KernelInfo, - server_info: ServerInfo | None = None, - sock: socket.socket | None = None, - handshake: bool = True, -) -> tuple[socket.socket, KernelInfo, ServerInfo]: - """Create a session client stream to a kernel. - - The stream is ready to use with capnp and the session protocol. - - Performs the following steps: - 1. Open a socket to the server (if `sock` == None). - 2. Perform the zi client handshake. - 3. Perform the protocol upgrade to capnp. - - Args: - kernel_info: Required information about the kernel to connect to. - server_info: Server info (default = None). - sock: Existing socket to the server (default = None). - If specified server_info will be ignored. - handshake: If true, the zi handshake is performed (default = True). - - Returns: - Tuple of the socket, the kernel info and the server info. - The kernel info is updated with the capability version. - The server info is updated with the hello message. - - Raises: - ValueError: If both `sock` and `host` are specified. - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If the error can not be mapped to a known error. - """ - # The initialization of the connection is synchronous ... - # This is due to the fact that capnp library does only support creating a connection - # from a socket and not from a stream. - if sock is None and server_info is not None: - sock = _open_socket(server_info) - elif server_info is not None: - msg = "Either sock or server_info can be specified, not both." - raise ValueError(msg) - elif sock is None and server_info is None: - msg = "Either sock or server_info must be specified." - raise ValueError(msg) - else: - host, port = sock.getpeername() - server_info = ServerInfo(host=host, port=port) - if handshake: - hello_msg = _client_handshake(sock) - server_info_extended = replace(server_info, hello_msg=hello_msg) - else: - server_info_extended = server_info - kernel_info_extended = _protocol_upgrade(sock, kernel_info=kernel_info) - return sock, kernel_info_extended, server_info_extended diff --git a/src/labone/core/errors.py b/src/labone/core/errors.py index e16239d..0782777 100644 --- a/src/labone/core/errors.py +++ b/src/labone/core/errors.py @@ -5,10 +5,9 @@ import typing as t from asyncio import QueueEmpty -from labone.errors import LabOneError +import zhinst.comms -if t.TYPE_CHECKING: - import capnp +from labone.errors import LabOneError class LabOneCoreError(LabOneError): @@ -63,6 +62,73 @@ def __init__(self, version: tuple[int, int]): super().__init__(msg, code=0, category="SHFHeaderVersionNotSupported") +T = t.TypeVar("T") + + +def translate_comms_error( + func: t.Callable[..., t.Awaitable[T]], +) -> t.Callable[..., t.Awaitable[T]]: + """Translate zhinst.comms exceptions to labone exceptions. + + A decorator to catch all exceptions from zhinst.comms and re-raise + them as LabOneCoreError exceptions. + """ + + def wrapper(*args, **kwargs) -> t.Awaitable[T]: + try: + return func(*args, **kwargs) + except zhinst.comms.errors.CancelledError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.NotFoundError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.OverwhelmedError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.BadRequestError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.UnimplementedError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.UnavailableError as e: + raise UnavailableError(str(e)) from e + except zhinst.comms.errors.TimeoutError as e: + raise LabOneTimeoutError(str(e)) from e + except zhinst.comms.errors.BaseError as e: + raise LabOneCoreError(str(e)) from e + + return wrapper + + +def async_translate_comms_error( + func: t.Callable[..., t.Awaitable[T]], +) -> t.Callable[..., t.Awaitable[T]]: + """Translate zhinst.comms exceptions to labone exceptions. + + A decorator to catch all exceptions from zhinst.comms and re-raise + them as LabOneCoreError exceptions. + """ + + async def wrapper(*args, **kwargs) -> T: + try: + return await func(*args, **kwargs) + except zhinst.comms.errors.CancelledError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.NotFoundError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.OverwhelmedError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.BadRequestError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.UnimplementedError as e: + raise LabOneCoreError(str(e)) from e + except zhinst.comms.errors.UnavailableError as e: + raise UnavailableError(str(e)) from e + except zhinst.comms.errors.TimeoutError as e: + raise LabOneTimeoutError(str(e)) from e + except zhinst.comms.errors.BaseError as e: + raise LabOneCoreError(str(e)) from e + + return wrapper + + ################################################################## ## Streaming Errors ## ################################################################## @@ -88,11 +154,11 @@ class EmptyDisconnectedDataQueueError(StreamingError, QueueEmpty): } -def error_from_capnp(err: capnp.lib.capnp._DynamicStructReader) -> LabOneCoreError: - """Create labone error from a error.capnp::Error struct. +def get_streaming_error(err: zhinst.comms.DynamicStructBase) -> LabOneCoreError: + """Create labone error from a labone error struct. Args: - err: The capnp error to be converted. + err: The streaming error to be converted. Returns: The corresponding error. diff --git a/src/labone/core/helper.py b/src/labone/core/helper.py index 162a58e..468c5a1 100644 --- a/src/labone/core/helper.py +++ b/src/labone/core/helper.py @@ -4,58 +4,38 @@ within the core. """ -import asyncio +from __future__ import annotations + import logging from enum import IntEnum -from functools import partial -import asyncio_atexit # type: ignore [import] -import capnp import numpy as np +import zhinst.comms from typing_extensions import TypeAlias logger = logging.getLogger(__name__) LabOneNodePath: TypeAlias = str -CapnpCapability: TypeAlias = capnp.lib.capnp._DynamicCapabilityClient # noqa: SLF001 -CapnpStructReader: TypeAlias = capnp.lib.capnp._DynamicStructReader # noqa: SLF001 -CapnpStructBuilder: TypeAlias = capnp.lib.capnp._DynamicStructBuilder # noqa: SLF001 -async def ensure_capnp_event_loop() -> None: - """Ensure that the capnp event loop is running. +ZIContext: TypeAlias = zhinst.comms.CapnpContext +_DEFAULT_CONTEXT: ZIContext | None = None - Pycapnp requires the capnp event loop to be running for every async - function call to the capnp library. This function ensures that the capnp - event loop is running. The event loop is intended to be managed through a - context manager. This function fakes the context by using asyncio_atexit - to close the context when the asyncio event loop is closed. This ensures - that the capnp event loop will be closed before the asyncio event loop. - """ - # The kj event loop is attached to the current asyncio event loop. - # Pycapnp does this by adding an attribute _kj_loop to the asyncio - # event loop. This is done in the capnp.kj_loop() context manager. - # The context manager should only be entered once. To avoid entering - # the context manager multiple times we check if the attribute is - # already set. - if not hasattr(asyncio.get_running_loop(), "_kj_loop"): - loop = capnp.kj_loop() - logger.debug("kj event loop attached to asyncio event loop %s", id(loop)) - await loop.__aenter__() - asyncio_atexit.register(partial(loop.__aexit__, None, None, None)) - - -def request_field_type_description( - request: capnp.lib.capnp._Request, - field: str, -) -> str: - """Get given `capnp` request field type description. - - Args: - request: Capnp request. - field: Field name of the request. + +def get_default_context() -> ZIContext: + """Get the default context. + + The default context is a global context that is used by default if no + context is provided. It is typically the desired behavior to have a single + context and there are only rare cases where multiple contexts are needed. + + Returns: + The default context. """ - return request.schema.fields[field].proto.slot.type.which() + global _DEFAULT_CONTEXT # noqa: PLW0603 + if _DEFAULT_CONTEXT is None: + _DEFAULT_CONTEXT = zhinst.comms.CapnpContext() + return _DEFAULT_CONTEXT class VectorValueType(IntEnum): @@ -76,11 +56,11 @@ class VectorValueType(IntEnum): class VectorElementType(IntEnum): - """Type of the elements in a vector supported by the capnp interface. + """Type of the elements in a vector supported by the labone interface. Since the vector data is transmitted as a byte array the type of the elements in the vector must be specified. This enum contains all supported - types by the capnp interface. + types by the labone interface. """ UINT8 = 0 @@ -97,7 +77,7 @@ class VectorElementType(IntEnum): def from_numpy_type( cls, numpy_type: np.dtype, - ) -> "VectorElementType": + ) -> VectorElementType: """Construct a VectorElementType from a numpy type. Args: diff --git a/src/labone/core/hpk_schema.py b/src/labone/core/hpk_schema.py new file mode 100644 index 0000000..a0bfa3a --- /dev/null +++ b/src/labone/core/hpk_schema.py @@ -0,0 +1,1008 @@ +# ruff: noqa: E501 Line too long +"""Python schema for zhinst/io/protocol/capnp/hpk/hpk.capnp. + +This file is generated and contains a capnproto schema in packed binary format; +specifically a serialized reflection_capnp.CapSchema message with the typeId field +not set. + +The schema has been compiled from the file zhinst/io/protocol/capnp/hpk/hpk.capnp by the capnp-python-schema-generator +capnpc plugin. + +This file contains the following schema nodes: +# - capnp/c++.capnp:allowCancellation @0xac7096ff8cfc9dce +# - capnp/c++.capnp:namespace @0xb9c6f99ebf805f2c +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection @0xf9a52e68104bc776 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session @0xb9d445582da4a55c +# - zhinst/io/protocol/capnp/hpk/hpk.capnp:Hpk @0xa621130a90860008 +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Params @0x86a77be57bfa08f7 +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:CapSchema @0xcb31ef7a76eb85cf +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Results @0xc9db2193e1883dd1 +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Params @0xe3e32fd4d93c1199 +# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Results @0xfb31a67fd905411a +# - capnp/schema.capnp:Node @0xe682ab4cf923a417 +# - capnp/schema.capnp:ElementSize @0xd1958f7dba521926 +# - capnp/schema.capnp:Field @0x9aad50a41f4af45f +# - capnp/schema.capnp:Node.struct @0x9ea0b19b37fb4435 +# - capnp/schema.capnp:Enumerant @0x978a7cebdc549a4d +# - capnp/schema.capnp:Node.enum @0xb54ab3364333f598 +# - capnp/schema.capnp:Method @0x9500cce23b334d80 +# - capnp/schema.capnp:Superclass @0xa9962a9ed0a4d7f8 +# - capnp/schema.capnp:Node.interface @0xe82753cff0c2218f +# - capnp/schema.capnp:Type @0xd07378ede1f9cc60 +# - capnp/schema.capnp:Value @0xce23dcd2d7b00c9b +# - capnp/schema.capnp:Node.const @0xb18aa5ac7a0d9420 +# - capnp/schema.capnp:Node.annotation @0xec1619d4400a0290 +# - capnp/schema.capnp:Node.NestedNode @0xdebf55bbfa0fc242 +# - capnp/schema.capnp:Annotation @0xf1c8950dab257542 +# - capnp/schema.capnp:Node.Parameter @0xb9521bccf10fa3b1 +# - capnp/schema.capnp:Brand @0x903455f06065422b +# - capnp/schema.capnp:Brand.Scope @0xabd73485a9636bc9 +# - capnp/schema.capnp:Brand.Binding @0xc863cd16969ee7fc +# - capnp/schema.capnp:Type.list @0x87e739250a60ea97 +# - capnp/schema.capnp:Type.enum @0x9e0e78711a7f87a9 +# - capnp/schema.capnp:Type.struct @0xac3a6f60ef4cc6d3 +# - capnp/schema.capnp:Type.interface @0xed8bca69f7fb0cbf +# - capnp/schema.capnp:Type.anyPointer.unconstrained @0x8e3b5f79fe593656 +# - capnp/schema.capnp:Type.anyPointer.parameter @0x9dd1f724f4614a85 +# - capnp/schema.capnp:Type.anyPointer.implicitMethodParameter @0xbaefc9120c56e274 +# - capnp/schema.capnp:Type.anyPointer @0xc2573fe8a23e49f1 +# - capnp/schema.capnp:Field.slot @0xc42305476bb4746f +# - capnp/schema.capnp:Field.group @0xcafccddb68db1d11 +# - capnp/schema.capnp:Field.ordinal @0xbb90d5c287870be6 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Params @0x92035429255ef5a8 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Results @0xebd34bdb74c961b0 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Params @0xcd242e1bb990dfe7 +# - zhinst/io/protocol/capnp/common/result.capnp:Result @0xbab0f33e1934323d +# - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue @0xf408ee376e837cdc +# - zhinst/io/protocol/capnp/common/error.capnp:Error @0xc4e34e4c517d11d9 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Results @0xe948518c129575bb +# - zhinst/io/protocol/capnp/common/value.capnp:Value @0xb1838b4771be75ac +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:ReturnFromSetWhen @0xdd2da53aac55edf9 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Params @0xa8f6fded40f38065 +# - zhinst/io/protocol/capnp/common/value.capnp:VoidStruct @0xdf7fe8e981437816 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Results @0x8aef0146a9595df9 +# - zhinst/io/protocol/capnp/streaming/streaming.capnp:Subscription @0xedac21a53de1b1d4 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Params @0xb9b18be9afb1b3e4 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Results @0xa0ca6692df48c93f +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.disconnectDevice$Params @0xe682e60b2cd90782 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Params @0xcd837fe86531f6c1 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Results @0xa8151642645bfa32 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.unsubscribe$Params @0xb16a1640db91e61c +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Params @0xd77d14cf14b3405a +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Results @0xcc68434da57cf6b3 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Params @0xdae05cd32680aba1 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Results @0xfd47648e56c0e689 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:LookupMode @0xda5049b5e072f425 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Params @0x9fe29a10493e27c7 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Results @0x9474d85f26f63900 +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Params @0xc5e872236cc4917b +# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Results @0xce98719dbad0a401 +# - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue.Metadata @0xad53d8ca57af3018 +# - zhinst/io/protocol/capnp/common/complex.capnp:Complex @0xaaf1afaf97b4b157 +# - zhinst/io/protocol/capnp/common/value.capnp:VectorData @0x994c65b80df38978 +# - zhinst/io/protocol/capnp/common/value.capnp:CntSample @0xe9370bd8287d6065 +# - zhinst/io/protocol/capnp/common/value.capnp:TriggerSample @0xdeb72097c27d0d95 +# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfDemodulatorVectorData @0x9b03e3e3e6006582 +# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData @0xbba061f579761ddd +# - zhinst/io/protocol/capnp/common/value.capnp:LargeVectorData @0xd948f7b09959b00a +# - zhinst/io/protocol/capnp/common/error.capnp:ErrorKind @0xb7e671e24a9802bd +# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfDemodulatorVectorData.properties @0xd475786ca25c300d +# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData.properties @0xddb7b421ea0eec5e +# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData.vector @0x980b68b5449bdf12 +# - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle @0xf51e28a7f5b41574 +# - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle.sendValues$Params @0xa30bad0f967c47b6 +# - capnp/stream.capnp:StreamResult @0x995f9a3377c0b16e + +""" + +import zhinst.comms + +_CAPNP_BINARY_SCHEMA = b"\x21\ +\x03\x04\x22\x04\x08\x03\x9f\x08\x50\x01\x01\x00\x00\x31\x01\x8f\x1c\x53\x4c\x01\x05\x06\xff\xce\x9d\ +\xfc\x8c\xff\x96\x70\xac\x00\xd1\x10\x05\x01\x03\xff\x81\x4e\x30\xb8\x7b\x7d\xf8\xbd\x00\x00\x01\x33\ +\x2d\x0e\x12\x01\x13\x3d\x0e\x07\x00\x00\x53\x38\x0e\x03\x01\x00\x01\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\ +\xb9\x00\x51\x10\x05\x01\xff\x81\x4e\x30\xb8\x7b\x7d\xf8\xbd\x00\x00\x01\x13\x29\x0e\xd2\x13\x35\x0e\ +\x07\x00\x00\x53\x30\x0e\x03\x01\x00\x01\xff\x76\xc7\x4b\x10\x68\x2e\xa5\xf9\x00\x11\x35\x03\xff\x0e\ +\xad\x39\x6c\x91\xc2\x61\x84\x00\x00\x01\x33\x21\x0e\x02\x02\x13\x3d\x0e\x17\x00\x00\x13\x4d\x0e\x87\ +\x13\xa9\x0e\x07\x00\x00\xff\x5c\xa5\xa4\x2d\x58\x45\xd4\xb9\x00\x11\x38\x03\xff\x47\x73\x43\xd5\x89\ +\x12\xc2\xb6\x00\x00\x01\x33\x91\x0e\x02\x02\x13\xad\x0e\x17\x00\x00\x11\x02\x01\x13\xb9\x0e\x07\x00\ +\x00\xfd\x08\x86\x90\x0a\x13\x21\xa6\x11\x27\x03\xff\x3e\xce\x86\x0b\xb1\x1b\xb4\xe5\x00\x00\x01\x33\ +\xa1\x0e\x5a\x01\x13\xb5\x0e\x07\x00\x00\x13\xb1\x0e\x07\x13\xb1\x0e\x27\x00\x00\xff\xf7\x08\xfa\x7b\ +\xe5\x7b\xa7\x86\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x33\xa9\x0e\xa2\x02\x00\x04\xff\xcf\x85\xeb\ +\x76\x7a\xef\x31\xcb\x00\x51\x35\x01\x01\xff\x0e\xad\x39\x6c\x91\xc2\x61\x84\x00\x05\x01\x07\x00\x00\ +\x33\xa9\x0e\xfa\x01\x13\xc5\x0e\x07\x00\x00\x13\x02\x04\x01\x00\x01\xff\xd1\x3d\x88\xe1\x93\x21\xdb\ +\xc9\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x2a\x05\x01\x00\x01\x13\x8a\x05\x01\x00\x01\xff\ +\x99\x11\x3c\xd9\xd4\x2f\xe3\xe3\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\x1a\x06\x01\x00\x04\xff\ +\x1a\x41\x05\xd9\x7f\xa6\x31\xfb\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x82\x06\x01\x00\x01\ +\x13\xea\x06\x01\x00\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x51\x13\x01\x05\xff\xd9\x72\x4c\x62\ +\x09\xc5\x3f\xa9\x00\x45\x06\x07\x06\x01\x06\x13\x72\x07\x01\x13\x92\x07\x01\x00\x00\x13\x02\x08\x01\ +\x00\x01\xff\x26\x19\x52\xba\x7d\x8f\x95\xd1\x00\x11\x13\x02\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\ +\x00\x01\x13\x32\x0e\x01\x13\xed\x0d\x07\x00\x00\x13\x5a\x0e\x01\x00\x01\xff\x5f\xf4\x4a\x1f\xa4\x50\ +\xad\x9a\x00\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x04\x07\x02\x01\x04\x13\x8a\ +\x0f\x01\x13\xb2\x0f\x01\x00\x00\x13\xe2\x0f\x01\x00\x01\xff\x35\x44\xfb\x37\x9b\xb1\xa0\x9e\x00\x51\ +\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\xd2\x12\x01\x00\x01\ +\x13\xfa\x12\x01\x00\x01\xff\x4d\x9a\x54\xdc\xeb\x7c\x8a\x97\x00\x51\x13\x01\x01\xff\xd9\x72\x4c\x62\ +\x09\xc5\x3f\xa9\x00\x05\x02\x07\x00\x00\x13\xb2\x16\x01\x13\xda\x16\x01\x00\x00\x13\xea\x16\x01\x00\ +\x01\xff\x98\xf5\x33\x43\x36\xb3\x4a\xb5\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\ +\x15\x06\x07\x01\x00\x00\x13\x92\x18\x01\x00\x01\x13\xba\x18\x01\x00\x01\xbf\x80\x4d\x33\x3b\xe2\xcc\ +\x95\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x05\x07\x00\x00\x13\x6a\x19\x01\x13\ +\x92\x19\x01\x00\x00\x13\xa2\x19\x01\x00\x01\xff\xf8\xd7\xa4\xd0\x9e\x2a\x96\xa9\x00\x51\x13\x01\x01\ +\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x01\x07\x00\x00\x13\xfa\x1d\x01\x13\x22\x1e\x01\x00\x00\ +\x13\x32\x1e\x01\x00\x01\xff\x8f\x21\xc2\xf0\xcf\x53\x27\xe8\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\ +\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x32\x1f\x01\x00\x01\x13\x62\x1f\x01\x00\x01\xff\x60\ +\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x01\x07\ +\x13\x00\x00\x11\xca\x02\x11\xea\x02\x00\x00\x11\xfa\x02\x00\x01\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\ +\x00\x51\x13\x01\x02\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x01\x07\x13\x00\x00\x13\xea\x08\x02\ +\x13\x12\x09\x02\x00\x00\x13\x22\x09\x02\x00\x01\xff\x20\x94\x0d\x7a\xac\xa5\x8a\xb1\x00\x51\x18\x01\ +\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x2a\x12\x02\x00\x01\x13\x52\ +\x12\x02\x00\x01\xff\x90\x02\x0a\x40\xd4\x19\x16\xec\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\ +\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x52\x13\x02\x00\x01\x13\x82\x13\x02\x00\x01\xff\x42\xc2\x0f\ +\xfa\xbb\x55\xbf\xde\x00\x51\x18\x01\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x05\x01\x07\x00\x00\ +\x13\x22\x1a\x02\x13\x52\x1a\x02\x00\x00\x13\x62\x1a\x02\x00\x01\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\ +\x00\x51\x13\x01\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x02\x07\x00\x00\x13\x62\x1b\x02\x13\ +\x8a\x1b\x02\x00\x00\x13\x9a\x1b\x02\x00\x01\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x11\x18\x01\xff\ +\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x05\x01\x07\x00\x00\x13\x12\x1d\x02\x13\x42\x1d\x02\x00\x00\x13\ +\x52\x1d\x02\x00\x01\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x11\x13\x01\xff\xd9\x72\x4c\x62\x09\xc5\ +\x3f\xa9\x00\x05\x01\x07\x00\x00\x13\xda\x1d\x02\x13\x02\x1e\x02\x00\x00\x13\x42\x1e\x02\x00\x01\xff\ +\xc9\x6b\x63\xa9\x85\x34\xd7\xab\x00\x51\x19\x01\x02\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x45\x01\ +\x07\x02\x01\x04\x13\xea\x1e\x02\x13\x12\x1f\x02\x00\x00\x13\x22\x1f\x02\x00\x01\xff\xfc\xe7\x9e\x96\ +\x16\xcd\x63\xc8\x00\x51\x19\x01\x01\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x45\x01\x07\x02\x00\x00\ +\x13\xba\x20\x02\x13\xea\x20\x02\x00\x00\x13\xfa\x20\x02\x00\x01\xff\x97\xea\x60\x0a\x25\x39\xe7\x87\ +\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\xfa\x21\x02\ +\x00\x01\x13\x22\x22\x02\x00\x01\xff\xa9\x87\x7f\x1a\x71\x78\x0e\x9e\x00\x51\x18\x01\x03\xff\x60\xcc\ +\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\xb2\x22\x02\x00\x01\x13\xda\x22\x02\x00\x01\ +\xff\xd3\xc6\x4c\xef\x60\x6f\x3a\xac\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\ +\x01\x07\x01\x00\x00\x13\xda\x23\x02\x00\x01\x13\x02\x24\x02\x00\x01\xff\xbf\x0c\xfb\xf7\x69\xca\x8b\ +\xed\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\x02\x25\ +\x02\x00\x01\x13\x32\x25\x02\x00\x01\xff\x56\x36\x59\xfe\x79\x5f\x3b\x8e\x00\x51\x23\x01\x03\xff\xf1\ +\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x55\x01\x07\x01\x04\x01\x05\x13\x32\x26\x02\x00\x01\x13\x72\x26\x02\ +\x00\x01\xff\x85\x4a\x61\xf4\x24\xf7\xd1\x9d\x00\x51\x23\x01\x03\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\ +\x00\x15\x01\x07\x01\x00\x00\x13\x6a\x28\x02\x00\x01\x13\xa2\x28\x02\x00\x01\xff\x74\xe2\x56\x0c\x12\ +\xc9\xef\xba\x00\x51\x23\x01\x03\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x15\x01\x07\x01\x00\x00\x13\ +\xaa\x29\x02\x00\x01\x13\xf2\x29\x02\x00\x01\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x51\x18\x01\x03\ +\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x55\x01\x07\x01\x03\x01\x04\x13\x82\x2a\x02\x00\x01\x13\xb2\ +\x2a\x02\x00\x01\xff\x6f\x74\xb4\x6b\x47\x05\x23\xc4\x00\x51\x19\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\ +\xad\x9a\x00\x15\x04\x07\x01\x00\x00\x13\xa2\x2b\x02\x00\x01\x13\xca\x2b\x02\x00\x01\xff\x11\x1d\xdb\ +\x68\xdb\xcd\xfc\xca\x00\x51\x19\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x15\x04\x07\x01\x00\ +\x00\x13\xd2\x2d\x02\x00\x01\x13\xfa\x2d\x02\x00\x01\xff\xe6\x0b\x87\x87\xc2\xd5\x90\xbb\x00\x51\x19\ +\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x55\x04\x07\x01\x02\x01\x05\x13\x82\x2e\x02\x00\x01\ +\x13\xb2\x2e\x02\x00\x01\xff\xa8\xf5\x5e\x25\x29\x54\x03\x92\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\ +\x00\x00\x13\xc2\x2f\x02\x00\x01\x13\x22\x30\x02\x00\x01\xff\xb0\x61\xc9\x74\xdb\x4b\xd3\xeb\x00\x11\ +\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xa2\x31\x02\x00\x01\x13\x02\x32\x02\x00\x01\xff\xe7\xdf\x90\ +\xb9\x1b\x2e\x24\xcd\x00\x11\x40\x01\x00\x00\x05\x02\x07\x00\x00\x13\xaa\x32\x02\x00\x01\x13\x12\x33\ +\x02\x00\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x51\x2d\x01\x01\xff\xd5\xab\xa4\xdc\x1f\xe3\xe8\ +\xf7\x00\x45\x01\x07\x02\x10\x01\x13\x32\x34\x02\x13\x72\x34\x02\x00\x00\x13\x82\x34\x02\x00\x00\x13\ +\x82\x35\x02\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x11\x2c\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\ +\x00\x05\x02\x07\x00\x00\x13\xb2\x35\x02\x13\xfa\x35\x02\x00\x00\x13\x2a\x36\x02\x00\x01\xff\xd9\x11\ +\x7d\x51\x4c\x4e\xe3\xc4\x00\x51\x2c\x01\x01\xff\xeb\x9b\xb9\x2c\xd6\x62\x70\xb9\x00\x05\x03\x07\x00\ +\x00\x13\x32\x37\x02\x13\x72\x37\x02\x00\x00\x13\x82\x37\x02\x00\x01\xff\xbb\x75\x95\x12\x8c\x51\x48\ +\xe9\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xf2\x39\x02\x00\x01\x13\x5a\x3a\x02\x00\x01\xff\ +\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x51\x2c\x01\x02\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x45\x01\ +\x07\x0e\x01\x04\x13\x92\x3b\x02\x13\xd2\x3b\x02\x00\x00\x13\xe2\x3b\x02\x00\x01\xff\xf9\xed\x55\xac\ +\x3a\xa5\x2d\xdd\x00\x11\x38\x02\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x00\x01\x13\x12\x04\x03\x13\ +\x6a\x04\x03\x00\x00\x13\x7a\x04\x03\x00\x01\xff\x65\x80\xf3\x40\xed\xfd\xf6\xa8\x00\x51\x40\x01\x01\ +\x00\x00\x05\x02\x07\x00\x00\x13\x2a\x05\x03\x00\x01\x13\x92\x05\x03\x00\x01\xff\x16\x78\x43\x81\xe9\ +\xe8\x7f\xdf\x00\x11\x2c\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\x12\x07\x03\ +\x13\x52\x07\x03\x00\x03\xff\xf9\x5d\x59\xa9\x46\x01\xef\x8a\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\ +\x00\x13\x62\x07\x03\x00\x01\x13\xca\x07\x03\x00\x01\xff\xd4\xb1\xe1\x3d\xa5\x21\xac\xed\x00\x11\x33\ +\x01\xff\xc8\x74\x9c\x6b\x05\xf4\x93\xcc\x00\x05\x03\x07\x00\x00\x13\xe2\x08\x03\x13\x2a\x09\x03\x00\ +\x00\x13\x3a\x09\x03\x00\x01\xff\xe4\xb3\xb1\xaf\xe9\x8b\xb1\xb9\x00\x11\x40\x01\x00\x00\x05\x01\x07\ +\x00\x00\x13\xc2\x0a\x03\x00\x01\x13\x22\x0b\x03\x00\x01\xff\x3f\xc9\x48\xdf\x92\x66\xca\xa0\x00\x11\ +\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xb2\x0b\x03\x00\x01\x13\x12\x0c\x03\x00\x01\xff\x82\x07\xd9\ +\x2c\x0b\xe6\x82\xe6\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\x2a\x0d\x03\x00\x04\xff\xc1\xf6\x31\ +\x65\xe8\x7f\x83\xcd\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\x8a\x0d\x03\x00\x01\x13\xea\ +\x0d\x03\x00\x01\xff\x32\xfa\x5b\x64\x42\x16\x15\xa8\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\ +\x6a\x0f\x03\x00\x01\x13\xca\x0f\x03\x00\x01\xff\x1c\xe6\x91\xdb\x40\x16\x6a\xb1\x00\x11\x40\x01\x00\ +\x00\x05\x02\x07\x00\x00\x13\x5a\x10\x03\x00\x01\x13\xba\x10\x03\x00\x01\xff\x5a\x40\xb3\x14\xcf\x14\ +\x7d\xd7\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\xe2\x11\x03\x00\x04\xff\xb3\xf6\x7c\xa5\x4d\x43\ +\x68\xcc\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x4a\x12\x03\x00\x01\x13\xb2\x12\x03\x00\x01\ +\xff\xa1\xab\x80\x26\xd3\x5c\xe0\xda\x00\x51\x40\x01\x01\x00\x00\x05\x03\x07\x00\x00\x13\x3a\x13\x03\ +\x00\x01\x13\xa2\x13\x03\x00\x01\xff\x89\xe6\xc0\x56\x8e\x64\x47\xfd\x00\x11\x40\x01\x00\x00\x05\x01\ +\x07\x00\x00\x13\x9a\x15\x03\x00\x01\x13\x02\x16\x03\x00\x01\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\ +\x11\x38\x02\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x00\x01\x13\x1a\x17\x03\x13\x6a\x17\x03\x00\x00\ +\x13\x7a\x17\x03\x00\x01\xff\xc7\x27\x3e\x49\x10\x9a\xe2\x9f\x00\x51\x40\x01\x01\x00\x00\x05\x03\x07\ +\x00\x00\x13\xda\x17\x03\x00\x01\x13\x32\x18\x03\x00\x01\xfe\x39\xf6\x26\x5f\xd8\x74\x94\x11\x40\x01\ +\x00\x00\x05\x01\x07\x00\x00\x13\xb2\x1a\x03\x00\x01\x13\x12\x1b\x03\x00\x01\xff\x7b\x91\xc4\x6c\x23\ +\x72\xe8\xc5\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\x4a\x1c\x03\x00\x01\x13\xa2\x1c\x03\ +\x00\x01\xff\x01\xa4\xd0\xba\x9d\x71\x98\xce\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xa2\x1e\ +\x03\x00\x01\x13\x02\x1f\x03\x00\x01\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x51\x3b\x01\x01\xff\xdc\ +\x7c\x83\x6e\x37\xee\x08\xf4\x00\x05\x01\x07\x00\x00\x13\x3a\x20\x03\x13\x8a\x20\x03\x00\x00\x13\x9a\ +\x20\x03\x00\x01\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x51\x2e\x01\x02\xff\xe9\x50\x8e\xfe\x16\x14\ +\x63\xd1\x00\x04\x07\x00\x00\x13\xa2\x21\x03\x13\xe2\x21\x03\x00\x00\x13\xf2\x21\x03\x00\x01\xff\x78\ +\x89\xf3\x0d\xb8\x65\x4c\x99\x00\x51\x2c\x01\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x05\x01\x07\ +\x00\x00\x13\xf2\x22\x03\x13\x32\x23\x03\x00\x00\x13\x42\x23\x03\x00\x01\xff\x65\x60\x7d\x28\xd8\x0b\ +\x37\xe9\x00\x51\x2c\x01\x02\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\x52\x25\x03\ +\x13\x92\x25\x03\x00\x00\x13\xa2\x25\x03\x00\x01\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x51\x2c\x01\ +\x05\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\x22\x27\x03\x13\x6a\x27\x03\x00\x00\ +\x13\x7a\x27\x03\x00\x01\xfb\x82\x65\xe6\xe3\xe3\x03\x9b\x51\x32\x01\x09\xff\x94\x4d\x64\x7f\x18\x83\ +\x9d\xb4\x00\x05\x02\x07\x00\x00\x13\xfa\x2a\x03\x13\x52\x2b\x03\x00\x00\x13\x62\x2b\x03\x00\x01\xff\ +\xdd\x1d\x76\x79\xf5\x61\xa0\xbb\x00\x51\x32\x01\x09\xff\x94\x4d\x64\x7f\x18\x83\x9d\xb4\x00\x05\x01\ +\x07\x00\x00\x13\xea\x2c\x03\x13\x42\x2d\x03\x00\x00\x13\x52\x2d\x03\x00\x01\xff\x0a\xb0\x59\x99\xb0\ +\xf7\x48\xd9\x00\x51\x2c\x01\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x05\x01\x07\x00\x00\x13\xea\ +\x2d\x03\x13\x32\x2e\x03\x00\x00\x13\x42\x2e\x03\x00\x01\xff\xbd\x02\x98\x4a\xe2\x71\xe6\xb7\x00\x11\ +\x2c\x02\xff\xeb\x9b\xb9\x2c\xd6\x62\x70\xb9\x00\x00\x01\x13\x7a\x30\x03\x13\xba\x30\x03\x00\x00\x13\ +\xca\x30\x03\x00\x01\xff\x0d\x30\x5c\xa2\x6c\x78\x75\xd4\x00\x51\x4b\x01\x09\xfb\x82\x65\xe6\xe3\xe3\ +\x03\x9b\x15\x02\x07\x01\x00\x00\x13\x52\x32\x03\x00\x01\x13\xb2\x32\x03\x00\x01\xff\x5e\xec\x0e\xea\ +\x21\xb4\xb7\xdd\x00\x51\x4c\x01\x09\xff\xdd\x1d\x76\x79\xf5\x61\xa0\xbb\x00\x15\x01\x07\x01\x00\x00\ +\x13\xba\x39\x03\x00\x01\x13\x1a\x3a\x03\x00\x01\xff\x12\xdf\x9b\x44\xb5\x68\x0b\x98\x00\x51\x4c\x01\ +\x09\xff\xdd\x1d\x76\x79\xf5\x61\xa0\xbb\x00\x55\x01\x07\x01\x02\x01\x1d\x13\x42\x41\x03\x00\x01\x13\ +\xa2\x41\x03\x00\x01\xff\x74\x15\xb4\xf5\xa7\x28\x1e\xf5\x00\x11\x33\x03\xff\xc8\x74\x9c\x6b\x05\xf4\ +\x93\xcc\x00\x00\x01\x13\xe2\x42\x03\x13\x32\x43\x03\x00\x00\x13\x42\x43\x03\x13\xaa\x43\x03\x00\x00\ +\xff\xb6\x47\x7c\x96\x0f\xad\x0b\xa3\x00\x11\x43\x01\x00\x00\x05\x01\x07\x00\x00\x13\xba\x43\x03\x00\ +\x01\x13\x1a\x44\x03\x00\x01\xff\x6e\xb1\xc0\x77\x33\x9a\x5f\x99\x00\x11\x13\x01\xff\xf8\xf3\x93\x13\ +\xa9\x66\xc3\x86\x00\x04\x07\x00\x00\x13\xc2\x44\x03\x13\xea\x44\x03\x00\x03\xff\x63\x61\x70\x6e\x70\ +\x2f\x63\x2b\x03\x2b\x2e\x63\x61\x70\x6e\x70\x3a\x61\x6c\x6c\x6f\x77\x43\x61\x6e\x63\x65\x6c\x6c\x61\ +\x74\x69\x6f\x01\x6e\x50\x01\x01\x00\x03\xff\x63\x61\x70\x6e\x70\x2f\x63\x2b\x02\x2b\x2e\x63\x61\x70\ +\x6e\x70\x3a\x6e\x61\x6d\x65\x73\x70\x61\x63\x01\x65\x50\x01\x01\x01\x0c\x00\x02\xff\x7a\x68\x69\x6e\ +\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\ +\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\ +\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x00\x51\x04\x01\x01\xff\x5a\xfd\x0f\x06\x75\xe5\xcb\x8c\x00\ +\x11\x01\x92\xff\x63\x61\x70\x61\x62\x69\x6c\x69\x01\x74\x79\x56\x65\x72\x73\x69\x6f\x01\x6e\x51\x08\ +\x03\x05\x01\x01\xff\xf7\x08\xfa\x7b\xe5\x7b\xa7\x86\x01\xd1\x3d\x88\xe1\x93\x21\xdb\xc9\x11\x31\x6a\ +\x00\x02\x11\x29\x07\x00\x00\xff\x99\x11\x3c\xd9\xd4\x2f\xe3\xe3\x01\x1a\x41\x05\xd9\x7f\xa6\x31\xfb\ +\x11\x1d\xaa\x00\x02\x11\x19\x07\xff\x67\x65\x74\x54\x68\x65\x53\x63\x00\x0f\x68\x65\x6d\x61\x40\x01\ +\xff\x67\x65\x74\x52\x65\x66\x6c\x65\x01\x63\x74\x69\x6f\x6e\x56\x65\x72\x0f\x73\x69\x6f\x6e\x40\x01\ +\x50\x01\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\ +\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\ +\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x00\x51\x04\x01\x01\xff\xd0\ +\x02\x2f\x96\x1c\xae\x50\xb7\x00\x11\x01\x92\xff\x63\x61\x70\x61\x62\x69\x6c\x69\x01\x74\x79\x56\x65\ +\x72\x73\x69\x6f\x01\x6e\x50\x01\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x04\x6f\x2f\x70\x72\x6f\x74\ +\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x68\x70\x6b\x2f\x68\x70\x6b\x2e\x63\x61\x70\x6e\x70\x3a\ +\x48\x03\x70\x6b\x50\x01\x01\x50\x03\x05\x51\x08\x01\x01\xff\x76\xc7\x4b\x10\x68\x2e\xa5\xf9\x00\x00\ +\x00\xff\x5c\xa5\xa4\x2d\x58\x45\xd4\xb9\x00\x00\x00\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\ +\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\ +\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\ +\x69\x6f\x6e\x2e\x67\x65\x74\x54\x68\x65\x53\x63\x68\x65\x6d\x61\x24\x50\x61\x72\x07\x61\x6d\x73\xff\ +\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\ +\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\ +\x70\x6e\x70\x3a\x43\x61\x70\x3f\x53\x63\x68\x65\x6d\x61\x50\x01\x01\x50\x01\x01\x31\x01\xc7\x02\x51\ +\x2c\x03\x05\x01\x01\xff\xa8\xf5\x5e\x25\x29\x54\x03\x92\x01\xb0\x61\xc9\x74\xdb\x4b\xd3\xeb\x13\x51\ +\x01\x52\x00\x02\x13\x49\x01\x07\x01\x0a\xff\xe7\xdf\x90\xb9\x1b\x2e\x24\xcd\x01\xbb\x75\x95\x12\x8c\ +\x51\x48\xe9\x13\x3d\x01\xa2\x00\x02\x13\x39\x01\x07\x01\x08\xff\x65\x80\xf3\x40\xed\xfd\xf6\xa8\x01\ +\xf9\x5d\x59\xa9\x46\x01\xef\x8a\x13\x2d\x01\x9a\x00\x02\x13\x29\x01\x07\x01\x04\xff\xe4\xb3\xb1\xaf\ +\xe9\x8b\xb1\xb9\x01\x3f\xc9\x48\xdf\x92\x66\xca\xa0\x13\x1d\x01\x52\x00\x02\x13\x15\x01\x07\x01\x06\ +\xff\x82\x07\xd9\x2c\x0b\xe6\x82\xe6\x01\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x13\x09\x01\x8a\x00\x02\x13\ +\x05\x01\x07\x01\x07\xff\xc1\xf6\x31\x65\xe8\x7f\x83\xcd\x01\x32\xfa\x5b\x64\x42\x16\x15\xa8\x11\xf9\ +\x72\x00\x02\x11\xf1\x07\x01\x05\xff\x1c\xe6\x91\xdb\x40\x16\x6a\xb1\x01\x16\x78\x43\x81\xe9\xe8\x7f\ +\xdf\x11\xe5\x62\x00\x02\x11\xdd\x07\x00\x00\xff\x5a\x40\xb3\x14\xcf\x14\x7d\xd7\x01\xb3\xf6\x7c\xa5\ +\x4d\x43\x68\xcc\x11\xd1\x92\x00\x02\x11\xcd\x07\x01\x09\xff\xa1\xab\x80\x26\xd3\x5c\xe0\xda\x01\x89\ +\xe6\xc0\x56\x8e\x64\x47\xfd\x11\xc1\xa2\x00\x02\x11\xbd\x07\x01\x03\xff\xc7\x27\x3e\x49\x10\x9a\xe2\ +\x9f\x01\x00\x39\xf6\x26\x5f\xd8\x74\x94\x11\xb1\x4a\x00\x02\x11\xa9\x07\x01\x02\xff\x7b\x91\xc4\x6c\ +\x23\x72\xe8\xc5\x01\x01\xa4\xd0\xba\x9d\x71\x98\xce\x11\x9d\x4a\x00\x02\x11\x95\x07\xff\x6c\x69\x73\ +\x74\x4e\x6f\x64\x65\x00\x01\x73\x40\x01\xff\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x47\x65\x74\ +\x56\x61\x6c\x07\x75\x65\x73\x40\x01\xff\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x53\x65\x74\x56\ +\x61\x6c\x03\x75\x65\x40\x01\xff\x73\x75\x62\x73\x63\x72\x69\x62\x00\x01\x65\x40\x01\xff\x64\x69\x73\ +\x63\x6f\x6e\x6e\x65\x01\x63\x74\x44\x65\x76\x69\x63\x65\x00\x00\x40\x01\xff\x6c\x69\x73\x74\x4e\x6f\ +\x64\x65\x00\x1f\x73\x4a\x73\x6f\x6e\x40\x01\xff\x75\x6e\x73\x75\x62\x73\x63\x72\x00\x07\x69\x62\x65\ +\x40\x01\xff\x67\x65\x74\x53\x65\x73\x73\x69\x01\x6f\x6e\x56\x65\x72\x73\x69\x6f\x01\x6e\x40\x01\xff\ +\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x53\x65\x74\x56\x61\x6c\x07\x75\x65\x32\x40\x01\xff\x73\ +\x65\x74\x56\x61\x6c\x75\x65\x00\x00\x00\x40\x01\xff\x67\x65\x74\x56\x61\x6c\x75\x65\x00\x00\x00\x40\ +\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x3a\x00\x00\x51\x24\x03\x01\x51\x30\ +\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x52\x00\x00\x51\x2c\x03\x01\x51\x48\x02\x01\x3f\x74\x79\ +\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x74\x68\x65\x53\x63\x68\x65\x6d\x00\x01\x61\x01\ +\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x00\x01\x01\x0e\x00\x01\x31\ +\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\ +\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\ +\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x54\x68\x65\ +\x53\x63\x68\x65\x6d\x61\x24\x52\x65\x73\x0f\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\ +\x01\x00\x00\x11\x0d\x52\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x74\x68\x65\x53\x63\x68\x65\x6d\ +\x00\x01\x61\x01\x10\xff\xcf\x85\xeb\x76\x7a\xef\x31\xcb\x00\x00\x01\x01\x10\x00\x01\x31\x01\xe2\x02\ +\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\ +\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\ +\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x52\x65\x66\x6c\x65\x63\ +\x74\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x50\x61\x72\x07\x61\x6d\x73\x31\x01\xea\x02\xff\x7a\ +\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\ +\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\ +\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x52\x65\x66\x6c\x65\x63\x74\x69\ +\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x52\x65\x73\x0f\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\ +\x00\x00\x04\x01\x00\x00\x11\x0d\x42\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x7f\x76\x65\x72\x73\x69\ +\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x11\x01\xc2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\ +\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x00\x11\x01\x37\x51\x0c\x01\x01\xff\xb1\xa3\x0f\ +\xf1\xcc\x1b\x52\xb9\x00\x11\x11\x52\xff\x42\xc2\x0f\xfa\xbb\x55\xbf\xde\x00\x11\x11\x5a\xff\xae\x57\ +\x13\x04\xe3\x1d\x8e\xf3\x00\x11\x11\x5a\xff\x50\x61\x72\x61\x6d\x65\x74\x65\x00\x01\x72\xff\x4e\x65\ +\x73\x74\x65\x64\x4e\x6f\x00\x03\x64\x65\xff\x53\x6f\x75\x72\x63\x65\x49\x6e\x00\x03\x66\x6f\x31\x01\ +\x17\x03\x51\x38\x03\x04\x00\x00\x04\x01\x00\x00\x13\x79\x01\x1a\x00\x00\x53\x74\x01\x03\x01\x53\x80\ +\x01\x02\x01\x01\x01\x14\x01\x01\x00\x00\x13\x7d\x01\x62\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\ +\x01\x11\x02\x02\x14\x01\x02\x00\x00\x13\x85\x01\xc2\x00\x00\x53\x88\x01\x03\x01\x53\x94\x01\x02\x01\ +\x11\x03\x02\x14\x01\x03\x00\x00\x13\x91\x01\x42\x00\x00\x53\x8c\x01\x03\x01\x53\x98\x01\x02\x01\x11\ +\x06\x01\x14\x01\x04\x00\x00\x13\x95\x01\x62\x00\x00\x53\x94\x01\x03\x01\x53\xb0\x01\x02\x01\x11\x07\ +\x02\x14\x01\x05\x00\x00\x13\xad\x01\x62\x00\x00\x53\xac\x01\x03\x01\x53\xc8\x01\x02\x01\x0d\x08\xff\ +\xff\x14\x01\x06\x00\x00\x13\xc5\x01\x2a\x00\x00\x53\xc0\x01\x03\x01\x53\xcc\x01\x02\x01\x0d\x09\xfe\ +\xff\x01\x01\xff\x35\x44\xfb\x37\x9b\xb1\xa0\x9e\x00\x13\xc9\x01\x3a\x00\x02\x0d\x0a\xfd\xff\x01\x01\ +\xff\x98\xf5\x33\x43\x36\xb3\x4a\xb5\x00\x13\xb1\x01\x2a\x00\x02\x0d\x0b\xfc\xff\x01\x01\xff\x8f\x21\ +\xc2\xf0\xcf\x53\x27\xe8\x00\x13\x99\x01\x52\x00\x02\x0d\x0c\xfb\xff\x01\x01\xff\x20\x94\x0d\x7a\xac\ +\xa5\x8a\xb1\x00\x13\x85\x01\x32\x00\x02\x0d\x0d\xfa\xff\x01\x01\xff\x90\x02\x0a\x40\xd4\x19\x16\xec\ +\x00\x13\x6d\x01\x5a\x00\x02\x11\x04\x05\x14\x01\x20\x00\x00\x13\x59\x01\x5a\x00\x00\x53\x58\x01\x03\ +\x01\x53\x74\x01\x02\x01\x31\x05\x20\x01\x14\x01\x21\x00\x00\x13\x71\x01\x52\x00\x00\x53\x70\x01\x03\ +\x01\x53\x7c\x01\x02\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x64\x69\x73\x70\x6c\x61\x79\ +\x4e\x00\x07\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x64\x69\x73\x70\x6c\x61\x79\x4e\x02\x61\ +\x6d\x65\x50\x72\x65\x66\x69\x78\x4c\x65\x6e\x67\x74\x68\x00\x01\x08\x00\x02\x01\x08\x00\x01\x7f\x73\ +\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x6e\x65\x73\x74\x65\x64\x4e\x6f\x00\x07\ +\x64\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\xc2\x0f\xfa\xbb\x55\xbf\xde\x00\x00\x01\x01\ +\x0e\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\ +\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\x0f\x66\x69\x6c\x65\x00\x06\x3f\ +\x73\x74\x72\x75\x63\x74\x0f\x65\x6e\x75\x6d\xff\x69\x6e\x74\x65\x72\x66\x61\x63\x00\x01\x65\x1f\x63\ +\x6f\x6e\x73\x74\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x03\x6f\x6e\xff\x70\x61\x72\x61\x6d\x65\x74\ +\x65\x00\x03\x72\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x00\ +\x01\x01\x0e\x00\x01\xff\x69\x73\x47\x65\x6e\x65\x72\x69\x00\x01\x63\x01\x01\x00\x02\x01\x01\x00\x01\ +\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x45\ +\x6c\x65\x6d\x65\x3f\x6e\x74\x53\x69\x7a\x65\x11\x01\xc7\x51\x20\x01\x02\x00\x00\x11\x59\x32\x00\x00\ +\x01\x01\x11\x51\x22\x00\x00\x01\x02\x11\x49\x2a\x00\x00\x01\x03\x11\x41\x4a\x00\x00\x01\x04\x11\x3d\ +\x52\x00\x00\x01\x05\x11\x39\x5a\x00\x00\x01\x06\x11\x35\x42\x00\x00\x01\x07\x11\x2d\x82\x00\x00\x1f\ +\x65\x6d\x70\x74\x79\x07\x62\x69\x74\x0f\x62\x79\x74\x65\xff\x74\x77\x6f\x42\x79\x74\x65\x73\x00\x00\ +\x00\xff\x66\x6f\x75\x72\x42\x79\x74\x65\x00\x01\x73\xff\x65\x69\x67\x68\x74\x42\x79\x74\x00\x03\x65\ +\x73\x7f\x70\x6f\x69\x6e\x74\x65\x72\xff\x69\x6e\x6c\x69\x6e\x65\x43\x6f\x01\x6d\x70\x6f\x73\x69\x74\ +\x65\x00\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\ +\x3a\x46\x69\x65\x6c\x64\x00\x00\x11\x01\x17\x51\x04\x01\x01\xff\x12\xc7\xfe\x7c\xbe\x4c\xb1\x97\x00\ +\x11\x01\x7a\xff\x6e\x6f\x44\x69\x73\x63\x72\x69\x00\x3f\x6d\x69\x6e\x61\x6e\x74\x31\x01\x8f\x01\x51\ +\x1c\x03\x04\x00\x00\x04\x01\x00\x00\x11\xb5\x2a\x00\x00\x51\xb0\x03\x01\x51\xbc\x02\x01\x01\x01\x14\ +\x01\x01\x00\x00\x11\xb9\x52\x00\x00\x51\xb8\x03\x01\x51\xc4\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\ +\x11\xc1\x62\x00\x00\x51\xc0\x03\x01\x51\xdc\x02\x01\x11\x03\x01\x14\x01\x03\x01\x01\x11\xd9\x92\x00\ +\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\x0d\x04\xff\xff\x01\x01\xff\x6f\x74\xb4\x6b\x47\x05\x23\xc4\x00\ +\x11\xe5\x2a\x00\x02\x0d\x05\xfe\xff\x01\x01\xff\x11\x1d\xdb\x68\xdb\xcd\xfc\xca\x00\x11\xcd\x32\x00\ +\x02\x01\x06\x01\x01\xff\xe6\x0b\x87\x87\xc2\xd5\x90\xbb\x00\x11\xb5\x42\x00\x02\x0f\x6e\x61\x6d\x65\ +\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\x65\x4f\x72\x64\x65\x00\x01\x72\x01\x07\x00\x02\x01\ +\x07\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\ +\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\ +\x69\x01\x6e\x61\x6e\x74\x56\x61\x6c\x75\x01\x65\x01\x07\x00\x02\x0d\x07\xff\xff\x00\x01\x0f\x73\x6c\ +\x6f\x74\x1f\x67\x72\x6f\x75\x70\x7f\x6f\x72\x64\x69\x6e\x61\x6c\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\ +\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x3f\x73\x74\x72\x75\ +\x63\x74\x31\x01\x8f\x01\x51\x1c\x03\x04\x10\x07\x14\x01\x07\x00\x00\x11\xb5\x72\x00\x00\x51\xb4\x03\ +\x01\x51\xc0\x02\x01\x11\x01\x0c\x14\x01\x08\x00\x00\x11\xbd\x6a\x00\x00\x51\xbc\x03\x01\x51\xc8\x02\ +\x01\x11\x02\x0d\x14\x01\x09\x00\x00\x11\xc5\xb2\x00\x00\x51\xc8\x03\x01\x51\xd4\x02\x01\x11\x03\xe0\ +\x14\x01\x0a\x00\x00\x11\xd1\x42\x00\x00\x51\xcc\x03\x01\x51\xd8\x02\x01\x11\x04\x0f\x14\x01\x0b\x00\ +\x00\x11\xd5\x92\x00\x00\x51\xd8\x03\x01\x51\xe4\x02\x01\x11\x05\x08\x14\x01\x0c\x00\x00\x11\xe1\x9a\ +\x00\x00\x51\xe4\x03\x01\x51\xf0\x02\x01\x11\x06\x03\x14\x01\x0d\x00\x00\x11\xed\x3a\x00\x00\x51\xe8\ +\x03\x01\x53\x04\x01\x02\x01\xff\x64\x61\x74\x61\x57\x6f\x72\x64\x00\x1f\x43\x6f\x75\x6e\x74\x01\x07\ +\x00\x02\x01\x07\x00\x01\xff\x70\x6f\x69\x6e\x74\x65\x72\x43\x00\x0f\x6f\x75\x6e\x74\x01\x07\x00\x02\ +\x01\x07\x00\x01\xff\x70\x72\x65\x66\x65\x72\x72\x65\x01\x64\x4c\x69\x73\x74\x45\x6e\x63\x1f\x6f\x64\ +\x69\x6e\x67\x01\x0f\xff\x26\x19\x52\xba\x7d\x8f\x95\xd1\x00\x00\x01\x01\x0f\x00\x01\x7f\x69\x73\x47\ +\x72\x6f\x75\x70\x01\x01\x00\x02\x01\x01\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\x69\x01\x6e\x61\x6e\ +\x74\x43\x6f\x75\x6e\x01\x74\x01\x07\x00\x02\x01\x07\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\x69\x01\ +\x6e\x61\x6e\x74\x4f\x66\x66\x73\x03\x65\x74\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x66\x69\x65\x6c\x64\ +\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x00\x01\x01\x0e\x00\ +\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\ +\x45\x6e\x75\x6d\x65\x0f\x72\x61\x6e\x74\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\ +\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\ +\x49\x52\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x51\x62\x00\x00\ +\x51\x50\x03\x01\x51\x6c\x02\x01\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\ +\x65\x4f\x72\x64\x65\x00\x01\x72\x01\x07\x00\x02\x01\x07\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\ +\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\ +\x01\x01\x0e\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\ +\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x0f\x65\x6e\x75\x6d\x11\x01\x3f\x51\x04\x03\x04\x10\x03\x14\x01\ +\x0e\x00\x00\x11\x0d\x5a\x00\x00\x51\x0c\x03\x01\x51\x28\x02\x01\xff\x65\x6e\x75\x6d\x65\x72\x61\x6e\ +\x00\x03\x74\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x4d\x9a\x54\xdc\xeb\x7c\x8a\x97\x00\x00\x01\ +\x01\x0e\x00\x01\x11\x01\xd2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\ +\x6e\x70\x3a\x4d\x65\x74\x68\x6f\x01\x64\x11\x01\x07\x50\x01\x01\x31\x01\xc7\x01\x51\x20\x03\x04\x00\ +\x00\x04\x01\x00\x00\x11\xd1\x2a\x00\x00\x51\xcc\x03\x01\x51\xd8\x02\x01\x01\x01\x14\x01\x01\x00\x00\ +\x11\xd5\x52\x00\x00\x51\xd4\x03\x01\x51\xe0\x02\x01\x11\x03\x01\x14\x01\x02\x00\x00\x11\xdd\x82\x00\ +\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\x11\x05\x02\x14\x01\x03\x00\x00\x11\xe5\x8a\x00\x00\x51\xe8\x03\ +\x01\x51\xf4\x02\x01\x11\x07\x01\x14\x01\x04\x00\x00\x11\xf1\x62\x00\x00\x51\xf0\x03\x01\x53\x0c\x01\ +\x02\x01\x11\x04\x02\x14\x01\x05\x00\x00\x13\x09\x01\x5a\x00\x00\x53\x08\x01\x03\x01\x53\x14\x01\x02\ +\x01\x11\x06\x03\x14\x01\x06\x00\x00\x13\x11\x01\x62\x00\x00\x53\x10\x01\x03\x01\x53\x1c\x01\x02\x01\ +\x11\x02\x04\x14\x01\x07\x00\x00\x13\x19\x01\x9a\x00\x00\x53\x1c\x01\x03\x01\x53\x38\x01\x02\x01\x0f\ +\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\x65\x4f\x72\x64\x65\x00\x01\x72\x01\ +\x07\x00\x02\x01\x07\x00\x01\xff\x70\x61\x72\x61\x6d\x53\x74\x72\x01\x75\x63\x74\x54\x79\x70\x65\x00\ +\x01\x09\x00\x02\x01\x09\x00\x01\xff\x72\x65\x73\x75\x6c\x74\x53\x74\x01\x72\x75\x63\x74\x54\x79\x70\ +\x65\x00\x00\x01\x09\x00\x02\x01\x09\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\ +\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\ +\xff\x70\x61\x72\x61\x6d\x42\x72\x61\x00\x03\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\ +\x00\x01\x01\x10\x00\x01\xff\x72\x65\x73\x75\x6c\x74\x42\x72\x00\x07\x61\x6e\x64\x01\x10\xff\x2b\x42\ +\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\xff\x69\x6d\x70\x6c\x69\x63\x69\x74\x01\x50\x61\ +\x72\x61\x6d\x65\x74\x65\x03\x72\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xb1\xa3\x0f\xf1\xcc\x1b\ +\x52\xb9\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\ +\x61\x2e\x63\x61\x70\x6e\x70\x3a\x53\x75\x70\x65\x72\x1f\x63\x6c\x61\x73\x73\x11\x01\x07\x50\x01\x01\ +\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x1a\x00\x00\x51\x24\x03\x01\x51\x30\x02\ +\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x03\x69\x64\x01\ +\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\ +\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\ +\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x69\x6e\x74\x65\x72\x66\x61\x63\x01\x65\x11\x01\x77\x51\ +\x08\x03\x04\x10\x03\x14\x01\x0f\x00\x00\x11\x29\x42\x00\x00\x11\x02\x02\x51\x20\x02\x01\x11\x01\x04\ +\x14\x01\x1f\x00\x00\x11\x4a\x02\x00\x00\x11\x62\x02\x11\xaa\x02\x7f\x6d\x65\x74\x68\x6f\x64\x73\x01\ +\x0e\x00\x01\x50\x03\x01\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xbf\x80\x4d\x33\x3b\xe2\xcc\x95\x00\x01\ +\x11\x01\x6a\xff\x73\x75\x70\x65\x72\x63\x6c\x61\x00\x0f\x73\x73\x65\x73\x50\x03\x01\x01\x0e\x00\x01\ +\x50\x03\x01\x01\x10\xff\xf8\xd7\xa4\xd0\x9e\x2a\x96\xa9\x00\x00\x01\x50\x02\x01\x01\x0e\x00\x01\x11\ +\x01\xc2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\ +\x70\x65\x00\x11\x01\x07\x50\x01\x01\x31\x01\x2f\x04\x51\x4c\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x13\ +\x05\x02\x2a\x00\x00\x52\x02\x03\x01\x53\x0c\x02\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x13\x09\ +\x02\x2a\x00\x00\x53\x04\x02\x03\x01\x53\x10\x02\x02\x01\x0d\x02\xfd\xff\x14\x01\x02\x00\x00\x13\x0d\ +\x02\x2a\x00\x00\x53\x08\x02\x03\x01\x53\x14\x02\x02\x01\x0d\x03\xfc\xff\x14\x01\x03\x00\x00\x13\x11\ +\x02\x32\x00\x00\x53\x0c\x02\x03\x01\x53\x18\x02\x02\x01\x0d\x04\xfb\xff\x14\x01\x04\x00\x00\x13\x15\ +\x02\x32\x00\x00\x53\x10\x02\x03\x01\x53\x1c\x02\x02\x01\x0d\x05\xfa\xff\x14\x01\x05\x00\x00\x13\x19\ +\x02\x32\x00\x00\x53\x14\x02\x03\x01\x53\x20\x02\x02\x01\x0d\x06\xf9\xff\x14\x01\x06\x00\x00\x13\x1d\ +\x02\x32\x00\x00\x53\x18\x02\x03\x01\x53\x24\x02\x02\x01\x0d\x07\xf8\xff\x14\x01\x07\x00\x00\x13\x21\ +\x02\x3a\x00\x00\x53\x1c\x02\x03\x01\x53\x28\x02\x02\x01\x0d\x08\xf7\xff\x14\x01\x08\x00\x00\x13\x25\ +\x02\x3a\x00\x00\x53\x20\x02\x03\x01\x53\x2c\x02\x02\x01\x0d\x09\xf6\xff\x14\x01\x09\x00\x00\x13\x29\ +\x02\x3a\x00\x00\x53\x24\x02\x03\x01\x53\x30\x02\x02\x01\x0d\x0a\xf5\xff\x14\x01\x0a\x00\x00\x13\x2d\ +\x02\x42\x00\x00\x53\x28\x02\x03\x01\x53\x34\x02\x02\x01\x0d\x0b\xf4\xff\x14\x01\x0b\x00\x00\x13\x31\ +\x02\x42\x00\x00\x53\x2c\x02\x03\x01\x53\x38\x02\x02\x01\x0d\x0c\xf3\xff\x14\x01\x0c\x00\x00\x13\x35\ +\x02\x2a\x00\x00\x53\x30\x02\x03\x01\x53\x3c\x02\x02\x01\x0d\x0d\xf2\xff\x14\x01\x0d\x00\x00\x13\x39\ +\x02\x2a\x00\x00\x53\x34\x02\x03\x01\x53\x40\x02\x02\x01\x0d\x0e\xf1\xff\x01\x01\xff\x97\xea\x60\x0a\ +\x25\x39\xe7\x87\x00\x13\x3d\x02\x2a\x00\x02\x0d\x0f\xf0\xff\x01\x01\xff\xa9\x87\x7f\x1a\x71\x78\x0e\ +\x9e\x00\x13\x25\x02\x2a\x00\x02\x0d\x10\xef\xff\x01\x01\xff\xd3\xc6\x4c\xef\x60\x6f\x3a\xac\x00\x13\ +\x0d\x02\x3a\x00\x02\x0d\x11\xee\xff\x01\x01\xff\xbf\x0c\xfb\xf7\x69\xca\x8b\xed\x00\x13\xf5\x01\x52\ +\x00\x02\x0d\x12\xed\xff\x01\x01\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x13\xe1\x01\x5a\x00\x02\x0f\ +\x76\x6f\x69\x64\x00\x06\x0f\x62\x6f\x6f\x6c\x00\x06\x0f\x69\x6e\x74\x38\x00\x06\x1f\x69\x6e\x74\x31\ +\x36\x00\x06\x1f\x69\x6e\x74\x33\x32\x00\x06\x1f\x69\x6e\x74\x36\x34\x00\x06\x1f\x75\x69\x6e\x74\x38\ +\x00\x06\x3f\x75\x69\x6e\x74\x31\x36\x00\x06\x3f\x75\x69\x6e\x74\x33\x32\x00\x06\x3f\x75\x69\x6e\x74\ +\x36\x34\x00\x06\x7f\x66\x6c\x6f\x61\x74\x33\x32\x00\x06\x7f\x66\x6c\x6f\x61\x74\x36\x34\x00\x06\x0f\ +\x74\x65\x78\x74\x00\x06\x0f\x64\x61\x74\x61\x00\x06\x0f\x6c\x69\x73\x74\x0f\x65\x6e\x75\x6d\x3f\x73\ +\x74\x72\x75\x63\x74\xff\x69\x6e\x74\x65\x72\x66\x61\x63\x00\x01\x65\xff\x61\x6e\x79\x50\x6f\x69\x6e\ +\x74\x00\x03\x65\x72\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\ +\x70\x6e\x70\x3a\x56\x61\x6c\x75\x65\x00\x00\x11\x01\x07\x50\x01\x01\x31\x01\x2f\x04\x51\x4c\x03\x04\ +\x0c\xff\xff\x04\x01\x00\x00\x13\x05\x02\x2a\x00\x00\x52\x02\x03\x01\x53\x0c\x02\x02\x01\x1d\x01\xfe\ +\xff\x10\x14\x01\x01\x00\x00\x13\x09\x02\x2a\x00\x00\x53\x04\x02\x03\x01\x53\x10\x02\x02\x01\x1d\x02\ +\xfd\xff\x02\x14\x01\x02\x00\x00\x13\x0d\x02\x2a\x00\x00\x53\x08\x02\x03\x01\x53\x14\x02\x02\x01\x1d\ +\x03\xfc\xff\x01\x14\x01\x03\x00\x00\x13\x11\x02\x32\x00\x00\x53\x0c\x02\x03\x01\x53\x18\x02\x02\x01\ +\x1d\x04\xfb\xff\x01\x14\x01\x04\x00\x00\x13\x15\x02\x32\x00\x00\x53\x10\x02\x03\x01\x53\x1c\x02\x02\ +\x01\x1d\x05\xfa\xff\x01\x14\x01\x05\x00\x00\x13\x19\x02\x32\x00\x00\x53\x14\x02\x03\x01\x53\x20\x02\ +\x02\x01\x1d\x06\xf9\xff\x02\x14\x01\x06\x00\x00\x13\x1d\x02\x32\x00\x00\x53\x18\x02\x03\x01\x53\x24\ +\x02\x02\x01\x1d\x07\xf8\xff\x01\x14\x01\x07\x00\x00\x13\x21\x02\x3a\x00\x00\x53\x1c\x02\x03\x01\x53\ +\x28\x02\x02\x01\x1d\x08\xf7\xff\x01\x14\x01\x08\x00\x00\x13\x25\x02\x3a\x00\x00\x53\x20\x02\x03\x01\ +\x53\x2c\x02\x02\x01\x1d\x09\xf6\xff\x01\x14\x01\x09\x00\x00\x13\x29\x02\x3a\x00\x00\x53\x24\x02\x03\ +\x01\x53\x30\x02\x02\x01\x1d\x0a\xf5\xff\x01\x14\x01\x0a\x00\x00\x13\x2d\x02\x42\x00\x00\x53\x28\x02\ +\x03\x01\x53\x34\x02\x02\x01\x1d\x0b\xf4\xff\x01\x14\x01\x0b\x00\x00\x13\x31\x02\x42\x00\x00\x53\x2c\ +\x02\x03\x01\x53\x38\x02\x02\x01\x0d\x0c\xf3\xff\x14\x01\x0c\x00\x00\x13\x35\x02\x2a\x00\x00\x53\x30\ +\x02\x03\x01\x53\x3c\x02\x02\x01\x0d\x0d\xf2\xff\x14\x01\x0d\x00\x00\x13\x39\x02\x2a\x00\x00\x53\x34\ +\x02\x03\x01\x53\x40\x02\x02\x01\x0d\x0e\xf1\xff\x14\x01\x0e\x00\x00\x13\x3d\x02\x2a\x00\x00\x53\x38\ +\x02\x03\x01\x53\x44\x02\x02\x01\x1d\x0f\xf0\xff\x01\x14\x01\x0f\x00\x00\x13\x41\x02\x2a\x00\x00\x53\ +\x3c\x02\x03\x01\x53\x48\x02\x02\x01\x0d\x10\xef\xff\x14\x01\x10\x00\x00\x13\x45\x02\x3a\x00\x00\x53\ +\x40\x02\x03\x01\x53\x4c\x02\x02\x01\x0d\x11\xee\xff\x14\x01\x11\x00\x00\x13\x49\x02\x52\x00\x00\x53\ +\x48\x02\x03\x01\x53\x54\x02\x02\x01\x0d\x12\xed\xff\x14\x01\x12\x00\x00\x13\x51\x02\x5a\x00\x00\x53\ +\x50\x02\x03\x01\x53\x5c\x02\x02\x01\x0f\x76\x6f\x69\x64\x00\x06\x0f\x62\x6f\x6f\x6c\x01\x01\x00\x02\ +\x01\x01\x00\x01\x0f\x69\x6e\x74\x38\x01\x02\x00\x02\x01\x02\x00\x01\x1f\x69\x6e\x74\x31\x36\x01\x03\ +\x00\x02\x01\x03\x00\x01\x1f\x69\x6e\x74\x33\x32\x01\x04\x00\x02\x01\x04\x00\x01\x1f\x69\x6e\x74\x36\ +\x34\x01\x05\x00\x02\x01\x05\x00\x01\x1f\x75\x69\x6e\x74\x38\x01\x06\x00\x02\x01\x06\x00\x01\x3f\x75\ +\x69\x6e\x74\x31\x36\x01\x07\x00\x02\x01\x07\x00\x01\x3f\x75\x69\x6e\x74\x33\x32\x01\x08\x00\x02\x01\ +\x08\x00\x01\x3f\x75\x69\x6e\x74\x36\x34\x01\x09\x00\x02\x01\x09\x00\x01\x7f\x66\x6c\x6f\x61\x74\x33\ +\x32\x01\x0a\x00\x02\x01\x0a\x00\x01\x7f\x66\x6c\x6f\x61\x74\x36\x34\x01\x0b\x00\x02\x01\x0b\x00\x01\ +\x0f\x74\x65\x78\x74\x01\x0c\x00\x02\x01\x0c\x00\x01\x0f\x64\x61\x74\x61\x01\x0d\x00\x02\x01\x0d\x00\ +\x01\x0f\x6c\x69\x73\x74\x01\x12\x00\x02\x01\x12\x00\x01\x0f\x65\x6e\x75\x6d\x01\x07\x00\x02\x01\x07\ +\x00\x01\x3f\x73\x74\x72\x75\x63\x74\x01\x12\x00\x02\x01\x12\x00\x01\xff\x69\x6e\x74\x65\x72\x66\x61\ +\x63\x00\x01\x65\x00\x06\xff\x61\x6e\x79\x50\x6f\x69\x6e\x74\x00\x03\x65\x72\x01\x12\x00\x02\x01\x12\ +\x00\x01\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\ +\x3a\x4e\x6f\x64\x65\x2e\x1f\x63\x6f\x6e\x73\x74\x11\x01\x77\x51\x08\x03\x04\x10\x03\x14\x01\x10\x00\ +\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x04\x14\x01\x11\x00\x00\x11\x2d\x32\ +\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\ +\xd0\x00\x00\x01\x01\x10\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\ +\x00\x00\x01\x01\x10\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\ +\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x61\x6e\x6e\x6f\x74\x61\x74\x69\x03\x6f\x6e\x31\x01\ +\xdf\x02\x51\x34\x03\x04\x10\x03\x14\x01\x12\x00\x00\x13\x5d\x01\x2a\x00\x00\x53\x58\x01\x03\x01\x53\ +\x64\x01\x02\x01\x11\x01\x70\x14\x01\x13\x00\x00\x13\x61\x01\x62\x00\x00\x53\x60\x01\x03\x01\x53\x6c\ +\x01\x02\x01\x11\x02\x71\x14\x01\x14\x00\x00\x13\x69\x01\x6a\x00\x00\x53\x68\x01\x03\x01\x53\x74\x01\ +\x02\x01\x11\x03\x72\x14\x01\x15\x00\x00\x13\x71\x01\x62\x00\x00\x53\x70\x01\x03\x01\x53\x7c\x01\x02\ +\x01\x11\x04\x73\x14\x01\x16\x00\x00\x13\x79\x01\x8a\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\x01\ +\x11\x05\x74\x14\x01\x17\x00\x00\x13\x85\x01\x72\x00\x00\x53\x84\x01\x03\x01\x53\x90\x01\x02\x01\x11\ +\x06\x75\x14\x01\x18\x00\x00\x13\x8d\x01\x6a\x00\x00\x53\x8c\x01\x03\x01\x53\x98\x01\x02\x01\x11\x07\ +\x76\x14\x01\x19\x00\x00\x13\x95\x01\x6a\x00\x00\x53\x94\x01\x03\x01\x53\xa0\x01\x02\x01\x11\x08\x77\ +\x14\x01\x1a\x00\x00\x13\x9d\x01\x6a\x00\x00\x53\x9c\x01\x03\x01\x53\xa8\x01\x02\x01\x11\x09\x78\x14\ +\x01\x1b\x00\x00\x13\xa5\x01\x8a\x00\x00\x53\xa8\x01\x03\x01\x53\xb4\x01\x02\x01\x11\x0a\x79\x14\x01\ +\x1c\x00\x00\x13\xb1\x01\x72\x00\x00\x53\xb0\x01\x03\x01\x53\xbc\x01\x02\x01\x11\x0b\x7a\x14\x01\x1d\ +\x00\x00\x13\xb9\x01\x6a\x00\x00\x53\xb8\x01\x03\x01\x53\xc4\x01\x02\x01\x11\x0c\x7b\x14\x01\x1e\x00\ +\x00\x13\xc1\x01\x92\x00\x00\x53\xc4\x01\x03\x01\x53\xd0\x01\x02\x01\x0f\x74\x79\x70\x65\x01\x10\xff\ +\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x46\x00\ +\x07\x69\x6c\x65\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x43\x00\x0f\x6f\x6e\ +\x73\x74\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x45\x00\x07\x6e\x75\x6d\x01\ +\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x45\x01\x6e\x75\x6d\x65\x72\x61\x6e\x74\ +\x00\x00\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x53\x00\x1f\x74\x72\x75\x63\ +\x74\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x46\x00\x0f\x69\x65\x6c\x64\x01\ +\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x55\x00\x0f\x6e\x69\x6f\x6e\x01\x01\x00\ +\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x47\x00\x0f\x72\x6f\x75\x70\x01\x01\x00\x02\x01\ +\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x49\x01\x6e\x74\x65\x72\x66\x61\x63\x65\x00\x00\x01\x01\ +\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x4d\x00\x1f\x65\x74\x68\x6f\x64\x01\x01\x00\ +\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x50\x00\x0f\x61\x72\x61\x6d\x01\x01\x00\x02\x01\ +\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x41\x01\x6e\x6e\x6f\x74\x61\x74\x69\x6f\x01\x6e\x01\x01\ +\x00\x02\x01\x01\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\ +\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x4e\x65\x73\x74\x65\x64\x4e\x6f\x03\x64\x65\x11\x01\x07\ +\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\ +\x51\x30\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x1a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x0f\ +\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\x11\x01\ +\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\ +\x6f\x74\x1f\x61\x74\x69\x6f\x6e\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\ +\x00\x00\x11\x45\x1a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x01\x02\x14\x01\x01\x00\x00\x11\x49\x32\ +\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x11\x01\x01\x14\x01\x02\x00\x00\x11\x4d\x32\x00\x00\x51\x48\ +\x03\x01\x51\x54\x02\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\ +\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x00\x01\x01\x10\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\ +\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\x6e\x70\ +\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x50\x61\x72\x61\x6d\ +\x65\x74\x65\x01\x72\x11\x01\x07\x50\x01\x01\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\ +\x0d\x2a\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\ +\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x42\ +\x72\x61\x6e\x64\x00\x00\x11\x01\x27\x51\x08\x01\x01\xff\xc9\x6b\x63\xa9\x85\x34\xd7\xab\x00\x11\x09\ +\x32\xff\xfc\xe7\x9e\x96\x16\xcd\x63\xc8\x00\x11\x05\x42\x1f\x53\x63\x6f\x70\x65\x7f\x42\x69\x6e\x64\ +\x69\x6e\x67\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\ +\x51\x24\x02\x01\x3f\x73\x63\x6f\x70\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xc9\x6b\x63\xa9\ +\x85\x34\xd7\xab\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\ +\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x42\x72\x61\x6e\x64\x3f\x2e\x53\x63\x6f\x70\x65\x11\x01\x07\ +\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x42\x00\x00\x51\x40\x03\x01\ +\x51\x4c\x02\x01\x0d\x01\xff\xff\x14\x01\x01\x00\x00\x11\x49\x2a\x00\x00\x51\x44\x03\x01\x51\x60\x02\ +\x01\x0d\x02\xfe\xff\x14\x01\x02\x00\x00\x11\x5d\x42\x00\x00\x51\x58\x03\x01\x51\x64\x02\x01\x7f\x73\ +\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x0f\x62\x69\x6e\x64\x01\x0e\x00\x01\x50\x03\ +\x01\x01\x10\xff\xfc\xe7\x9e\x96\x16\xcd\x63\xc8\x00\x00\x01\x01\x0e\x00\x01\x7f\x69\x6e\x68\x65\x72\ +\x69\x74\x00\x06\x31\x01\x0a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\ +\x70\x6e\x70\x3a\x42\x72\x61\x6e\x64\x2e\x42\x69\x6e\x64\x69\x6e\x67\x00\x00\x11\x01\x07\x50\x01\x01\ +\x11\x01\x77\x51\x08\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x11\x29\x42\x00\x00\x51\x24\x03\x01\x51\x30\ +\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x11\x2d\x2a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x7f\ +\x75\x6e\x62\x6f\x75\x6e\x64\x00\x06\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\ +\x00\x00\x01\x01\x10\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\ +\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x0f\x6c\x69\x73\x74\x11\x01\x3f\x51\x04\x03\x04\x00\x00\ +\x14\x01\x0e\x00\x00\x11\x0d\x62\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x65\x6c\x65\x6d\x65\x6e\ +\x74\x54\x00\x07\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\ +\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\ +\x79\x70\x65\x2e\x0f\x65\x6e\x75\x6d\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x0f\x00\x00\x11\x29\ +\x3a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x15\x00\x00\x11\x2d\x32\x00\x00\x51\x28\ +\x03\x01\x51\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\ +\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x11\x01\xfa\xff\x63\ +\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x3f\ +\x73\x74\x72\x75\x63\x74\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x10\x00\x00\x11\x29\x3a\x00\x00\ +\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x16\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\ +\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\ +\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\ +\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x69\x6e\x74\ +\x65\x72\x66\x61\x63\x01\x65\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x11\x00\x00\x11\x29\x3a\x00\ +\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x17\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\ +\x51\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\ +\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x8a\x01\xff\x63\x61\ +\x70\x6e\x70\x2f\x73\x63\x05\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\ +\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\x75\x6e\x63\x6f\x6e\x73\x74\x72\x61\x69\x6e\x65\x64\x00\x00\x11\ +\x01\xe7\x51\x10\x03\x04\x0c\xff\xff\x14\x01\x12\x00\x00\x11\x61\x42\x00\x00\x51\x5c\x03\x01\x51\x68\ +\x02\x01\x0d\x01\xfe\xff\x14\x01\x19\x00\x00\x11\x65\x3a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x0d\ +\x02\xfd\xff\x14\x01\x1a\x00\x00\x11\x69\x2a\x00\x00\x51\x64\x03\x01\x51\x70\x02\x01\x0d\x03\xfc\xff\ +\x14\x01\x1b\x00\x00\x11\x6d\x5a\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x7f\x61\x6e\x79\x4b\x69\x6e\ +\x64\x00\x06\x3f\x73\x74\x72\x75\x63\x74\x00\x06\x0f\x6c\x69\x73\x74\x00\x06\xff\x63\x61\x70\x61\x62\ +\x69\x6c\x69\x00\x03\x74\x79\x00\x06\x31\x01\x6a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x04\x68\x65\ +\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\ +\x70\x61\x72\x61\x6d\x0f\x65\x74\x65\x72\x11\x01\x77\x51\x08\x03\x04\x10\x02\x14\x01\x13\x00\x00\x11\ +\x29\x42\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x05\x14\x01\x14\x00\x00\x11\x2d\x7a\x00\x00\ +\x51\x2c\x03\x01\x51\x38\x02\x01\x7f\x73\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\ +\x70\x61\x72\x61\x6d\x65\x74\x65\x00\x3f\x72\x49\x6e\x64\x65\x78\x01\x07\x00\x02\x01\x07\x00\x01\x31\ +\x01\xda\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x06\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\ +\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\x69\x6d\x70\x6c\x69\x63\x69\x74\x4d\x65\ +\x74\x68\x6f\x64\x50\x61\x72\x61\x6d\x65\x74\x03\x65\x72\x11\x01\x3f\x51\x04\x03\x04\x10\x05\x14\x01\ +\x18\x00\x00\x11\x0d\x7a\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x70\x61\x72\x61\x6d\x65\x74\x65\ +\x00\x3f\x72\x49\x6e\x64\x65\x78\x01\x07\x00\x02\x01\x07\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\ +\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\ +\x6f\x69\x6e\x74\x03\x65\x72\x11\x01\xaf\x51\x0c\x03\x04\x0c\xff\xff\x01\x01\xff\x56\x36\x59\xfe\x79\ +\x5f\x3b\x8e\x00\x11\x45\x72\x00\x02\x0d\x01\xfe\xff\x01\x01\xff\x85\x4a\x61\xf4\x24\xf7\xd1\x9d\x00\ +\x11\x31\x52\x00\x02\x0d\x02\xfd\xff\x01\x01\xff\x74\xe2\x56\x0c\x12\xc9\xef\xba\x00\x11\x1d\xc2\x00\ +\x02\xff\x75\x6e\x63\x6f\x6e\x73\x74\x72\x00\x1f\x61\x69\x6e\x65\x64\xff\x70\x61\x72\x61\x6d\x65\x74\ +\x65\x00\x01\x72\xff\x69\x6d\x70\x6c\x69\x63\x69\x74\x02\x4d\x65\x74\x68\x6f\x64\x50\x61\x72\x61\x6d\ +\x65\x74\x65\x72\x00\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\ +\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x1f\x2e\x73\x6c\x6f\x74\x11\x01\xe7\x51\x10\x03\x04\x10\x01\x14\ +\x01\x04\x00\x00\x11\x61\x3a\x00\x00\x51\x5c\x03\x01\x51\x68\x02\x01\x11\x01\x02\x14\x01\x05\x00\x00\ +\x11\x65\x2a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x11\x02\x03\x14\x01\x06\x00\x00\x11\x69\x6a\x00\ +\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x03\x80\x14\x01\x0a\x00\x00\x11\x71\x9a\x00\x00\x51\x74\x03\ +\x01\x51\x80\x02\x01\x3f\x6f\x66\x66\x73\x65\x74\x01\x08\x00\x02\x01\x08\x00\x01\x0f\x74\x79\x70\x65\ +\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\xff\x64\x65\x66\x61\x75\x6c\ +\x74\x56\x00\x0f\x61\x6c\x75\x65\x01\x10\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x00\x01\x01\x10\x00\ +\x01\xff\x68\x61\x64\x45\x78\x70\x6c\x69\x01\x63\x69\x74\x44\x65\x66\x61\x75\x03\x6c\x74\x01\x01\x00\ +\x02\x01\x01\x00\x01\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\ +\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x3f\x2e\x67\x72\x6f\x75\x70\x11\x01\x3f\x51\x04\x03\x04\x10\x02\ +\x14\x01\x07\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x3f\x74\x79\x70\x65\x49\x64\ +\x01\x09\x00\x02\x01\x09\x00\x01\x31\x01\x0a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\ +\x61\x2e\x63\x61\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x2e\x6f\x72\x64\x69\x6e\x61\x6c\x00\x00\x11\x01\ +\x77\x51\x08\x03\x04\x0c\xff\xff\x14\x01\x08\x00\x00\x11\x29\x4a\x00\x00\x51\x28\x03\x01\x51\x34\x02\ +\x01\x1d\x01\xfe\xff\x06\x14\x01\x09\x00\x00\x11\x31\x4a\x00\x00\x51\x30\x03\x01\x51\x3c\x02\x01\xff\ +\x69\x6d\x70\x6c\x69\x63\x69\x74\x00\x00\x07\xff\x65\x78\x70\x6c\x69\x63\x69\x74\x00\x00\x00\x01\x07\ +\x00\x02\x01\x07\x00\x01\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\ +\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\ +\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\ +\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x24\x50\x61\x72\x61\x6d\x73\x00\x00\x11\x01\xaf\x51\x0c\x03\ +\x04\x00\x00\x04\x01\x00\x00\x11\x45\x7a\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\x01\x14\x01\x01\ +\x00\x00\x11\x4d\x32\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x51\ +\x3a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\ +\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\x01\x08\x00\x01\ +\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\x92\x02\xff\x7a\x68\x69\x6e\x73\ +\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\ +\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\ +\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x24\x52\x65\x73\x75\x6c\ +\x74\x01\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x32\x00\x00\x51\x08\x03\x01\ +\x51\x24\x02\x01\x1f\x70\x61\x74\x68\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\ +\x31\x01\xda\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\ +\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\ +\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\ +\x63\x61\x74\x65\x64\x47\x65\x74\x56\x61\x6c\x75\x65\x73\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\x77\ +\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x32\x00\x00\x51\x24\x03\x01\x51\x40\x02\x01\x11\x01\ +\x01\x14\x01\x01\x01\x01\x11\x3d\x3a\x00\x00\x51\x38\x03\x01\x51\x44\x02\x01\x1f\x70\x61\x74\x68\x73\ +\x01\x0e\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\ +\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\xa2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\ +\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x72\x65\x73\x75\ +\x6c\x74\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x73\x07\x75\x6c\x74\x11\x01\x07\x50\x01\x01\x11\x01\x77\ +\x51\x08\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x11\x29\x1a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x0d\ +\x01\xfe\xff\x14\x01\x01\x00\x00\x11\x2d\x22\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x03\x6f\x6b\x01\ +\x12\x01\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x01\x12\x00\x01\x07\x65\x72\x72\x01\x12\ +\x05\x01\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x01\x12\x00\x01\x11\x01\x17\x41\x08\x01\ +\x11\x05\x2a\x11\x05\x32\x0f\x54\x79\x70\x65\x1f\x45\x72\x72\x6f\x72\x31\x01\xda\x01\xff\x7a\x68\x69\ +\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\ +\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\x6f\x74\x61\x74\x65\ +\x64\x56\x61\x6c\x03\x75\x65\x11\x01\x17\x51\x04\x01\x01\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x11\ +\x01\x4a\xff\x4d\x65\x74\x61\x64\x61\x74\x61\x00\x00\x00\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\ +\x00\x00\x11\x29\x4a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x31\ +\x32\x00\x00\x51\x2c\x03\x01\x51\x38\x02\x01\xff\x6d\x65\x74\x61\x64\x61\x74\x61\x00\x00\x00\x01\x10\ +\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x00\x01\x01\x10\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\ +\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\x31\x01\x92\x01\xff\x7a\x68\x69\x6e\x73\ +\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\ +\x6f\x6e\x2f\x65\x72\x72\x6f\x72\x2e\x63\x61\x70\x6e\x70\x3a\x45\x72\x72\x6f\x01\x72\x11\x01\x07\x50\ +\x01\x01\x31\x01\x1f\x01\x51\x14\x03\x04\x00\x00\x04\x01\x00\x00\x11\x7d\x2a\x00\x00\x51\x78\x03\x01\ +\x51\x84\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x81\x42\x00\x00\x51\x7c\x03\x01\x51\x88\x02\x01\x11\ +\x02\x01\x14\x01\x02\x00\x00\x11\x85\x4a\x00\x00\x51\x84\x03\x01\x51\x90\x02\x01\x11\x03\x02\x14\x01\ +\x03\x01\x01\x11\x8d\x2a\x00\x00\x51\x88\x03\x01\x51\x94\x02\x01\x11\x04\x02\x14\x01\x04\x00\x00\x11\ +\x91\x3a\x00\x00\x51\x8c\x03\x01\x51\x98\x02\x01\x0f\x63\x6f\x64\x65\x01\x08\x00\x02\x01\x08\x00\x01\ +\x7f\x6d\x65\x73\x73\x61\x67\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x61\x74\x65\x67\x6f\x72\x79\ +\x00\x00\x00\x01\x0c\x00\x02\x01\x0c\x00\x01\x0f\x6b\x69\x6e\x64\x01\x0f\xff\xbd\x02\x98\x4a\xe2\x71\ +\xe6\xb7\x00\x00\x01\x05\x0f\x02\x00\x01\x3f\x73\x6f\x75\x72\x63\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\ +\x31\x01\xe2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\ +\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\ +\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\ +\x63\x61\x74\x65\x64\x47\x65\x74\x56\x61\x6c\x75\x65\x73\x24\x52\x65\x73\x75\x07\x6c\x74\x73\x11\x01\ +\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\ +\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\ +\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\ +\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\ +\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\ +\x01\x92\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\ +\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\ +\x61\x6c\x75\x01\x65\x11\x01\x07\x50\x01\x01\x31\x01\x17\x03\x51\x38\x03\x04\x0c\xff\xff\x04\x01\x00\ +\x00\x13\x79\x01\x32\x00\x00\x53\x74\x01\x03\x01\x53\x80\x01\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\ +\x00\x13\x7d\x01\x3a\x00\x00\x53\x78\x01\x03\x01\x53\x84\x01\x02\x01\x0d\x02\xfd\xff\x14\x01\x02\x00\ +\x00\x13\x81\x01\x42\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\x01\x0d\x03\xfc\xff\x14\x01\x03\x00\ +\x00\x13\x85\x01\x3a\x00\x00\x53\x80\x01\x03\x01\x53\x8c\x01\x02\x01\x0d\x04\xfb\xff\x14\x01\x04\x00\ +\x00\x11\x02\x03\x00\x00\x11\x1a\x03\x11\x42\x03\x0d\x05\xfa\xff\x14\x01\x05\x00\x00\x11\x62\x03\x00\ +\x00\x11\x7a\x03\x11\xa2\x03\x0d\x06\xf9\xff\x14\x01\x06\x00\x00\x11\xc2\x03\x00\x00\x11\xda\x03\x13\ +\x02\x01\x03\x0d\x07\xf8\xff\x14\x01\x07\x00\x00\x13\x22\x01\x03\x00\x00\x13\x32\x01\x03\x13\x5a\x01\ +\x03\x0d\x08\xf7\xff\x14\x01\x08\x00\x00\x13\x7a\x01\x03\x00\x00\x13\x92\x01\x03\x13\xba\x01\x03\x0d\ +\x09\xf6\xff\x14\x01\x09\x00\x00\x13\xda\x01\x03\x00\x00\x13\xf2\x01\x03\x13\x1a\x02\x03\x0d\x0a\xf5\ +\xff\x14\x01\x0a\x00\x00\x13\x3a\x02\x03\x00\x00\x13\x5a\x02\x03\x13\x82\x02\x03\x0d\x0b\xf4\xff\x14\ +\x01\x0b\x00\x00\x13\xa2\x02\x03\x00\x00\x13\xc2\x02\x03\x13\x0a\x03\x03\x0d\x0c\xf3\xff\x14\x01\x0c\ +\x00\x00\x13\x2a\x03\x03\x00\x00\x13\x4a\x03\x03\x13\x92\x03\x03\x0d\x0d\xf2\xff\x14\x01\x0d\x00\x00\ +\x13\xb2\x03\x03\x00\x00\x13\xca\x03\x03\x13\xf2\x03\x03\x1f\x69\x6e\x74\x36\x34\x01\x05\x00\x02\x01\ +\x05\x00\x01\x3f\x64\x6f\x75\x62\x6c\x65\x01\x0b\x00\x02\x01\x0b\x00\x01\x7f\x63\x6f\x6d\x70\x6c\x65\ +\x78\x01\x10\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x00\x01\x01\x10\x00\x01\x3f\x73\x74\x72\x69\x6e\ +\x67\x01\x0c\x00\x02\x01\x0c\x00\x01\x11\x01\x5a\xff\x76\x65\x63\x74\x6f\x72\x44\x61\x00\x03\x74\x61\ +\x50\x03\x01\x01\x10\xff\x78\x89\xf3\x0d\xb8\x65\x4c\x99\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\ +\x01\x52\xff\x63\x6e\x74\x53\x61\x6d\x70\x6c\x00\x01\x65\x50\x03\x01\x01\x10\xff\x65\x60\x7d\x28\xd8\ +\x0b\x37\xe9\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\x72\xff\x74\x72\x69\x67\x67\x65\x72\x53\ +\x00\x1f\x61\x6d\x70\x6c\x65\x50\x03\x01\x01\x10\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x00\x01\x50\ +\x02\x01\x01\x10\x00\x01\x11\x01\x2a\x0f\x6e\x6f\x6e\x65\x50\x03\x01\x01\x10\xff\x16\x78\x43\x81\xe9\ +\xe8\x7f\xdf\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\x7a\xff\x73\x74\x72\x65\x61\x6d\x69\x6e\ +\x00\x3f\x67\x45\x72\x72\x6f\x72\x50\x03\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\ +\x50\x02\x01\x01\x10\x00\x01\x11\x01\x6a\xff\x73\x68\x66\x44\x65\x6d\x6f\x64\x00\x0f\x44\x61\x74\x61\ +\x50\x03\x01\x01\x10\xfb\x82\x65\xe6\xe3\xe3\x03\x9b\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\xa2\ +\xff\x73\x68\x66\x52\x65\x73\x75\x6c\x01\x74\x4c\x6f\x67\x67\x65\x72\x44\x07\x61\x74\x61\x50\x03\x01\ +\x01\x10\xff\xdd\x1d\x76\x79\xf5\x61\xa0\xbb\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\x8a\xff\ +\x76\x65\x63\x74\x6f\x72\x43\x6e\x01\x74\x53\x61\x6d\x70\x6c\x65\x73\x00\x00\x50\x03\x01\x01\x0e\x00\ +\x01\x50\x03\x01\x01\x10\xff\x65\x60\x7d\x28\xd8\x0b\x37\xe9\x00\x00\x01\x50\x02\x01\x01\x0e\x00\x01\ +\x11\x01\xaa\xff\x76\x65\x63\x74\x6f\x72\x54\x72\x01\x69\x67\x67\x65\x72\x53\x61\x6d\x0f\x70\x6c\x65\ +\x73\x50\x03\x01\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x00\x01\ +\x50\x02\x01\x01\x0e\x00\x01\x11\x01\x82\xff\x6c\x61\x72\x67\x65\x56\x65\x63\x01\x74\x6f\x72\x44\x61\ +\x74\x61\x00\x50\x03\x01\x01\x10\xff\x0a\xb0\x59\x99\xb0\xf7\x48\xd9\x00\x00\x01\x50\x02\x01\x01\x10\ +\x00\x01\x31\x01\x52\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x08\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\ +\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\ +\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x74\x75\x72\x6e\x46\x72\x6f\x6d\x53\ +\x65\x74\x57\x68\x65\x01\x6e\x11\x01\x07\x50\x01\x01\x11\x01\x67\x51\x10\x01\x02\x00\x00\x11\x29\x2a\ +\x00\x00\x01\x01\x11\x21\x52\x00\x00\x01\x02\x11\x1d\x62\x00\x00\x01\x03\x11\x19\xa2\x00\x00\x0f\x61\ +\x73\x61\x70\xff\x64\x65\x76\x69\x63\x65\x41\x63\x00\x01\x6b\xff\x75\x6e\x75\x73\x65\x64\x41\x73\x00\ +\x07\x79\x6e\x63\xff\x75\x6e\x75\x73\x65\x64\x54\x72\x01\x61\x6e\x73\x61\x63\x74\x69\x6f\x07\x6e\x61\ +\x6c\x31\x01\xd2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\ +\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\ +\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\ +\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\x61\x6d\x01\x73\x11\x01\xaf\ +\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x11\x01\ +\x01\x14\x01\x01\x00\x00\x11\x49\x32\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\x02\x14\x01\x02\x01\ +\x01\x11\x4d\x6a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\ +\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\ +\x01\xff\x63\x6f\x6d\x70\x6c\x65\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\ +\x2d\xdd\x00\x00\x01\x05\x0f\x01\x00\x01\x31\x01\xba\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\ +\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\ +\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\x6f\x69\x64\x3f\x53\x74\x72\x75\x63\x74\x11\x01\x07\x50\ +\x01\x01\x31\x01\xda\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\ +\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\ +\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\ +\x72\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x24\x52\x65\x73\x75\x6c\x03\x74\x73\x11\ +\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\ +\x3f\x72\x65\x73\x75\x6c\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\ +\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\ +\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x00\x01\ +\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\x02\x02\xff\x7a\x68\ +\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\ +\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x6e\x70\x3a\ +\x53\x75\x62\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x00\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\ +\x04\x00\x00\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x11\x01\x01\x14\x01\ +\x01\x00\x00\x11\x49\x82\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x02\x14\x01\x02\x00\x00\x11\ +\x51\x6a\x00\x00\x51\x50\x03\x01\x51\x5c\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\ +\xff\x73\x74\x72\x65\x61\x6d\x69\x6e\x01\x67\x48\x61\x6e\x64\x6c\x65\x00\x01\x11\xff\x74\x15\xb4\xf5\ +\xa7\x28\x1e\xf5\x00\x00\x01\x01\x11\x00\x01\xff\x73\x75\x62\x73\x63\x72\x69\x62\x00\x0f\x65\x72\x49\ +\x64\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\ +\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\ +\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\ +\x69\x6f\x6e\x2e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x00\x11\x01\x3f\ +\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x6a\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x73\ +\x75\x62\x73\x63\x72\x69\x70\x00\x0f\x74\x69\x6f\x6e\x01\x10\xff\xd4\xb1\xe1\x3d\xa5\x21\xac\xed\x00\ +\x00\x01\x01\x10\x00\x01\x31\x01\x92\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\ +\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\ +\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\ +\x2e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x52\x65\x73\x75\x6c\x74\x01\x73\x11\x01\x3f\x51\x04\x03\ +\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\x3f\x72\x65\x73\x75\ +\x6c\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\ +\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\ +\x01\x01\x51\x10\x03\x01\x01\x10\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x00\x01\x01\x10\xff\xd9\x11\ +\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\xc2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\ +\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\ +\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\ +\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x69\x73\x63\x6f\x6e\x6e\x65\x63\x74\x44\x65\x76\x69\x63\x65\x24\ +\x50\x61\x72\x61\x6d\x73\x00\x31\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\ +\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\ +\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\ +\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x4a\x73\x6f\x6e\x24\x50\x61\x0f\x72\x61\x6d\x73\x11\x01\ +\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x7a\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\ +\x01\x14\x01\x01\x00\x00\x11\x4d\x32\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\ +\x00\x00\x11\x51\x3a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\ +\x3f\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\ +\x01\x08\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\xb2\x02\xff\x7a\ +\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\ +\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\ +\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x4a\x73\ +\x6f\x6e\x24\x52\x65\x1f\x73\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\ +\x0d\x52\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x6e\x6f\x64\x65\x50\x72\x6f\x70\x00\x01\x73\x01\ +\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\x9a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\ +\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\ +\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\ +\x6e\x2e\x75\x6e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\x77\x51\ +\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x6a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x11\x01\x01\ +\x14\x01\x01\x00\x00\x11\x31\x32\x00\x00\x51\x2c\x03\x01\x51\x48\x02\x01\xff\x73\x75\x62\x73\x63\x72\ +\x69\x62\x00\x0f\x65\x72\x49\x64\x01\x0d\x00\x02\x01\x0d\x00\x01\x1f\x70\x61\x74\x68\x73\x01\x0e\x00\ +\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\x31\x01\xca\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\ +\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\ +\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\ +\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x53\x65\x73\x73\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\ +\x50\x61\x72\x61\x6d\x73\x00\x00\x31\x01\xd2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\ +\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\ +\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\ +\x6f\x6e\x2e\x67\x65\x74\x53\x65\x73\x73\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x52\x65\x73\x75\ +\x6c\x74\x01\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x42\x00\x00\x51\x08\x03\ +\x01\x51\x14\x02\x01\x7f\x76\x65\x72\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\xda\x02\ +\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\ +\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\ +\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\ +\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x32\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\xe7\x51\x10\x03\x04\ +\x00\x00\x04\x01\x00\x00\x11\x61\x2a\x00\x00\x51\x5c\x03\x01\x51\x68\x02\x01\x11\x01\x01\x14\x01\x01\ +\x00\x00\x11\x65\x32\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x01\x02\x14\x01\x02\x01\x01\x11\x69\x6a\ +\x00\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x03\x02\x14\x01\x03\x01\x01\x11\x71\x3a\x00\x00\x51\x6c\ +\x03\x01\x51\x78\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x76\x61\x6c\x75\x65\ +\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\xff\x63\x6f\x6d\x70\x6c\x65\ +\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x00\x01\x05\x0f\x01\ +\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\xe2\x02\xff\ +\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\ +\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\ +\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\ +\x53\x65\x74\x56\x61\x6c\x75\x65\x32\x24\x52\x65\x73\x75\x07\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\ +\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\x3f\x72\x65\x73\x75\x6c\ +\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\ +\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\ +\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\ +\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\x1a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\ +\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\ +\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x4c\ +\x6f\x6f\x6b\x75\x70\x4d\x6f\x03\x64\x65\x11\x01\x07\x50\x01\x01\x11\x01\x37\x51\x08\x01\x02\x00\x00\ +\x11\x11\x6a\x00\x00\x01\x01\x11\x0d\x72\x00\x00\xff\x64\x69\x72\x65\x63\x74\x4c\x6f\x00\x0f\x6f\x6b\ +\x75\x70\xff\x77\x69\x74\x68\x45\x78\x70\x61\x00\x1f\x6e\x73\x69\x6f\x6e\x31\x01\x82\x02\xff\x7a\x68\ +\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\ +\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\ +\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\ +\x61\x6d\x73\x00\x31\x01\x1f\x01\x51\x14\x03\x04\x00\x00\x04\x01\x00\x00\x11\x7d\x7a\x00\x00\x51\x7c\ +\x03\x01\x51\x88\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x85\x32\x00\x00\x51\x80\x03\x01\x51\x8c\ +\x02\x01\x01\x02\x14\x01\x02\x01\x01\x11\x89\x5a\x00\x00\x51\x88\x03\x01\x51\x94\x02\x01\x11\x03\x01\ +\x14\x01\x03\x01\x01\x11\x91\x6a\x00\x00\x51\x90\x03\x01\x51\x9c\x02\x01\x11\x04\x02\x14\x01\x04\x01\ +\x01\x11\x99\x3a\x00\x00\x51\x94\x03\x01\x51\xa0\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\ +\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\ +\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\xff\x6c\x6f\x6f\x6b\x75\x70\x4d\x6f\x00\x03\x64\ +\x65\x01\x0f\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x00\x01\x01\x0f\x00\x01\xff\x63\x6f\x6d\x70\x6c\ +\x65\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x00\x01\x05\x0f\ +\x01\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\x8a\x02\ +\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\ +\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\ +\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x65\x74\x56\x61\x6c\x75\x65\x24\ +\x52\x65\x73\x75\x6c\x74\x73\x00\x00\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\ +\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\x01\ +\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\ +\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\ +\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\ +\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x82\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\ +\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\ +\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\ +\x73\x69\x6f\x6e\x2e\x67\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x11\x01\xe7\x51\ +\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x7a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x01\x01\x14\ +\x01\x01\x01\x01\x11\x69\x5a\x00\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x02\x01\x14\x01\x02\x01\x01\ +\x11\x71\x32\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x11\x03\x01\x14\x01\x03\x01\x01\x11\x75\x3a\x00\ +\x00\x51\x70\x03\x01\x51\x7c\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\x69\x6f\ +\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x6c\x6f\x6f\x6b\x75\x70\x4d\x6f\x00\x03\x64\x65\x01\x0f\xff\ +\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x00\x01\x01\x0f\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\ +\x01\x08\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\x8a\ +\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\ +\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\ +\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x56\x61\x6c\x75\x65\ +\x24\x52\x65\x73\x75\x6c\x74\x73\x00\x00\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\ +\x3a\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\ +\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\ +\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\ +\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\ +\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x22\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\ +\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\ +\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\x6f\x74\x61\x74\x65\x64\x56\x61\x6c\x75\x65\ +\x2e\x4d\x65\x74\x61\x64\x07\x61\x74\x61\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\ +\x04\x01\x00\x00\x11\x29\x52\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\ +\x31\x2a\x00\x00\x51\x2c\x03\x01\x51\x38\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\ +\x09\x00\x02\x01\x09\x00\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\xb2\x01\xff\ +\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\ +\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x63\x6f\x6d\x70\x6c\x65\x78\x2e\x63\x61\x70\x6e\x70\x3a\x43\x6f\x1f\ +\x6d\x70\x6c\x65\x78\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\ +\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x2a\x00\x00\ +\x51\x28\x03\x01\x51\x34\x02\x01\x0f\x72\x65\x61\x6c\x01\x0b\x00\x02\x01\x0b\x00\x01\x0f\x69\x6d\x61\ +\x67\x01\x0b\x00\x02\x01\x0b\x00\x01\x31\x01\xba\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\ +\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\ +\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\x65\x63\x74\x3f\x6f\x72\x44\x61\x74\x61\x11\x01\x07\x50\x01\ +\x01\x11\x01\xe7\x51\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x52\x00\x00\x51\x60\x03\x01\x51\x6c\ +\x02\x01\x11\x01\x02\x14\x01\x01\x00\x00\x11\x69\x92\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x11\x02\ +\x01\x14\x01\x02\x00\x00\x11\x75\x82\x00\x00\x51\x74\x03\x01\x51\x80\x02\x01\x01\x03\x14\x01\x03\x00\ +\x00\x11\x7d\x2a\x00\x00\x51\x78\x03\x01\x51\x84\x02\x01\xff\x76\x61\x6c\x75\x65\x54\x79\x70\x00\x01\ +\x65\x01\x07\x00\x02\x01\x07\x00\x01\xff\x76\x65\x63\x74\x6f\x72\x45\x6c\x01\x65\x6d\x65\x6e\x74\x54\ +\x79\x70\x01\x65\x01\x06\x00\x02\x01\x06\x00\x01\xff\x65\x78\x74\x72\x61\x48\x65\x61\x01\x64\x65\x72\ +\x49\x6e\x66\x6f\x00\x01\x08\x00\x02\x01\x08\x00\x01\x0f\x64\x61\x74\x61\x01\x0d\x00\x02\x01\x0d\x00\ +\x01\x31\x01\xb2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\ +\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\ +\x3a\x43\x6e\x74\x53\x1f\x61\x6d\x70\x6c\x65\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\ +\x00\x04\x01\x00\x00\x11\x45\x52\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x11\x01\x02\x14\x01\x01\x00\ +\x00\x11\x4d\x42\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x03\x14\x01\x02\x00\x00\x11\x51\x42\ +\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\ +\x02\x01\x09\x00\x01\x7f\x63\x6f\x75\x6e\x74\x65\x72\x01\x04\x00\x02\x01\x04\x00\x01\x7f\x74\x72\x69\ +\x67\x67\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\x31\x01\xd2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\ +\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\ +\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x54\x72\x69\x67\x67\x65\x72\x53\x61\x6d\x70\x6c\x01\ +\x65\x11\x01\x07\x50\x01\x01\x31\x01\x8f\x01\x51\x1c\x03\x04\x00\x00\x04\x01\x00\x00\x11\xb5\x52\x00\ +\x00\x51\xb4\x03\x01\x51\xc0\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\xbd\x5a\x00\x00\x51\xbc\x03\ +\x01\x51\xc8\x02\x01\x11\x02\x04\x14\x01\x02\x00\x00\x11\xc5\x42\x00\x00\x51\xc0\x03\x01\x51\xcc\x02\ +\x01\x11\x03\x05\x14\x01\x03\x00\x00\x11\xc9\x7a\x00\x00\x51\xc8\x03\x01\x51\xd4\x02\x01\x11\x04\x06\ +\x14\x01\x04\x00\x00\x11\xd1\x5a\x00\x00\x51\xd0\x03\x01\x51\xdc\x02\x01\x11\x05\x07\x14\x01\x05\x00\ +\x00\x11\xd9\x22\x00\x00\x51\xd4\x03\x01\x51\xe0\x02\x01\x11\x06\x08\x14\x01\x06\x00\x00\x11\xdd\x72\ +\x00\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\ +\x02\x01\x09\x00\x01\xff\x73\x61\x6d\x70\x6c\x65\x54\x69\x00\x03\x63\x6b\x01\x09\x00\x02\x01\x09\x00\ +\x01\x7f\x74\x72\x69\x67\x67\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\xff\x6d\x69\x73\x73\x65\x64\x54\ +\x72\x00\x3f\x69\x67\x67\x65\x72\x73\x01\x08\x00\x02\x01\x08\x00\x01\xff\x61\x77\x67\x54\x72\x69\x67\ +\x67\x00\x03\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\x07\x64\x69\x6f\x01\x08\x00\x02\x01\x08\x00\x01\ +\xff\x73\x65\x71\x75\x65\x6e\x63\x65\x00\x1f\x49\x6e\x64\x65\x78\x01\x08\x00\x02\x01\x08\x00\x01\x31\ +\x01\x5a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x08\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\ +\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x73\x68\x66\x5f\x76\x65\x63\x74\x6f\x72\x73\x2e\x63\ +\x61\x70\x6e\x70\x3a\x53\x68\x66\x44\x65\x6d\x6f\x64\x75\x6c\x61\x74\x6f\x72\x56\x65\x63\x74\x6f\x72\ +\x44\x61\x03\x74\x61\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x01\x01\xff\x0d\x30\ +\x5c\xa2\x6c\x78\x75\xd4\x00\x11\x45\x5a\x00\x02\x01\x01\x14\x01\x0e\x00\x00\x11\x31\x12\x00\x00\x51\ +\x2c\x03\x01\x51\x48\x02\x01\x11\x02\x01\x14\x01\x0f\x00\x00\x11\x45\x12\x00\x00\x51\x40\x03\x01\x51\ +\x5c\x02\x01\xff\x70\x72\x6f\x70\x65\x72\x74\x69\x00\x03\x65\x73\x01\x78\x01\x0e\x00\x01\x50\x03\x01\ +\x01\x0b\x00\x02\x01\x0e\x00\x01\x01\x79\x01\x0e\x00\x01\x50\x03\x01\x01\x0b\x00\x02\x01\x0e\x00\x01\ +\x31\x01\x62\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x08\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\ +\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x73\x68\x66\x5f\x76\x65\x63\x74\x6f\x72\x73\x2e\ +\x63\x61\x70\x6e\x70\x3a\x53\x68\x66\x52\x65\x73\x75\x6c\x74\x4c\x6f\x67\x67\x65\x72\x56\x65\x63\x74\ +\x6f\x72\x44\x07\x61\x74\x61\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x01\x01\xff\ +\x5e\xec\x0e\xea\x21\xb4\xb7\xdd\x00\x11\x29\x5a\x00\x02\x01\x01\x01\x01\xff\x12\xdf\x9b\x44\xb5\x68\ +\x0b\x98\x00\x11\x15\x3a\x00\x02\xff\x70\x72\x6f\x70\x65\x72\x74\x69\x00\x03\x65\x73\x3f\x76\x65\x63\ +\x74\x6f\x72\x31\x01\xe2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\ +\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\ +\x6e\x70\x3a\x4c\x61\x72\x67\x65\x56\x65\x63\x74\x6f\x72\x44\x07\x61\x74\x61\x11\x01\x07\x50\x01\x01\ +\x11\x01\xe7\x51\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x52\x00\x00\x51\x60\x03\x01\x51\x6c\x02\ +\x01\x11\x01\x02\x14\x01\x01\x00\x00\x11\x69\x92\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x11\x02\x01\ +\x14\x01\x02\x00\x00\x11\x75\x82\x00\x00\x51\x74\x03\x01\x51\x80\x02\x01\x01\x03\x14\x01\x03\x00\x00\ +\x11\x7d\x6a\x00\x00\x51\x7c\x03\x01\x51\x98\x02\x01\xff\x76\x61\x6c\x75\x65\x54\x79\x70\x00\x01\x65\ +\x01\x07\x00\x02\x01\x07\x00\x01\xff\x76\x65\x63\x74\x6f\x72\x45\x6c\x01\x65\x6d\x65\x6e\x74\x54\x79\ +\x70\x01\x65\x01\x06\x00\x02\x01\x06\x00\x01\xff\x65\x78\x74\x72\x61\x48\x65\x61\x01\x64\x65\x72\x49\ +\x6e\x66\x6f\x00\x01\x08\x00\x02\x01\x08\x00\x01\xff\x64\x61\x74\x61\x53\x65\x67\x6d\x00\x0f\x65\x6e\ +\x74\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x0d\x00\x02\x01\x0e\x00\x01\x31\x01\xb2\x01\xff\x7a\x68\x69\ +\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\ +\x6d\x6d\x6f\x6e\x2f\x65\x72\x72\x6f\x72\x2e\x63\x61\x70\x6e\x70\x3a\x45\x72\x72\x6f\x1f\x72\x4b\x69\ +\x6e\x64\x11\x01\x07\x50\x01\x01\x11\x01\xf7\x51\x28\x01\x02\x00\x00\x11\x71\x1a\x00\x00\x01\x01\x11\ +\x69\x52\x00\x00\x01\x02\x11\x65\x42\x00\x00\x01\x03\x11\x5d\x4a\x00\x00\x01\x04\x11\x59\x62\x00\x00\ +\x01\x05\x11\x55\x5a\x00\x00\x01\x06\x11\x51\x72\x00\x00\x01\x07\x11\x4d\x4a\x00\x00\x01\x08\x11\x49\ +\x62\x00\x00\x01\x09\x11\x45\x42\x00\x00\x03\x6f\x6b\xff\x63\x61\x6e\x63\x65\x6c\x6c\x65\x00\x01\x64\ +\x7f\x75\x6e\x6b\x6e\x6f\x77\x6e\xff\x6e\x6f\x74\x46\x6f\x75\x6e\x64\x00\x00\x00\xff\x6f\x76\x65\x72\ +\x77\x68\x65\x6c\x00\x07\x6d\x65\x64\xff\x62\x61\x64\x52\x65\x71\x75\x65\x00\x03\x73\x74\xff\x75\x6e\ +\x69\x6d\x70\x6c\x65\x6d\x00\x1f\x65\x6e\x74\x65\x64\xff\x69\x6e\x74\x65\x72\x6e\x61\x6c\x00\x00\x00\ +\xff\x75\x6e\x61\x76\x61\x69\x6c\x61\x00\x07\x62\x6c\x65\x7f\x74\x69\x6d\x65\x6f\x75\x74\x31\x01\xb2\ +\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\ +\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x73\x68\x66\x5f\x76\x65\x63\x74\x6f\x72\x73\x2e\x63\x61\x70\ +\x6e\x70\x3a\x53\x68\x66\x44\x65\x6d\x6f\x64\x75\x6c\x61\x74\x6f\x72\x56\x65\x63\x74\x6f\x72\x44\x61\ +\x74\x61\x2e\x70\x72\x6f\x70\x65\x1f\x72\x74\x69\x65\x73\x31\x01\x17\x03\x51\x38\x03\x04\x00\x00\x04\ +\x01\x00\x00\x13\x79\x01\x52\x00\x00\x53\x78\x01\x03\x01\x53\x84\x01\x02\x01\x11\x01\x01\x14\x01\x01\ +\x00\x00\x13\x81\x01\x1a\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\x01\x11\x02\x04\x14\x01\x02\x00\ +\x00\x13\x85\x01\x62\x00\x00\x53\x84\x01\x03\x01\x53\x90\x01\x02\x01\x11\x03\x05\x14\x01\x03\x00\x00\ +\x13\x8d\x01\x62\x00\x00\x53\x8c\x01\x03\x01\x53\x98\x01\x02\x01\x11\x04\x06\x14\x01\x04\x00\x00\x13\ +\x95\x01\x6a\x00\x00\x53\x94\x01\x03\x01\x53\xa0\x01\x02\x01\x11\x05\x04\x14\x01\x05\x00\x00\x13\x9d\ +\x01\x8a\x00\x00\x53\xa0\x01\x03\x01\x53\xac\x01\x02\x01\x11\x06\x05\x14\x01\x06\x00\x00\x13\xa9\x01\ +\x5a\x00\x00\x53\xa8\x01\x03\x01\x53\xb4\x01\x02\x01\x11\x07\xe0\x14\x01\x07\x00\x00\x13\xb1\x01\x3a\ +\x00\x00\x53\xac\x01\x03\x01\x53\xb8\x01\x02\x01\x11\x08\x0f\x14\x01\x08\x00\x00\x13\xb5\x01\x8a\x00\ +\x00\x53\xb8\x01\x03\x01\x53\xc4\x01\x02\x01\x11\x09\x18\x14\x01\x09\x00\x00\x13\xc1\x01\x4a\x00\x00\ +\x53\xc0\x01\x03\x01\x53\xcc\x01\x02\x01\x11\x0a\x1d\x14\x01\x0a\x00\x00\x13\xc9\x01\x72\x00\x00\x53\ +\xc8\x01\x03\x01\x53\xd4\x01\x02\x01\x11\x0b\x19\x14\x01\x0b\x00\x00\x13\xd1\x01\x6a\x00\x00\x53\xd0\ +\x01\x03\x01\x53\xdc\x01\x02\x01\x11\x0c\x07\x14\x01\x0c\x00\x00\x13\xd9\x01\x7a\x00\x00\x53\xd8\x01\ +\x03\x01\x53\xe4\x01\x02\x01\x11\x0d\x08\x14\x01\x0d\x00\x00\x13\xe1\x01\x42\x00\x00\x53\xdc\x01\x03\ +\x01\x53\xe8\x01\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\x02\x01\x09\x00\ +\x01\x03\x64\x74\x01\x09\x00\x02\x01\x09\x00\x01\xff\x62\x75\x72\x73\x74\x4c\x65\x6e\x00\x07\x67\x74\ +\x68\x01\x08\x00\x02\x01\x08\x00\x01\xff\x62\x75\x72\x73\x74\x4f\x66\x66\x00\x07\x73\x65\x74\x01\x08\ +\x00\x02\x01\x08\x00\x01\xff\x74\x72\x69\x67\x67\x65\x72\x49\x00\x0f\x6e\x64\x65\x78\x01\x08\x00\x02\ +\x01\x08\x00\x01\xff\x74\x72\x69\x67\x67\x65\x72\x54\x01\x69\x6d\x65\x73\x74\x61\x6d\x70\x00\x00\x01\ +\x09\x00\x02\x01\x09\x00\x01\xff\x63\x65\x6e\x74\x65\x72\x46\x72\x00\x03\x65\x71\x01\x0b\x00\x02\x01\ +\x0b\x00\x01\x3f\x72\x66\x50\x61\x74\x68\x01\x01\x00\x02\x01\x01\x00\x01\xff\x6f\x73\x63\x69\x6c\x6c\ +\x61\x74\x01\x6f\x72\x53\x6f\x75\x72\x63\x65\x00\x00\x01\x07\x00\x02\x01\x07\x00\x01\xff\x68\x61\x72\ +\x6d\x6f\x6e\x69\x63\x00\x00\x00\x01\x07\x00\x02\x01\x07\x00\x01\xff\x74\x72\x69\x67\x67\x65\x72\x53\ +\x00\x1f\x6f\x75\x72\x63\x65\x01\x06\x00\x02\x01\x06\x00\x01\xff\x73\x69\x67\x6e\x61\x6c\x53\x6f\x00\ +\x0f\x75\x72\x63\x65\x01\x07\x00\x02\x01\x07\x00\x01\xff\x6f\x73\x63\x69\x6c\x6c\x61\x74\x00\x3f\x6f\ +\x72\x46\x72\x65\x71\x01\x0b\x00\x02\x01\x0b\x00\x01\x7f\x73\x63\x61\x6c\x69\x6e\x67\x01\x0b\x00\x02\ +\x01\x0b\x00\x01\x31\x01\xba\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\ +\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x73\x68\x66\x5f\x76\x65\x63\x74\ +\x6f\x72\x73\x2e\x63\x61\x70\x6e\x70\x3a\x53\x68\x66\x52\x65\x73\x75\x6c\x74\x4c\x6f\x67\x67\x65\x72\ +\x56\x65\x63\x74\x6f\x72\x44\x61\x74\x61\x2e\x70\x72\x6f\x70\x3f\x65\x72\x74\x69\x65\x73\x31\x01\x17\ +\x03\x51\x38\x03\x04\x00\x00\x04\x01\x00\x00\x13\x79\x01\x52\x00\x00\x53\x78\x01\x03\x01\x53\x84\x01\ +\x02\x01\x11\x01\x02\x14\x01\x01\x00\x00\x13\x81\x01\x32\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\ +\x01\x11\x02\x03\x14\x01\x02\x00\x00\x13\x85\x01\x6a\x00\x00\x53\x84\x01\x03\x01\x53\x90\x01\x02\x01\ +\x11\x03\x02\x14\x01\x03\x00\x00\x13\x8d\x01\x42\x00\x00\x53\x88\x01\x03\x01\x53\x94\x01\x02\x01\x11\ +\x04\x03\x14\x01\x04\x00\x00\x13\x91\x01\x82\x00\x00\x53\x90\x01\x03\x01\x53\x9c\x01\x02\x01\x11\x05\ +\x08\x14\x01\x05\x00\x00\x13\x99\x01\x5a\x00\x00\x53\x98\x01\x03\x01\x53\xa4\x01\x02\x01\x11\x06\x09\ +\x14\x01\x06\x00\x00\x13\xa1\x01\x5a\x00\x00\x53\xa0\x01\x03\x01\x53\xac\x01\x02\x01\x11\x07\x0a\x14\ +\x01\x07\x00\x00\x13\xa9\x01\x8a\x00\x00\x53\xac\x01\x03\x01\x53\xb8\x01\x02\x01\x11\x08\x0b\x14\x01\ +\x08\x00\x00\x13\xb5\x01\x62\x00\x00\x53\xb4\x01\x03\x01\x53\xc0\x01\x02\x01\x11\x09\x0c\x14\x01\x09\ +\x00\x00\x13\xbd\x01\x62\x00\x00\x53\xbc\x01\x03\x01\x53\xc8\x01\x02\x01\x11\x0a\x1a\x14\x01\x0a\x00\ +\x00\x13\xc5\x01\xa2\x00\x00\x53\xc8\x01\x03\x01\x53\xd4\x01\x02\x01\x11\x0b\x1b\x14\x01\x0b\x00\x00\ +\x13\xd1\x01\xaa\x00\x00\x53\xd4\x01\x03\x01\x53\xe0\x01\x02\x01\x11\x0c\x1c\x14\x01\x0c\x00\x00\x13\ +\xdd\x01\xa2\x00\x00\x53\xe0\x01\x03\x01\x53\xec\x01\x02\x01\x11\x0d\x08\x14\x01\x0d\x00\x00\x13\xe9\ +\x01\xaa\x00\x00\x53\xec\x01\x03\x01\x53\xf8\x01\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\ +\x70\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x6a\x6f\x62\x49\x64\x01\x08\x00\x02\x01\x08\x00\x01\xff\x72\ +\x65\x70\x65\x74\x69\x74\x69\x00\x0f\x6f\x6e\x49\x64\x01\x08\x00\x02\x01\x08\x00\x01\x7f\x73\x63\x61\ +\x6c\x69\x6e\x67\x01\x0b\x00\x02\x01\x0b\x00\x01\xff\x63\x65\x6e\x74\x65\x72\x46\x72\x01\x65\x71\x75\ +\x65\x6e\x63\x79\x00\x01\x0b\x00\x02\x01\x0b\x00\x01\xff\x64\x61\x74\x61\x53\x6f\x75\x72\x00\x03\x63\ +\x65\x01\x08\x00\x02\x01\x08\x00\x01\xff\x6e\x75\x6d\x53\x61\x6d\x70\x6c\x00\x03\x65\x73\x01\x08\x00\ +\x02\x01\x08\x00\x01\xff\x6e\x75\x6d\x53\x70\x65\x63\x74\x01\x72\x53\x61\x6d\x70\x6c\x65\x73\x00\x00\ +\x01\x08\x00\x02\x01\x08\x00\x01\xff\x6e\x75\x6d\x41\x76\x65\x72\x61\x00\x07\x67\x65\x73\x01\x08\x00\ +\x02\x01\x08\x00\x01\xff\x6e\x75\x6d\x41\x63\x71\x75\x69\x00\x07\x72\x65\x64\x01\x08\x00\x02\x01\x08\ +\x00\x01\xff\x68\x6f\x6c\x64\x6f\x66\x66\x45\x01\x72\x72\x6f\x72\x73\x52\x65\x73\x07\x6c\x6f\x67\x01\ +\x07\x00\x02\x01\x07\x00\x01\xff\x68\x6f\x6c\x64\x6f\x66\x66\x45\x01\x72\x72\x6f\x72\x73\x52\x65\x61\ +\x0f\x64\x6f\x75\x74\x01\x07\x00\x02\x01\x07\x00\x01\xff\x68\x6f\x6c\x64\x6f\x66\x66\x45\x01\x72\x72\ +\x6f\x72\x73\x53\x70\x65\x07\x63\x74\x72\x01\x07\x00\x02\x01\x07\x00\x01\xff\x66\x69\x72\x73\x74\x53\ +\x61\x6d\x01\x70\x6c\x65\x54\x69\x6d\x65\x73\x0f\x74\x61\x6d\x70\x01\x09\x00\x02\x01\x09\x00\x01\x31\ +\x01\x9a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\ +\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x73\x68\x66\x5f\x76\x65\x63\x74\x6f\x72\x73\x2e\x63\ +\x61\x70\x6e\x70\x3a\x53\x68\x66\x52\x65\x73\x75\x6c\x74\x4c\x6f\x67\x67\x65\x72\x56\x65\x63\x74\x6f\ +\x72\x44\x61\x74\x61\x2e\x76\x65\x63\x74\x03\x6f\x72\x11\x01\x77\x51\x08\x03\x04\x0c\xff\xff\x14\x01\ +\x0e\x00\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x40\x02\x01\x0d\x01\xfe\xff\x14\x01\x0f\x00\x00\ +\x11\x3d\x42\x00\x00\x51\x38\x03\x01\x51\x54\x02\x01\x0f\x72\x65\x61\x6c\x01\x0e\x00\x01\x50\x03\x01\ +\x01\x0b\x00\x02\x01\x0e\x00\x01\x7f\x63\x6f\x6d\x70\x6c\x65\x78\x01\x0e\x00\x01\x50\x03\x01\x01\x10\ +\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x1a\x02\xff\x7a\x68\x69\x6e\ +\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\ +\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x6e\x70\x3a\x53\x74\ +\x72\x65\x61\x6d\x69\x6e\x67\x48\x61\x6e\x64\x03\x6c\x65\x11\x01\x07\x50\x01\x01\x11\x01\x47\x51\x04\ +\x03\x05\x00\x00\xff\xb6\x47\x7c\x96\x0f\xad\x0b\xa3\x01\x6e\xb1\xc0\x77\x33\x9a\x5f\x99\x11\x11\x5a\ +\x00\x02\x11\x09\x07\xff\x73\x65\x6e\x64\x56\x61\x6c\x75\x00\x03\x65\x73\x40\x01\x11\x01\x07\x50\x01\ +\x01\x31\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\ +\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\ +\x67\x2e\x63\x61\x70\x6e\x70\x3a\x53\x74\x72\x65\x61\x6d\x69\x6e\x67\x48\x61\x6e\x64\x6c\x65\x2e\x73\ +\x65\x6e\x64\x56\x61\x6c\x75\x65\x73\x24\x50\x61\x0f\x72\x61\x6d\x73\x11\x01\x3f\x51\x04\x03\x04\x00\ +\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x24\x02\x01\x3f\x76\x61\x6c\x75\x65\x73\ +\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x0e\x00\x01\ +\x31\x01\x02\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x74\x03\x72\x65\x61\x6d\x2e\x63\x61\x70\x6e\x70\x3a\ +\x53\x74\x72\x65\x61\x6d\x52\x65\x73\x75\x6c\x74\x00\x11\x01\x07\x50\x01\x01" + + +_SCHEMA_ID = 0xA621130A90860008 + +_SCHEMA_LOADER = None + + +def get_schema_loader() -> zhinst.comms.SchemaLoader: + """Get the schema loader for the HPK schema. + + The schema is cached since it is expensive to load and there is no need to + load it multiple times. + + Returns: + The schema loader for the HPK schema. + """ + global _SCHEMA_LOADER # noqa: PLW0603 + if _SCHEMA_LOADER is None: + _SCHEMA_LOADER = zhinst.comms.SchemaLoader(_SCHEMA_ID, _CAPNP_BINARY_SCHEMA) + return _SCHEMA_LOADER diff --git a/src/labone/core/kernel_session.py b/src/labone/core/kernel_session.py index 6590147..e134142 100644 --- a/src/labone/core/kernel_session.py +++ b/src/labone/core/kernel_session.py @@ -5,7 +5,7 @@ can also be a kernel that provides additional functionality, e.g. the Data Server (ZI) kernel. -Every Kernel provides the same capnp interface and can therefore be handled +Every Kernel provides the same interface and can therefore be handled in the same way. The only difference is the set of nodes that are available on the kernel. @@ -16,45 +16,43 @@ from __future__ import annotations -import typing as t +from dataclasses import dataclass -import capnp +import zhinst.comms +from typing_extensions import TypeAlias -from labone.core.connection_layer import ( - KernelInfo, - ServerInfo, - create_session_client_stream, -) -from labone.core.errors import LabOneCoreError, UnavailableError -from labone.core.helper import ( - ensure_capnp_event_loop, -) -from labone.core.reflection.server import ReflectionServer +from labone.core import hpk_schema +from labone.core.errors import async_translate_comms_error +from labone.core.helper import ZIContext, get_default_context from labone.core.session import Session -if t.TYPE_CHECKING: - from labone.core.errors import ( # noqa: F401 - BadRequestError, - InternalError, - ) +KernelInfo: TypeAlias = zhinst.comms.DestinationParams + + +@dataclass(frozen=True) +class ServerInfo: + """Information about a server.""" + + host: str + port: int class KernelSession(Session): """Session to a LabOne kernel. Representation of a single session to a LabOne kernel. This class - encapsulates the capnp interaction and exposes a Python native API. + encapsulates the labone interaction and exposes a Python native API. All functions are exposed as they are implemented in the kernel - interface and are directly forwarded to the kernel through capnp. + interface and are directly forwarded to the kernel. Each function implements the required error handling both for the - capnp communication and the server errors. This means unless an Exception + socket communication and the server errors. This means unless an Exception is raised the call was successful. The KernelSession class is instantiated through the staticmethod `create()`. This is due to the fact that the instantiation is done asynchronously. - To call the constructor directly an already existing capnp io stream + To call the constructor directly an already existing connection must be provided. !!! note @@ -63,7 +61,7 @@ class KernelSession(Session): `create` instead of the `__init__` method. ```python - kernel_info = ZIKernelInfo() + kernel_info = ZIContext.zi_connection() server_info = ServerInfo(host="localhost", port=8004) kernel_session = await KernelSession( kernel_info = kernel_info, @@ -72,29 +70,29 @@ class KernelSession(Session): ``` Args: - reflection_server: The reflection server that is used for the session. + core_session: The underlying zhinst.comms session. + context: The context in which the session is running. kernel_info: Information about the target kernel. server_info: Information about the target data server. """ def __init__( self, - reflection_server: ReflectionServer, - kernel_info: KernelInfo, + core_session: zhinst.comms.DynamicClient, + *, + context: ZIContext, server_info: ServerInfo, ) -> None: - super().__init__( - reflection_server.session, # type: ignore[attr-defined] - reflection_server=reflection_server, - ) - self._kernel_info = kernel_info + super().__init__(core_session, context=context) self._server_info = server_info @staticmethod + @async_translate_comms_error async def create( *, kernel_info: KernelInfo, server_info: ServerInfo, + context: ZIContext | None = None, ) -> KernelSession: """Create a new session to a LabOne kernel. @@ -102,15 +100,12 @@ async def create( is required, instead of a simple constructor (since a constructor can not be asynchronous). - !!! warning - - The initial socket creation and setup (handshake, ...) is - currently not done asynchronously! The reason is that there is not - easy way of doing this with the current capnp implementation. - Args: kernel_info: Information about the target kernel. server_info: Information about the target data server. + context: Context in which the session should run. If not provided + the default context will be used which is in most cases the + desired behavior. Returns: A new session to the specified kernel. @@ -123,36 +118,19 @@ async def create( error occurred. LabOneCoreError: If another error happens during the session creation. """ - sock, kernel_info_extended, server_info_extended = create_session_client_stream( - kernel_info=kernel_info, - server_info=server_info, + if context is None: + context = get_default_context() + core_session = await context.connect_labone( + server_info.host, + server_info.port, + kernel_info, + schema=hpk_schema.get_schema_loader(), ) - await ensure_capnp_event_loop() - connection = await capnp.AsyncIoStream.create_connection(sock=sock) - try: - reflection_server = await ReflectionServer.create_from_connection( - connection, - ) - except LabOneCoreError as e: - msg = str( - f"Unable to connect to the kernel at {kernel_info.name}" - f"{kernel_info.query} ({server_info.host}:" - f"{server_info.port}). (extended information: {e})", - ) - raise UnavailableError(msg) from e - - session = KernelSession( - reflection_server=reflection_server, - kernel_info=kernel_info_extended, - server_info=server_info_extended, + return KernelSession( + core_session, + context=context, + server_info=server_info, ) - await session.ensure_compatibility() - return session - - @property - def kernel_info(self) -> KernelInfo: - """Information about the kernel.""" - return self._kernel_info @property def server_info(self) -> ServerInfo: diff --git a/src/labone/core/reflection/__init__.py b/src/labone/core/reflection/__init__.py deleted file mode 100644 index 9890be9..0000000 --- a/src/labone/core/reflection/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Submodule for reflection server handling.""" - -from labone.core.reflection.server import ReflectionServer - -__all__ = ["ReflectionServer"] diff --git a/src/labone/core/reflection/capnp_dynamic_type_system.py b/src/labone/core/reflection/capnp_dynamic_type_system.py deleted file mode 100644 index 430ed77..0000000 --- a/src/labone/core/reflection/capnp_dynamic_type_system.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Dynamically build a type system from a parsed schema. - -This is a modified version of the code from capnp.pycapnp.pxi -""" - -from __future__ import annotations - -import logging -import typing as t - -from capnp.lib.capnp import ( - _DynamicStructBuilder, - _DynamicStructReader, - _EnumModule, - _InterfaceModule, - _StructABCMeta, - _StructModule, -) - -if t.TYPE_CHECKING: - from typing_extensions import Self - - from labone.core.reflection.parsed_wire_schema import ( - LoadedNode, - Schema, - ) - -logger = logging.getLogger(__name__) - - -def _build_one_type(name: str, schema: Schema) -> type: - """Build a single type from a schema. - - Based on the SchemaParser code from capnp.pycapnp.pxi - - Args: - name: The name of the type. - schema: The schema of the type. - - Returns: - Created capnp type. - """ - proto = schema.get_proto() - if proto.isStruct: - local_module = _StructModule(schema.as_struct(), name) - - class Reader(_DynamicStructReader): - """An abstract base class. Readers are 'instances' of this class.""" - - __metaclass__ = _StructABCMeta - __slots__ = [] # type: ignore[var-annotated] - _schema = local_module.schema - - def __new__(cls) -> Self: - msg = "This is an abstract base class" - raise TypeError(msg) - - class Builder(_DynamicStructBuilder): - """An abstract base class. Builders are 'instances' of this class.""" - - __metaclass__ = _StructABCMeta - __slots__ = [] # type: ignore[var-annotated] - _schema = local_module.schema - - def __new__(cls) -> Self: - msg = "This is an abstract base class" - raise TypeError(msg) - - local_module.Reader = Reader - local_module.Builder = Builder - - return local_module - if proto.isConst: - return schema.as_const_value() - if proto.isInterface: - return _InterfaceModule(schema.as_interface(), name) - if proto.isEnum: - return _EnumModule(schema.as_enum(), name) - # This should really not happen - msg = f"Cannot load {name} because its of an unsupported type" - raise RuntimeError(msg) - - -def _build_types_from_node( - *, - full_schema: dict[int, LoadedNode], - module: object, - node_id: int, -) -> None: - """Recursively create a type from a node. - - This function will create a new type corresponding to the node with id - "node_id", and then will create a nested type for each node that is a - children of that node. - - Args: - full_schema: The full schema of the capnp file. - module: The module where the new type should be added. - node_id: The id of the node to create. - """ - node = full_schema[node_id] - if node.name == "": - logger.debug("Skipping node %s because it has no name", node_id) - return - if node.file_of_origin == "capnp/c++.capnp": - logger.debug("Skipping node %s because it is in capnp/c++.capnp", node_id) - return - logger.debug("Loading %s into module %s", node.name, module) - submodule = _build_one_type(node.name, node.schema) - setattr(module, node.name, submodule) - for subnode in node.schema.get_proto().nestedNodes: - if subnode.id not in full_schema: - # This can for example happen for constants. They are listed as nested - # nodes, but they are not part of the full schema. - logger.debug("%s not in full schema", subnode.id) - continue - # Call recursively to build subnodes - _build_types_from_node( - full_schema=full_schema, - module=submodule, - node_id=subnode.id, - ) - - -def _should_skip(node: LoadedNode, *, skip_files: list[str]) -> bool: - """Check if a node should be skipped. - - This is used to skip nodes that are not needed in the type system. - - Args: - node: The node to check. - skip_files: List of files that should be skipped. - - Returns: - Flag indicating if the node should be skipped. - """ - if node.is_nested: - return True - # Given RPC "foo", the schema will have "foo$Params" and "foo$Results". - # The client shouldn't need to access these, so we don't add them. - if "$" in node.name: - return True - return any(skip_file in node.file_of_origin for skip_file in skip_files) - - -def build_type_system( - full_schema: dict[int, LoadedNode], - root_module: object, - skip_files: list[str] | None = None, -) -> None: - """Dynamically build a type system from a parsed schema. - - The type system is built in the root_module. The root_module is usually - the module where the schema was loaded from. - - Note: there is some room for interpretation when building the full type - hierarchy. Currently we flat it out excluding the file of origin. That means - that if originally there was a car.capnp file containing a "Car" struct and - a bike.capnp file containing a "Bike" struct, this will be available in - root_module as root_module.Car and root_module.Bike. One could argue that it - would be better to have root_module.car.Car and root_module.bike.Bike. - - Args: - full_schema: The full schema of the capnp file. - root_module: The module where the type system should be built. - skip_files: List of files that should be skipped. - """ - _skip_files = ["schema.capnp"] if skip_files is None else skip_files - # The full schema is a flat dictionary indexed by node id. At the root - # node we make only types that don't have a parents. Nodes with a parent - # are built recursively once the parent is built. - for node_id, loaded_node in full_schema.items(): - if _should_skip(loaded_node, skip_files=_skip_files): - continue - _build_types_from_node( - full_schema=full_schema, - module=root_module, - node_id=node_id, - ) diff --git a/src/labone/core/reflection/helper.py b/src/labone/core/reflection/helper.py deleted file mode 100644 index 4063e05..0000000 --- a/src/labone/core/reflection/helper.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Helper module for the reflection module.""" - -import os -import typing as t -from contextlib import contextmanager - - -@contextmanager -def enforce_pwd() -> t.Generator[None, None, None]: - """Enforces the PWD environment variable to be equal to the pythons cwd. - - This context manager temporarily sets the PWD environment variable to the - current working directory, defined in the python os library. This is useful - when c++ code is called from python, which relies on the PWD environment - variable to be set correctly. - """ - old_pwd = os.environ.get("PWD") - try: - os.environ["PWD"] = os.getcwd() # noqa: PTH109 - yield - finally: - if old_pwd is not None: - os.environ["PWD"] = old_pwd - else: - del os.environ["PWD"] # pragma: no cover diff --git a/src/labone/core/reflection/parsed_wire_schema.py b/src/labone/core/reflection/parsed_wire_schema.py deleted file mode 100644 index 6e6fcbd..0000000 --- a/src/labone/core/reflection/parsed_wire_schema.py +++ /dev/null @@ -1,91 +0,0 @@ -"""This module contains the ParsedWireSchema class. - -It is used to recursively load the encoded schema of a LabOne server. -""" - -from __future__ import annotations - -import contextlib -import logging -import typing as t -from dataclasses import dataclass - -import capnp - -if t.TYPE_CHECKING: - from typing_extensions import TypeAlias - -logger = logging.getLogger(__name__) - -EncodedSchema: TypeAlias = capnp.lib.capnp._DynamicListReader # noqa: SLF001 -Schema: TypeAlias = capnp.lib.capnp._Schema # noqa: SLF001 - - -@dataclass -class LoadedNode: - """Single node of the parsed schema.""" - - name: str - schema: Schema - file_of_origin: str - is_nested: bool = False - - -class ParsedWireSchema: - """Internal representation of capnp schema. - - Args: - encoded_schema: The encoded capnp schema. - """ - - def __init__(self, encoded_schema: EncodedSchema) -> None: - # This can probably be removed since the SchemaLoader will copy the - # encoded schema anyway ... For now we keep it to ensure nothing is - # deleted by capnp. - self._encoded_schema = encoded_schema - self._loader = capnp.SchemaLoader() - self._full_schema = self._load_encoded_schema(encoded_schema) - - def _load_encoded_schema( - self, - encoded_schema: EncodedSchema, - ) -> dict[int, LoadedNode]: - """Load the encoded schema. - - Iterate through all schema nodes and use the capnp schema loader to - store the schema in a dict (id: schema) - - Args: - encoded_schema: The encoded capnp schema. - - Returns: - The parsed schema represented as a dict (id: schema). - """ - nodes: dict[int, LoadedNode] = {} - nested_nodes = set() - for serialized_node in encoded_schema: - node = self._loader.load_dynamic(serialized_node) - node_proto = node.get_proto() - if ":" not in node_proto.displayName: - continue - splitted_name = node_proto.displayName.split(":") - loaded_node = LoadedNode( - name=splitted_name[1].split(".")[-1], - file_of_origin=splitted_name[0], - schema=node, - ) - nodes[node_proto.id] = loaded_node - logging.debug("%s => %s", serialized_node.id, loaded_node.name) - # Collect all nested nodes - for nested_node in node_proto.nestedNodes: - nested_nodes.add(nested_node.id) - # Mark all nested nodes - for node_id in nested_nodes: - with contextlib.suppress(KeyError): - nodes[node_id].is_nested = True - return nodes - - @property - def full_schema(self) -> dict[int, LoadedNode]: - """The full schema of the server.""" - return self._full_schema diff --git a/src/labone/core/reflection/server.py b/src/labone/core/reflection/server.py deleted file mode 100644 index fbc0bb9..0000000 --- a/src/labone/core/reflection/server.py +++ /dev/null @@ -1,474 +0,0 @@ -"""Basic dynamic reflection server. - -This module implements a basic dynamic reflection server. It is used to -connected to a basic Zurich Instruments reflection server. Based on the -`reflection.capnp` schema, it loads all capabilities exposed by the server and -adds them as attributes to the server instance. This allows to access the -capabilities directly through the server instance. -""" - -from __future__ import annotations - -import asyncio -import logging -import re -import typing as t -from functools import lru_cache - -import capnp - -from labone.core.errors import UnavailableError -from labone.core.helper import ( - CapnpCapability, - CapnpStructBuilder, - CapnpStructReader, - ensure_capnp_event_loop, -) -from labone.core.reflection.capnp_dynamic_type_system import build_type_system -from labone.core.reflection.helper import enforce_pwd -from labone.core.reflection.parsed_wire_schema import EncodedSchema, ParsedWireSchema -from labone.core.result import unwrap - -# The kj library (used by capnp) requires the PWD environment variable to be set. -# This is needed to resolve relative paths. When using python from the command -# line, the PWD variable is set automatically. However, this is not guaranteed. -# E.g. when using python from a jupyter notebook, the PWD variable is not set -# or set to `/`. This leads to a ugly warning inside the c++ code that can not -# be caught using Python's built-in warning handling mechanisms. To avoid this -# warning, we temporary set the PWD environment variable to the current working -# directory. -with enforce_pwd(): - from labone.resources import ( # type: ignore[attr-defined, import-untyped] - reflection_capnp, - ) - -logger = logging.getLogger(__name__) - -SNAKE_CASE_REGEX_1 = re.compile(r"(.)([A-Z][a-z]+)") -SNAKE_CASE_REGEX_2 = re.compile(r"([a-z0-9])([A-Z])") - - -@lru_cache(maxsize=None) -def _to_snake_case(word: str) -> str: - """Convert camel case to snake case. - - Args: - word: Word to convert. - - Returns: - Converted word. - """ - s1 = SNAKE_CASE_REGEX_1.sub(r"\1_\2", word) - return SNAKE_CASE_REGEX_2.sub(r"\1_\2", s1).lower() - - -@lru_cache(maxsize=None) -def _to_camel_case(word: str) -> str: - temp = word.split("_") - return temp[0] + "".join(ele.title() for ele in temp[1:]) - - -async def _fetch_encoded_schema( - client: capnp.TwoPartyClient, -) -> tuple[int, EncodedSchema]: - """Fetch the encoded schema from the server. - - This is done through the reflection interface of the server. - - Args: - client: Basic capnp client. - - Returns: - The encoded schema and the id of the bootstrap capability. - - Raises: - LabOneUnavailableError: If the schema cannot be fetched from the server. - """ - reflection = client.bootstrap().cast_as(reflection_capnp.Reflection) - try: - schema_and_bootstrap_cap = await reflection.getTheSchema() - except capnp.lib.capnp.KjException as e: - if e.type == "DISCONNECTED": # pragma: no cover - msg = "Unable to connect to the server. The server disconnected." - raise UnavailableError(msg) from e - msg = str( - "Unable to connect to the server. Could not fetch the schema " - "from the server.", - ) - raise UnavailableError(msg) from e - server_schema = schema_and_bootstrap_cap.theSchema.theSchema - bootstrap_capability_id = schema_and_bootstrap_cap.theSchema.typeId - return bootstrap_capability_id, server_schema - - -def _maybe_unwrap(maybe_result: CapnpStructReader) -> CapnpStructReader: - """Unwrap the result of an rpc call if possible. - - Most rpc calls return a result object. This function unwraps the result - object if possible. If the result object is a list, then the function - unwraps every element of the list. - - Unwrapping means that the result object is replaced by the result of the - call. If the call was successful, then the result is returned. If the call - failed, then the corresponding exception is raised. - - Args: - maybe_result: The result of an rpc call. - - Returns: - The unwrapped result. - - Raises: - LabOneCancelledError: The request was cancelled. - NotFoundError: The requested value or node was not found. - OverwhelmedError: The server is overwhelmed. - BadRequestError: The request could not be interpreted. - UnimplementedError: The request is not implemented. - InternalError: An internal error occurred. - UnavailableError: The device is unavailable. - LabOneTimeoutError: A timeout occurred on the server. - """ - if len(maybe_result.schema.fields_list) != 1 or not hasattr(maybe_result, "result"): - # For now we just do not handle this case ... Normally we have a single - # result or a list of results called result - return maybe_result - result = maybe_result.result - if isinstance(result, capnp.lib.capnp._DynamicListReader): # noqa: SLF001 - return [unwrap(item) for item in result] # pragma: no cover - return unwrap(result) - - -def _maybe_wrap_interface(maybe_capability: CapnpStructReader) -> CapnpStructReader: - """Wrap the result of an rpc call in the CapabilityWrapper if possible. - - Some rpc calls return a new capability. In order to unwrap the result of - the new capability, it needs to be wrapped in the CapabilityWrapper. - - Args: - maybe_capability: The result of an rpc call. - - Returns: - The wrapped result. - """ - if isinstance( - maybe_capability, - capnp.lib.capnp._DynamicCapabilityClient, # noqa: SLF001 - ): - return CapabilityWrapper(maybe_capability) - return maybe_capability - - -class RequestWrapper: - """Wrapper around a capnp request. - - This class is used to wrap a capnp request. Its main purpose is to - automatically unwrap the result of the request. - - In addition it allows to access the attributes of the request in snake case. - Since the default in Capnp is camel case, this is a bit more pythonic. - - Args: - request: The capnp request to wrap. - """ - - def __init__(self, request: CapnpStructBuilder): - object.__setattr__(self, "_request", request) - - async def send(self) -> CapnpStructReader: - """Send the request via capnp and unwrap the result. - - This functions mimics the capnp send function. In addition it unwraps - the result of the request. - - Returns: - The unwrapped result of the request. - """ - return _maybe_wrap_interface( - _maybe_unwrap(await object.__getattribute__(self, "_request").send()), - ) - - def __getattr__(self, name: str): - return getattr( - object.__getattribute__(self, "_request"), - _to_camel_case(name), - ) - - def __setattr__(self, name: str, value) -> None: # noqa: ANN001 - setattr( - object.__getattribute__(self, "_request"), - _to_camel_case(name), - value, - ) - - def __dir__(self) -> set[str]: - original_dir = dir(object.__getattribute__(self, "_request")) - snake_case_version = [ - _to_snake_case(name) for name in original_dir if not name.startswith("_") - ] - return set(snake_case_version + original_dir) - - -class CapabilityWrapper: - """Wrapper around a capnp capability. - - This class is used to wrap a capnp capability. Its main purpose is to - automatically unwrap the result of the requests. - - In addition it allows to access the attributes of the capability in snake - case. Since the default in Capnp is camel case, this is a bit more pythonic. - - Args: - capability: The capnp capability to wrap. - """ - - def __init__(self, capability: CapnpCapability): - self._capability = capability - - def _send_wrapper(self, func_name: str) -> t.Callable[..., CapnpStructReader]: - """Wrap the send function of the capability. - - This function wraps the send function of the capability. It is used to - automatically unwrap the result of the request. - - In pycapnp all rpc calls are done through the send function. It takes - the name of the function to call as the first argument and calls the right - function on the server. The result of the call is returned as a capnp - struct. - - Args: - func_name: Name of the function to wrap. - - Returns: - The wrapped function. - """ - - async def wrapper(*args, **kwargs) -> CapnpStructReader: - """Generic capnp rpc call.""" - return _maybe_wrap_interface( - _maybe_unwrap( - await self._capability._send( # noqa: SLF001 - func_name, - *args, - **kwargs, - ), - ), - ) - - return wrapper - - def _request_wrapper( - self, - func_name: str, - ) -> t.Callable[..., RequestWrapper | CapnpStructBuilder]: - """Wrap the request function of the capability. - - This function wraps the request function of the capability. It is used - to automatically unwrap the result of the request. - - A request is a capnp struct that exposes all the attributes of the - request. The request is sent to the server by calling the `send` - function. The result of the call is returned as a capnp struct. - - Args: - func_name: Name of the function to wrap. - - Returns: - The wrapped function. - """ - - def wrapper( - *, - apply_wrapper: bool = True, - ) -> RequestWrapper | CapnpStructBuilder: - """Create a capnp rpc request.""" - capnp_request = self._capability._request(func_name) # noqa: SLF001 - return RequestWrapper(capnp_request) if apply_wrapper else capnp_request - - return wrapper - - def __getattr__(self, name: str): - if name.endswith("_request"): - name_converted = _to_camel_case(name[:-8]) - if name_converted in self._capability.schema.method_names_inherited: - return self._request_wrapper(name_converted) - name_converted = _to_camel_case(name) - if name_converted in self._capability.schema.method_names_inherited: - return self._send_wrapper(name_converted) - return getattr(self._capability, name) - - def __dir__(self) -> set[str]: - original_dir = dir(self._capability) - snake_case_version = [ - _to_snake_case(name) for name in original_dir if not name.startswith("_") - ] - return set(snake_case_version + original_dir) - - @property - def capnp_capability(self) -> CapnpCapability: - """Return the underlying capnp capability.""" - return self._capability # pragma: no cover - - -class ReflectionServer: - """Basic dynamic reflection server. - - This class is used to connected to a basic Zurich Instruments reflection - server. Based on the `reflection.capnp` schema, it loads all capabilities - exposed by the server and adds them as attributes to the server instance. - This allows to access the capabilities directly through the server instance. - - The ReflectionServer class is instantiated through the staticmethod - `create()` or `create_from_connection`. This is due to the fact that the - instantiation is done asynchronously. - - !!! note - - Due to the asynchronous interface, one needs to use the static method - `create` instead of the `__init__` method. - - Args: - connection: Raw capnp asyncio stream for the connection to the server. - client: Basic capnp client. - encoded_schema: The encoded schema of the server. - bootstrap_capability_id: The id of the bootstrap capability. - unwrap_result: Flag if results should be unwrapped. Most rpc calls - return a result object. If this flag is the result of these - calls will be unwrapped with `labone.core.result.unwrap()`. - For more information see the documentation of the `unwrap()`. - (default: True) - """ - - def __init__( # noqa: PLR0913 - self, - *, - connection: capnp.AsyncIoStream, - client: capnp.TwoPartyClient, - encoded_schema: EncodedSchema, - bootstrap_capability_id: int, - unwrap_result: bool = True, - ) -> None: - self._connection = connection - self._client = client - self._unwrap_result = unwrap_result - self._parsed_schema = ParsedWireSchema(encoded_schema) - build_type_system(self._parsed_schema.full_schema, self) - - # Add to the server an instance of the bootstrap capability. - # So for example if the server exposes a FluxDevice interface, - # server will have "flux_device" attribute. - bootstrap_capability_name = self._parsed_schema.full_schema[ - bootstrap_capability_id - ].name - instance_name = _to_snake_case(bootstrap_capability_name) - - capability = self._client.bootstrap().cast_as( - getattr(self, bootstrap_capability_name), - ) - setattr( - self, - instance_name, - CapabilityWrapper(capability) if unwrap_result else capability, - ) - - logger.info( - "Server exposes a %s interface. Access it with server. %s", - bootstrap_capability_name, - instance_name, - ) - # Save the event loop the server was created in. This is needed to - # close the rpc client connection in the destructor of the server. - self._creation_loop = asyncio.get_event_loop() - - async def _close_rpc_client(self) -> None: # pragma: no cover - """Close the rpc client connection. - - This function is called in the destructor of the server. It closes the - rpc client connection. - - There is a bit of a catch to this function. The capnp client does a lot - of stuff in the background for every client. Before the server can be - closed, the client needs to be closed. Python takes care of this - automatically since the client is a member of the server. - However the client MUST be closed in the same thread in which the kj - event loop is running. If everything is done in the same thread, then - there is not problem. However, if the kj event loop is running in a - different thread, e.g. when using the sync wrapper, then the client - needs to be closed in the same thread as the kj event loop. Thats why - this function is async even though it does not need to be. - """ - self._client.close() - - def __del__(self) -> None: # pragma: no cover - # call the close_rpc_client function in the event loop the server - # was created in. See the docstring of the function for more details. - if ( - hasattr(self, "_creation_loop") - and self._creation_loop is not None - and self._creation_loop.is_running() - and asyncio.get_event_loop() != self._creation_loop - ): - _ = asyncio.ensure_future( # noqa: RUF006 - self._close_rpc_client(), - loop=self._creation_loop, - ) - - @staticmethod - async def create( - host: str, - port: int, - *, - unwrap_result: bool = True, - ) -> ReflectionServer: - """Connect to a reflection server. - - Args: - host: Host of the server. - port: Port of the server. - unwrap_result: Flag if results should be unwrapped. Most rpc calls - return a result object. If this flag is the result of these - calls will be unwrapped with `labone.core.result.unwrap()`. - For more information see the documentation of the `unwrap()`. - (default: True) - - - Returns: - The reflection server instance. - """ - await ensure_capnp_event_loop() - connection = await capnp.AsyncIoStream.create_connection(host=host, port=port) - return await ReflectionServer.create_from_connection( - connection, - unwrap_result=unwrap_result, - ) - - @staticmethod - async def create_from_connection( - connection: capnp.AsyncIoStream, - *, - unwrap_result: bool = True, - ) -> ReflectionServer: - """Create a reflection server from an existing connection. - - Args: - connection: Raw capnp asyncio stream for the connection to the server. - unwrap_result: Flag if results should be unwrapped. Most rpc calls - return a result object. If this flag is the result of these - calls will be unwrapped with `labone.core.result.unwrap()`. - For more information see the documentation of the `unwrap()`. - (default: True) - - Returns: - The reflection server instance. - """ - client = capnp.TwoPartyClient(connection) - ( - bootstrap_capability_id, - encoded_schema, - ) = await _fetch_encoded_schema(client) - return ReflectionServer( - connection=connection, - client=client, - encoded_schema=encoded_schema, - bootstrap_capability_id=bootstrap_capability_id, - unwrap_result=unwrap_result, - ) diff --git a/src/labone/core/result.py b/src/labone/core/result.py deleted file mode 100644 index 3f5e4eb..0000000 --- a/src/labone/core/result.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Module for handling the result.capnp::Result struct. - -This module provides a function to unwrap the result of a capnp call and -raise the corresponding exception if the call failed. It is intendet to -be used for low level capnp interface calls. -""" - -import capnp - -from labone.core.errors import LabOneCoreError, error_from_capnp -from labone.core.helper import CapnpStructReader - - -def unwrap(result: CapnpStructReader) -> CapnpStructReader: - """Unwrap a result.capnp::Result struct. - - Args: - result: The result to be unwrapped. - - Returns: - The unwrapped result. - - Raises: - LabOneCancelledError: The request was cancelled. - NotFoundError: The requested value or node was not found. - OverwhelmedError: The server is overwhelmed. - BadRequestError: The request could not be interpreted. - UnimplementedError: The request is not implemented. - InternalError: An internal error occurred. - UnavailableError: The device is unavailable. - LabOneTimeoutError: A timeout occurred on the server. - """ - try: - return result.ok - except (capnp.KjException, AttributeError): - pass - try: - raise error_from_capnp(result.err) from None - except capnp.KjException: - msg = f"Unable to parse Server response. Received: \n{result}" - raise LabOneCoreError(msg) from None diff --git a/src/labone/core/session.py b/src/labone/core/session.py index c17dcfe..58a77fb 100644 --- a/src/labone/core/session.py +++ b/src/labone/core/session.py @@ -1,11 +1,11 @@ -"""Module for a session to a Zurich Instruments Session Capability. +"""Module for the Zurich Instruments Session Capability. The Session capability provides access to the basic interactions with nodes and or properties of a server. Its used in multiple places with the software stack of Zurich Instruments. For example the Data Server kernel for a device provides a Session capability to access the nodes of the device. -Every Session capability provides the same capnp interface and can therefore be +Every Session capability provides the same interface and can therefore be handled in the same way. The only difference is the set of nodes/properties that are available. @@ -24,37 +24,26 @@ from enum import IntFlag from typing import Literal -import capnp from packaging import version from typing_extensions import NotRequired, TypeAlias, TypedDict -from labone.core import errors, result -from labone.core.helper import ( - CapnpCapability, - LabOneNodePath, - request_field_type_description, -) -from labone.core.result import unwrap -from labone.core.subscription import DataQueue, QueueProtocol, streaming_handle_factory -from labone.core.value import AnnotatedValue +from labone.core import errors +from labone.core.errors import async_translate_comms_error, translate_comms_error +from labone.core.subscription import DataQueue, QueueProtocol, StreamingHandle +from labone.core.value import AnnotatedValue, value_from_python_types if t.TYPE_CHECKING: - from labone.core.errors import ( # noqa: F401 - BadRequestError, - InternalError, - LabOneCoreError, - NotFoundError, - OverwhelmedError, - UnavailableError, - UnimplementedError, - ) - from labone.core.reflection.server import ReflectionServer + import zhinst.comms + from labone.core.helper import ( + LabOneNodePath, + ZIContext, + ) T = t.TypeVar("T") # Control node for transactions. This node is only available for UHF and MF devices. -TRANSACTION_NODE_PATH = "/ctrl/transaction/state" +_TRANSACTION_NODE_PATH = "/ctrl/transaction/state" NodeType: TypeAlias = Literal[ "Integer (64 bit)", @@ -156,66 +145,24 @@ class ListNodesFlags(IntFlag): EXCLUDE_VECTORS = 1 << 24 -async def _send_and_wait_request( - request: capnp.lib.capnp._Request, -) -> capnp.lib.capnp._Response: - """Send a request and wait for the response. - - The main purpose of this function is to take care of the error handling - for the capnp communication. If an error occurs a LabOneCoreError is - raised. - - Args: - request: Capnp request. - - Returns: - Successful response. - - Raises: - LabOneCoreError: If sending the message or receiving the response failed. - UnavailableError: If the server has not implemented the requested - method. - """ - try: - return await request.send() - # TODO(markush): Raise more specific error types. # noqa: TD003, FIX002 - except capnp.lib.capnp.KjException as error: - if ( - "Method not implemented" in error.description - or "object has no attribute" in error.description - ): - msg = str( - "The requested method is not implemented by the server. " - "This most likely that the LabOne version is outdated. " - "Please update the LabOne software to the latest version.", - ) - raise errors.UnavailableError(msg) from None - msg = error.description - raise errors.LabOneCoreError(msg) from None - except Exception as error: # noqa: BLE001 - msg = str(error) - raise errors.LabOneCoreError(msg) from None - - class Session: """Generic Capnp session client. Representation of a single Session capability. This class - encapsulates the capnp interaction an exposes a python native api. + encapsulates the labone interaction an exposes a python native api. All function are exposed as they are implemented in the interface - of the capnp server and are directly forwarded. + of the labone server and are directly forwarded. Each function implements the required error handling both for the - capnp communication and the server errors. This means unless an Exception + socket communication and the server errors. This means unless an Exception is raised the call was successful. The Session already requires an existing connection this is due to the - fact that the instantiation is done asynchronously. In addition the underlying - reflection server is required to be able to create the capnp messages. + fact that the instantiation is done asynchronously. Args: - capnp_session: Capnp session capability. - reflection_server: Reflection server instance. + session: Active labone session. + context: Context the session runs in. """ # The minimum capability version that is required by the labone api. @@ -225,40 +172,46 @@ class Session: def __init__( self, - capnp_session: CapnpCapability, + session: zhinst.comms.DynamicClient, *, - reflection_server: ReflectionServer, + context: ZIContext, ): - self._reflection_server = reflection_server - self._session = capnp_session - # The reflection server might has enabled the automatic unwrapping - # of the result. To allow both cases we check if the capnp_session - # is wrapped and use the underlying capnp session instead. - if hasattr(capnp_session, "capnp_capability"): - self._session = capnp_session.capnp_capability - # The client_id is required by most capnp messages to identify the client + self._context = context + self._session = session + + # The client_id is required by most messages to identify the client # on the server side. It is unique per session. self._client_id = uuid.uuid4() self._has_transaction_support: bool | None = None - async def ensure_compatibility(self) -> None: + def close(self) -> None: + """Close the session. + + Release the underlying network resources and close the session. Since + python does not follow the RAII pattern, it is not guaranteed that the + destructor is called. This may causes a session to stay open even if the + object is not used anymore. + If needed, the close method should be called explicitly. + """ + self._session.close() + + async def _ensure_compatibility( + self, + future: t.Awaitable[zhinst.comms.DynamicStructBase], + ) -> None: """Ensure the compatibility with the connected server. - Ensures that all function call will work as expected and all required - features are implemented within the server. + Takes a future for a `getSessionVersion` call and checks if the server + version is compatible with the LabOne API. If the server version is not + supported, an UnavailableError is raised. - Info: - This function is already called within the create method and does - not need to be called again. + Args: + future: Future for a `getSessionVersion` call. Raises: - UnavailableError: If the kernel is not compatible. + UnavailableError: If the server version is not supported. """ - server_version = version.Version( - ( - await _send_and_wait_request(self._session.getSessionVersion_request()) - ).version, - ) + server_version = version.Version((await future).version) if server_version < Session.MIN_CAPABILITY_VERSION: msg = ( f"The data server version is not supported by the LabOne API. " @@ -274,19 +227,64 @@ async def ensure_compatibility(self) -> None: ) raise errors.UnavailableError(msg) - async def list_nodes( + @translate_comms_error + def ensure_compatibility(self) -> t.Awaitable[None]: + """Ensure the compatibility with the connected server. + + Ensures that all function call will work as expected and all required + features are implemented within the server. + + Warning: + Only the compatibility with the server is checked. The compatibility + with the device is not checked. + + Info: + This function is already called within the create method and does + not need to be called again. + + Raises: + UnavailableError: If the kernel is not compatible. + """ + future = self._session.getSessionVersion() + return self._ensure_compatibility(future) + + async def _list_nodes_postprocessing( + self, + future: t.Awaitable[zhinst.comms.DynamicStructBase], + ) -> list[LabOneNodePath]: + """Postprocessing for the list nodes function. + + Convert the response from the server to a list of node paths. + + Args: + future: Future for a list nodes call. + + Returns: + List of node paths. + """ + response = await future + return list(response.paths) + + @translate_comms_error + def list_nodes( self, path: LabOneNodePath = "", *, flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, - ) -> list[LabOneNodePath]: + ) -> t.Awaitable[list[LabOneNodePath]]: """List the nodes found at a given path. + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: path: A string representing the path where the nodes are to be listed. Value is case insensitive. - Supports asterix (*) wildcards, except in the place of node path forward + Supports * wildcards, except in the place of node path forward slashes. (default="") flags: The flags for modifying the returned nodes. @@ -332,39 +330,54 @@ async def list_nodes( ... flags=ListNodesFlags.RECURSIVE | ListNodesFlags.EXCLUDE_VECTORS ... ) """ - request = self._session.listNodes_request() - try: - request.pathExpression = path - except Exception: # noqa: BLE001 - msg = "`path` must be a string." - raise TypeError(msg) # noqa: TRY200, B904 + future = self._session.listNodes( + pathExpression=path, + flags=int(flags), + client=self._client_id.bytes, + ) + return self._list_nodes_postprocessing(future) + + async def _list_nodes_info_postprocessing( + self, + future: t.Awaitable[zhinst.comms.DynamicStructBase], + ) -> dict[LabOneNodePath, NodeInfo]: + """Postprocessing for the list nodes info function. + + Convert the response from the server to a json dict. + + Args: + future: Future for a list nodes call. + + Returns: + A python dictionary of the list nodes info response. + """ + response = await future try: - request.flags = int(flags) - except capnp.KjException: - field_type = request_field_type_description(request, "flags") - msg = f"`flags` value is out-of-bounds, it must be of type {field_type}." - raise ValueError( - msg, - ) from None - except (TypeError, ValueError): - msg = "`flags` must be an integer." - raise TypeError(msg) from None - response = await _send_and_wait_request(request) - return list(response.paths) + return json.loads(response.nodeProps) + except RuntimeError as e: + msg = f"Error while listing nodes info for {response.paths}." + raise errors.LabOneCoreError(msg) from e - async def list_nodes_info( + @translate_comms_error + def list_nodes_info( self, path: LabOneNodePath = "", *, flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, - ) -> dict[LabOneNodePath, NodeInfo]: + ) -> t.Awaitable[dict[LabOneNodePath, NodeInfo]]: """List the nodes and their information found at a given path. + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: path: A string representing the path where the nodes are to be listed. Value is case insensitive. - Supports asterix (*) wildcards, except in the place of node path + Supports * wildcards, except in the place of node path forward slashes. (default="") flags: The flags for modifying the returned nodes. @@ -430,33 +443,79 @@ async def list_nodes_info( } } """ - request = self._session.listNodesJson_request() + future = self._session.listNodesJson( + pathExpression=path, + flags=int(flags), + client=self._client_id.bytes, + ) + return self._list_nodes_info_postprocessing(future) + + async def _single_value_postprocessing( + self, + future: t.Awaitable[zhinst.comms.DynamicStructBase], + path: LabOneNodePath, + ) -> AnnotatedValue: + """Postprocessing for the get/set with expression functions. + + Convert a single value response from the server to an annotated value. + + Args: + future: Future for a get/set call. + path: LabOne node path. + + Returns: + Annotated value of the node. + """ + response = await future try: - request.pathExpression = path - except Exception: # noqa: BLE001 - msg = "`path` must be a string." - raise TypeError(msg) from None + return AnnotatedValue.from_capnp(response.result[0]) + except IndexError as e: + msg = f"No value returned for path {path}." + raise errors.LabOneCoreError(msg) from e + except RuntimeError as e: + msg = f"Error while processing value from {path}. {e}" + raise errors.LabOneCoreError(msg) from e + + async def _multi_value_postprocessing( + self, + future: t.Awaitable[zhinst.comms.DynamicStructBase], + path: LabOneNodePath, + ) -> list[AnnotatedValue]: + """Postprocessing for the get/set with expression functions. + + Convert a multiple value response from the server to a list of + annotated values. + + Args: + future: Future for a get/set call. + path: LabOne node path. + + Returns: + List of annotated value of the node. + """ + response = await future try: - request.flags = int(flags) - except capnp.KjException: - field_type = request_field_type_description(request, "flags") - msg = f"`flags` value is out-of-bounds, it must be of type {field_type}." - raise ValueError( - msg, - ) from None - except (TypeError, ValueError): - msg = "`flags` must be an integer." - raise TypeError(msg) from None - response = await _send_and_wait_request(request) - return json.loads(response.nodeProps) - - async def set(self, value: AnnotatedValue) -> AnnotatedValue: + return [ + AnnotatedValue.from_capnp(raw_result) for raw_result in response.result + ] + except RuntimeError as e: + msg = f"Error while setting {path}." + raise errors.LabOneCoreError(msg) from e + + @translate_comms_error + def set(self, value: AnnotatedValue) -> t.Awaitable[AnnotatedValue]: """Set the value of a node. ```python await session.set(AnnotatedValue(path="/zi/debug/level", value=2) ``` + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: value: Value to be set. The annotated value must contain a LabOne node path and a value. (The path can be relative or @@ -476,22 +535,19 @@ async def set(self, value: AnnotatedValue) -> AnnotatedValue: LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - capnp_value = value.to_capnp(reflection=self._reflection_server) - request = self._session.setValue_request() - request.pathExpression = capnp_value.metadata.path - request.value = capnp_value.value - request.lookupMode = ( - self._reflection_server.LookupMode.directLookup # type: ignore[attr-defined] + future = self._session.setValue( + pathExpression=value.path, + value=value_from_python_types(value.value, path=value.path), + lookupMode="directLookup", + client=self._client_id.bytes, ) - request.client = self._client_id.bytes - response = await _send_and_wait_request(request) - try: - return AnnotatedValue.from_capnp(result.unwrap(response.result[0])) - except IndexError as e: - msg = f"No acknowledgement returned while setting {value.path}." - raise errors.LabOneCoreError(msg) from e + return self._single_value_postprocessing(future, value.path) - async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValue]: + @translate_comms_error + def set_with_expression( + self, + value: AnnotatedValue, + ) -> t.Awaitable[list[AnnotatedValue]]: """Set the value of all nodes matching the path expression. A path expression is a labone node path. The difference to a normal @@ -509,6 +565,12 @@ async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValu print(ack_values[0]) ``` + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: value: Value to be set. The annotated value must contain a LabOne path expression and a value. @@ -527,22 +589,19 @@ async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValu LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - capnp_value = value.to_capnp(reflection=self._reflection_server) - request = self._session.setValue_request() - request.pathExpression = capnp_value.metadata.path - request.value = capnp_value.value - request.lookupMode = self._reflection_server.LookupMode.withExpansion # type: ignore[attr-defined] - request.client = self._client_id.bytes - response = await _send_and_wait_request(request) - return [ - AnnotatedValue.from_capnp(result.unwrap(raw_result)) - for raw_result in response.result - ] - - async def get( + future = self._session.setValue( + pathExpression=value.path, + value=value_from_python_types(value.value, path=value.path), + lookupMode="withExpansion", + client=self._client_id.bytes, + ) + return self._multi_value_postprocessing(future, value.path) + + @translate_comms_error + def get( self, path: LabOneNodePath, - ) -> AnnotatedValue: + ) -> t.Awaitable[AnnotatedValue]: """Get the value of a node. The node can either be passed as an absolute path, starting with a leading @@ -555,6 +614,12 @@ async def get( await session.get('/zi/devices/visible') ``` + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: path: LabOne node path (relative or absolute). @@ -572,23 +637,15 @@ async def get( LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - request = self._session.getValue_request() - try: - request.pathExpression = path - except (AttributeError, TypeError, capnp.KjException) as error: - field_type = request_field_type_description(request, "pathExpression") - msg = f"`path` attribute must be of type {field_type}." - raise TypeError(msg) from error - request.lookupMode = self._reflection_server.LookupMode.directLookup # type: ignore[attr-defined] - request.client = self._client_id.bytes - response = await _send_and_wait_request(request) - try: - return AnnotatedValue.from_capnp(result.unwrap(response.result[0])) - except IndexError as e: - msg = f"No value returned for {path}." - raise errors.LabOneCoreError(msg) from e + future = self._session.getValue( + pathExpression=path, + lookupMode="directLookup", + client=self._client_id.bytes, + ) + return self._single_value_postprocessing(future, path) - async def get_with_expression( + @translate_comms_error + def get_with_expression( self, path_expression: LabOneNodePath, flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE @@ -596,7 +653,7 @@ async def get_with_expression( | ListNodesFlags.LEAVES_ONLY | ListNodesFlags.EXCLUDE_STREAMING | ListNodesFlags.GET_ONLY, - ) -> list[AnnotatedValue]: + ) -> t.Awaitable[list[AnnotatedValue]]: """Get the value of all nodes matching the path expression. A path expression is a labone node path. The difference to a normal @@ -612,6 +669,12 @@ async def get_with_expression( print(values[0]) ``` + Note: + The function is not async but returns an awaitable object. + This makes this function eagerly evaluated instead of the python + default lazy evaluation. This means that the request is sent to the + server immediately even if the result is not awaited. + Args: path_expression: LabOne path expression. flags: The flags used by the server (list_nodes()) to filter the @@ -630,21 +693,13 @@ async def get_with_expression( LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - request = self._session.getValue_request() - try: - request.pathExpression = path_expression - except (AttributeError, TypeError, capnp.KjException) as error: - field_type = request_field_type_description(request, "pathExpression") - msg = f"`path` attribute must be of type {field_type}." - raise TypeError(msg) from error - request.lookupMode = self._reflection_server.LookupMode.withExpansion # type: ignore[attr-defined] - request.flags = int(flags) - request.client = self._client_id.bytes - response = await _send_and_wait_request(request) - return [ - AnnotatedValue.from_capnp(result.unwrap(raw_result)) - for raw_result in response.result - ] + future = self._session.getValue( + pathExpression=path_expression, + lookupMode="withExpansion", + flags=int(flags), + client=self._client_id.bytes, + ) + return self._multi_value_postprocessing(future, path_expression) @t.overload async def subscribe( @@ -666,6 +721,7 @@ async def subscribe( get_initial_value: bool = False, ) -> QueueProtocol: ... + @async_translate_comms_error async def subscribe( self, path: LabOneNodePath, @@ -724,41 +780,32 @@ async def subscribe( LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - streaming_handle = streaming_handle_factory(self._reflection_server)( - parser_callback=parser_callback, - ) - subscription = self._reflection_server.Subscription( # type: ignore[attr-defined] - streamingHandle=streaming_handle, - subscriberId=self._client_id.bytes, - ) - try: - subscription.path = path - except (AttributeError, TypeError, capnp.KjException): - field_type = request_field_type_description(subscription, "path") - msg = f"`path` attribute must be of type {field_type}." - raise TypeError(msg) from None - request = self._session.subscribe_request() - request.subscription = subscription - + streaming_handle = StreamingHandle(parser_callback=parser_callback) + + subscription = { + "path": path, + "streamingHandle": self._context.register_callback( + streaming_handle.capnp_callback, + ), + "subscriberId": self._client_id.bytes, + } if get_initial_value: - response, initial_value = await asyncio.gather( - _send_and_wait_request(request), + _, initial_value = await asyncio.gather( + self._session.subscribe(subscription=subscription), self.get(path), ) - unwrap(response.result) # Result(Void, Error) new_queue_type = queue_type or DataQueue queue = new_queue_type( path=path, - register_function=streaming_handle.register_data_queue, + handle=streaming_handle, ) queue.put_nowait(initial_value) return queue - response = await _send_and_wait_request(request) - unwrap(response.result) # Result(Void, Error) + await self._session.subscribe(subscription=subscription) new_queue_type = queue_type or DataQueue return new_queue_type( path=path, - register_function=streaming_handle.register_data_queue, + handle=streaming_handle, ) async def wait_for_state_change( @@ -803,7 +850,7 @@ async def _supports_transaction(self) -> bool: """ if self._has_transaction_support is None: self._has_transaction_support = ( - len(await self.get_with_expression(TRANSACTION_NODE_PATH)) == 1 + len(await self.get_with_expression(_TRANSACTION_NODE_PATH)) == 1 ) return self._has_transaction_support @@ -851,18 +898,23 @@ async def set_transaction(self) -> t.AsyncGenerator[list[t.Awaitable], None]: requests: list[t.Awaitable] = [] if await self._supports_transaction(): begin_request = self.set( - AnnotatedValue(path=TRANSACTION_NODE_PATH, value=1), + AnnotatedValue(path=_TRANSACTION_NODE_PATH, value=1), ) yield requests requests = [begin_request, *requests] requests.append( - self.set(AnnotatedValue(path=TRANSACTION_NODE_PATH, value=0)), + self.set(AnnotatedValue(path=_TRANSACTION_NODE_PATH, value=0)), ) else: yield requests await asyncio.gather(*requests) @property - def reflection_server(self) -> ReflectionServer: - """Get the reflection server instance.""" - return self._reflection_server # pragma: no cover + def context(self) -> ZIContext: + """Get the context instance.""" + return self._context # pragma: no cover + + @property + def raw_session(self) -> zhinst.comms.DynamicClient: + """Get the underlying session.""" + return self._session diff --git a/src/labone/core/shf_vector_data.py b/src/labone/core/shf_vector_data.py index 7947133..d8f763f 100644 --- a/src/labone/core/shf_vector_data.py +++ b/src/labone/core/shf_vector_data.py @@ -22,7 +22,11 @@ import numpy as np from labone.core.errors import LabOneCoreError, SHFHeaderVersionNotSupportedError -from labone.core.helper import CapnpCapability, VectorElementType, VectorValueType +from labone.core.helper import VectorElementType, VectorValueType + +if t.TYPE_CHECKING: + from zhinst.comms import DynamicStructBase + logger = logging.getLogger(__name__) @@ -572,7 +576,7 @@ def _deserialize_shf_demodulator_vector( return SHFDemodSample(data_x, data_y), extra_header -def get_header_length(vector_data: CapnpCapability) -> int: +def get_header_length(vector_data: DynamicStructBase) -> int: """Get the length of the extra header. The 16 least significant bits of extra_header_info contain the length of @@ -589,7 +593,7 @@ def get_header_length(vector_data: CapnpCapability) -> int: def parse_shf_vector_data_struct( - vector_data: CapnpCapability, + vector_data: DynamicStructBase, ) -> tuple[np.ndarray | SHFDemodSample, ExtraHeader | None]: """Parse the SHF vector data struct. @@ -640,10 +644,10 @@ def encode_shf_vector_data_struct( *, data: np.ndarray | SHFDemodSample, extra_header: ExtraHeader, -) -> CapnpCapability: +) -> dict[str, t.Any]: """Encode the SHF vector data struct. - Build a capnp struct (in form of a dictionary) from data and + Build a struct (in form of a dictionary) from data and extra header to send. Args: @@ -651,7 +655,7 @@ def encode_shf_vector_data_struct( extra_header: The extra header. Returns: - The capnp struct to send. + The struct to send. """ if isinstance(extra_header, ShfScopeVectorExtraHeader): if not isinstance(data, np.ndarray): diff --git a/src/labone/core/subscription.py b/src/labone/core/subscription.py index 5de4f04..b5012ce 100644 --- a/src/labone/core/subscription.py +++ b/src/labone/core/subscription.py @@ -1,6 +1,6 @@ """This module contains the logic for the subscription mechanism. -Subscriptions are implemented through the capnp stream mechanism. This handles +Subscriptions are implemented through the capnp stream mechanism. It handles all the communication stuff, e.g. back pressure and flow control. The only thing the client needs to provide is a `SessionClient.StreamingHandle.Server` implementation. The `sendValues()` method of this class will be called by the @@ -23,16 +23,14 @@ import logging import typing as t import weakref -from abc import ABC, abstractmethod -import capnp +import zhinst.comms from labone.core import errors from labone.core.value import AnnotatedValue if t.TYPE_CHECKING: - from labone.core.helper import CapnpCapability, LabOneNodePath - from labone.core.reflection.server import ReflectionServer + from labone.core.helper import LabOneNodePath logger = logging.getLogger(__name__) @@ -88,14 +86,14 @@ def __init__( self, *, path: LabOneNodePath, - register_function: t.Callable[[weakref.ReferenceType[DataQueue]], None], + handle: StreamingHandle, ) -> None: super().__init__() self._path = path self._connection_state = _ConnectionState() - self._register_function = register_function + self._handle = handle - register_function(weakref.ref(self)) + handle.register_data_queue(weakref.ref(self)) def __repr__(self) -> str: return str( @@ -145,7 +143,7 @@ def fork( new_queue_type = queue_type or DataQueue return new_queue_type( path=self._path, - register_function=self._register_function, + handle=self._handle, ) def disconnect(self) -> None: @@ -317,16 +315,11 @@ class DistinctConsecutiveDataQueue(DataQueue): will accept new values that have a different value than the last value. """ - def __init__( - self, - *, - path: LabOneNodePath, - register_function: t.Callable[[weakref.ReferenceType[DataQueue]], None], - ) -> None: + def __init__(self, *, path: LabOneNodePath, handle: StreamingHandle) -> None: DataQueue.__init__( self, path=path, - register_function=register_function, + handle=handle, ) self._last_value = AnnotatedValue(value=None, path="unkown") @@ -386,34 +379,28 @@ def fork( ) -class StreamingHandle(ABC): - """Streaming Handle server. - - This class is passed to the kernel when a subscription is requested. - Every update event to the subscribed node will result in the kernel - calling the sendValues method. - - Warning: - This function is owned by capnp and should not be called or referenced - by the user. - - The StreamingHandle holds only a weak reference to the data queue to which - the values will be added. This is done to avoid the subscription to stay - alive even if no one hold a reference to the data queue. +class StreamingHandle: + """Streaming Handle server implementation. Args: data_queue: Weak reference to the data queue to which the values will be added. """ - @abstractmethod def __init__( self, *, parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - ) -> None: ... + ) -> None: + self._data_queues = [] # type: ignore[var-annotated] + + if parser_callback is None: + + def parser_callback(x: AnnotatedValue) -> AnnotatedValue: + return x + + self._parser_callback = parser_callback - @abstractmethod def register_data_queue( self, data_queue: weakref.ReferenceType[QueueProtocol], @@ -424,195 +411,128 @@ def register_data_queue( data_queue: Weak reference to the data queue to which the values will be added. """ - ... + self._data_queues.append(data_queue) - @abstractmethod - async def sendValues( # noqa: N802 (function name is enforced through the schema) + def _add_to_data_queue( self, - values: CapnpCapability, - **_, - ) -> None: - """Capnp Interface callback. + data_queue: QueueProtocol | None, + value: AnnotatedValue, + ) -> bool: + """Add a value to the data queue. - This function is called by the kernel (through RPC) when an update - event for the subscribed node is received. + The value is added to the queue non blocking, meaning that if the queue + is full, an error is raised. Args: - values: List of update events for the subscribed node. + data_queue: The data queue to which the value will be added. + value: The value to add to the data queue. + + Returns: + True if the value was added to the data queue, False otherwise. Raises: - capnp.KjException: If no data queues are registered any more and - the subscription should be removed. + StreamingError: If the data queue is full or disconnected. + AttributeError: If the data queue has been garbage collected. """ - ... - - -def streaming_handle_factory( - reflection_server: ReflectionServer, -) -> type[StreamingHandle]: - """Factory for the StreamingHandle class. - - The StreamingHandle class is a capnp server implementation. It is passed - to the server when a subscription is requested. Every update event to the - subscription will result in the server calling the sendValues method. - - Since due to the dynamic reflection mechanism (loading capnp schema at - runtime) used, the StreamingHandle class can not be defined statically. - - Args: - reflection_server: The reflection server that is used for the session. + if data_queue is None: + # The server holds only a weak reference to the data queue. + # If the data queue has been garbage collected, the weak reference + # will be None. + return False + try: + data_queue.put_nowait(value) + except errors.StreamingError: + logger.debug( + "Data queue %s has disconnected. Removing from list of queues.", + hex(id(data_queue)), + ) + return False + except asyncio.QueueFull: + logger.warning( + "Data queue %s is full. No more data will be pushed to the queue.", + hex(id(data_queue)), + ) + data_queue.disconnect() # type: ignore[union-attr] # supposed to throw + return False + return True - Returns: - The StreamingHandle class, matching the schema of the reflection server. - """ + def distribute_to_data_queues(self, value: AnnotatedValue) -> None: + """Add a value to all data queues. - class StreamingHandleImpl( - StreamingHandle, - reflection_server.StreamingHandle.Server, # type: ignore[name-defined] - ): - """Streaming Handle server implementation. + Distribute to all data queues and remove the ones that are not + connected anymore. Args: - data_queue: Weak reference to the data queue to which the values - will be added. + value: The value to add to the data queue. """ + self._data_queues = [ + data_queue + for data_queue in self._data_queues + if self._add_to_data_queue(data_queue(), value) + ] - def __init__( - self, - *, - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - ) -> None: - self._data_queues = [] # type: ignore[var-annotated] - - if parser_callback is None: - - def parser_callback(x: AnnotatedValue) -> AnnotatedValue: - return x + def _distribute_to_data_queues( + self, + value: zhinst.comms.DynamicStructBase, + ) -> None: + """Add a value to all data queues. - self._parser_callback = parser_callback + The value is added to the queue non blocking, meaning that if the queue + is full, an error is raised. - def register_data_queue( - self, - data_queue: weakref.ReferenceType[QueueProtocol], - ) -> None: - """Register a new data queue. - - Args: - data_queue: Weak reference to the data queue to which the values - will be added. - """ - self._data_queues.append(data_queue) + Args: + value: The value to add to the data queue. - def _add_to_data_queue( - self, - data_queue: QueueProtocol | None, - value: AnnotatedValue, - ) -> bool: - """Add a value to the data queue. - - The value is added to the queue non blocking, meaning that if the queue - is full, an error is raised. - - Args: - data_queue: The data queue to which the value will be added. - value: The value to add to the data queue. - - Returns: - True if the value was added to the data queue, False otherwise. - - Raises: - StreamingError: If the data queue is full or disconnected. - AttributeError: If the data queue has been garbage collected. - """ - if data_queue is None: - # The server holds only a weak reference to the data queue. - # If the data queue has been garbage collected, the weak reference - # will be None. - return False - try: - data_queue.put_nowait(value) - except errors.StreamingError: - logger.debug( - "Data queue %s has disconnected. Removing from list of queues.", - hex(id(data_queue)), - ) - return False - except asyncio.QueueFull: - logger.warning( - "Data queue %s is full. No more data will be pushed to the queue.", - hex(id(data_queue)), - ) - data_queue.disconnect() # type: ignore[union-attr] # supposed to throw - return False - return True - - def _distribute_to_data_queues( - self, - value: CapnpCapability, - ) -> None: - """Add a value to all data queues. - - The value is added to the queue non blocking, meaning that if the queue - is full, an error is raised. - - Args: - value: The value to add to the data queue. - - Raises: - capnp.KjException: If no data queues are registered any more and - the subscription should be removed. - """ - try: - parsed_value = self._parser_callback(AnnotatedValue.from_capnp(value)) - except errors.LabOneCoreError as err: # pragma: no cover - logger.exception(err.args[0]) - except ValueError as err: # pragma: no cover - self._data_queues = [ - data_queue().disconnect() # type: ignore[union-attr] # supposed to throw - for data_queue in self._data_queues - if data_queue() is not None - ] - logger.error( # noqa: TRY400 - "Disconnecting subscription. Could not parse value. Error: %s", - err.args[0], - ) - raise - - # distribute to all data queues and remove the ones that are not - # connected anymore. + Raises: + capnp.KjException: If no data queues are registered any more and + the subscription should be removed. + """ + try: + parsed_value = self._parser_callback(AnnotatedValue.from_capnp(value)) + except errors.LabOneCoreError as err: # pragma: no cover + logger.exception(err.args[0]) + raise + except ValueError as err: # pragma: no cover self._data_queues = [ - data_queue + data_queue().disconnect() # type: ignore[union-attr] # supposed to throw for data_queue in self._data_queues - if self._add_to_data_queue(data_queue(), parsed_value) + if data_queue() is not None ] - if not self._data_queues: - # TODO(tobiasa): The kernel expects a KjException of type # noqa: FIX002 - # DISCONNECTED for a clean removal of the subscription. However, - # pycapnp does currently not support this. - # https://github.com/capnproto/pycapnp/issues/324 - msg = "DISCONNECTED" - raise capnp.KjException( - type=capnp.KjException.Type.DISCONNECTED, - message=msg, - ) - - async def sendValues( # noqa: N802 (function name is enforced through the schema) - self, - values: CapnpCapability, - **_, - ) -> None: - """Capnp Interface callback. + logger.error( # noqa: TRY400 + "Disconnecting subscription. Could not parse value. Error: %s", + err.args[0], + ) + raise + self.distribute_to_data_queues(parsed_value) - This function is called by the kernel (through RPC) when an update - event for the subscribed node is received. + if not self._data_queues: + msg = "No data queues are registered anymore. Disconnecting subscription." + raise errors.StreamingError(msg) - Args: - values: List of update events for the subscribed node. + async def capnp_callback( + self, + interface: int, # noqa: ARG002 + method_index: int, # noqa: ARG002 + call_input: zhinst.comms.DynamicStructBase, + fulfiller: zhinst.comms.Fulfiller, + ) -> None: + """Capnp Interface callback. - Raises: - capnp.KjException: If no data queues are registered any more and - the subscription should be removed. - """ - list(map(self._distribute_to_data_queues, values)) + This function is called by the kernel (through RPC) when an update + event for the subscribed node is received. + + Args: + interface: The interface of the capnp schema. + method_index: The method index of the capnp schema. + call_input: The input data of the capnp schema. + fulfiller: The fulfiller to fulfill the promise. - return StreamingHandleImpl + Raises: + capnp.KjException: If no data queues are registered any more and + the subscription should be removed. + """ + try: + list(map(self._distribute_to_data_queues, call_input.values)) + fulfiller.fulfill() + except Exception as err: # noqa: BLE001 + fulfiller.reject(zhinst.comms.Fulfiller.DISCONNECTED, err.args[0]) diff --git a/src/labone/core/value.py b/src/labone/core/value.py index 54038b6..bee9c48 100644 --- a/src/labone/core/value.py +++ b/src/labone/core/value.py @@ -12,15 +12,18 @@ import typing as t from dataclasses import dataclass -import capnp import numpy as np +from typing_extensions import TypeAlias -from labone.core.errors import SHFHeaderVersionNotSupportedError, error_from_capnp +from labone.core.errors import ( + LabOneCoreError, + SHFHeaderVersionNotSupportedError, + get_streaming_error, +) from labone.core.helper import ( LabOneNodePath, VectorElementType, VectorValueType, - request_field_type_description, ) from labone.core.shf_vector_data import ( ExtraHeader, @@ -30,14 +33,15 @@ preprocess_complex_shf_waveform_vector, ) -if t.TYPE_CHECKING: - from labone.core.errors import LabOneCoreError # noqa: F401 - from labone.core.helper import CapnpCapability - from labone.core.reflection.server import ReflectionServer +if t.TYPE_CHECKING: # pragma: no cover + import zhinst.comms logger = logging.getLogger(__name__) +CapnpInput: TypeAlias = dict[str, t.Any] + + @dataclass class AnnotatedValue: """Python representation of a node value. @@ -75,7 +79,7 @@ def __repr__(self) -> str: ) @staticmethod - def from_capnp(raw: CapnpCapability) -> AnnotatedValue: + def from_capnp(raw: zhinst.comms.DynamicStructBase) -> AnnotatedValue: """Convert a capnp AnnotatedValue to a python AnnotatedValue. Args: @@ -87,43 +91,19 @@ def from_capnp(raw: CapnpCapability) -> AnnotatedValue: Raises: ValueError: If the capnp value has an unknown type or can not be parsed. """ - value, extra_header = _capnp_value_to_python_value(raw.value) - return AnnotatedValue( - value=value, - timestamp=raw.metadata.timestamp, - path=raw.metadata.path, - extra_header=extra_header, - ) - - def to_capnp(self, *, reflection: ReflectionServer) -> CapnpCapability: - """Convert a python AnnotatedValue to a capnp AnnotatedValue. - - Warning: - This method is not the inversion of `from_capnp`. It is only - packs the relevant information that are parsed by the server - for a set request into a capnp message! - - Returns: - The capnp message containing the relevant information for a set - request. - - Raises: - TypeError: If the `path` attribute is not of type `str`. - LabOneCoreError: If the data type of the value to be set is not supported. - """ - message = reflection.AnnotatedValue.new_message() # type: ignore[attr-defined] try: - message.metadata.path = self.path - except (AttributeError, TypeError, capnp.KjException): - field_type = request_field_type_description(message.metadata, "path") - msg = f"`path` attribute must be of type {field_type}." - raise TypeError(msg) from None - message.value = _value_from_python_types( - self.value, - path=self.path, - reflection=reflection, - ) - return message + try: + value, extra_header = _capnp_value_to_python_value(raw.value) + except AttributeError: + value, extra_header = None, None + return AnnotatedValue( + value=value, + timestamp=raw.metadata.timestamp, + path=raw.metadata.path, + extra_header=extra_header, + ) + except RuntimeError as e: + raise LabOneCoreError(str(e)) from e @dataclass @@ -149,7 +129,7 @@ class TriggerSample: sequence_index: int @staticmethod - def from_capnp(raw: CapnpCapability) -> TriggerSample: + def from_capnp(raw: zhinst.comms.DynamicStructBase) -> TriggerSample: """Convert a capnp TriggerSample to a python TriggerSample. Args: @@ -184,7 +164,7 @@ class CntSample: trigger: int @staticmethod - def from_capnp(raw: CapnpCapability) -> CntSample: + def from_capnp(raw: zhinst.comms.DynamicStructBase) -> CntSample: """Convert a capnp CntSample to a python CntSample. Args: @@ -210,12 +190,14 @@ def from_capnp(raw: CapnpCapability) -> CntSample: SHFDemodSample, TriggerSample, CntSample, + list[TriggerSample], + list[CntSample], None, ] def _capnp_vector_to_value( - vector_data: CapnpCapability, + vector_data: zhinst.comms.DynamicStructBase, ) -> tuple[np.ndarray | SHFDemodSample, ExtraHeader | None]: """Parse a capnp vector to a numpy array. @@ -264,7 +246,7 @@ def _capnp_vector_to_value( def _capnp_value_to_python_value( - capnp_value: CapnpCapability, + capnp_value: zhinst.comms.DynamicStructBase, ) -> tuple[Value, ExtraHeader | None]: """Convert a capnp value to a python value. @@ -278,26 +260,34 @@ def _capnp_value_to_python_value( ValueError: If the capnp value has an unknown type or can not be parsed. LabOneCoreError: If the capnp value is a streaming error. """ - capnp_type = capnp_value.which() - if capnp_type == "int64": + if hasattr(capnp_value, "int64"): return capnp_value.int64, None - if capnp_type == "double": + if hasattr(capnp_value, "double"): return capnp_value.double, None - if capnp_type == "complex": + if hasattr(capnp_value, "complex"): return complex(capnp_value.complex.real, capnp_value.complex.imag), None - if capnp_type == "string": + if hasattr(capnp_value, "string"): return capnp_value.string, None - if capnp_type == "vectorData": + if hasattr(capnp_value, "vectorData"): return _capnp_vector_to_value(capnp_value.vectorData) - if capnp_type == "cntSample": + if hasattr(capnp_value, "cntSample"): return CntSample.from_capnp(capnp_value.cntSample), None - if capnp_type == "triggerSample": + if hasattr(capnp_value, "vectorCntSamples"): + return [ + CntSample.from_capnp(sample) for sample in capnp_value.vectorCntSamples + ], None + if hasattr(capnp_value, "triggerSample"): return TriggerSample.from_capnp(capnp_value.triggerSample), None - if capnp_type == "none": + if hasattr(capnp_value, "vectorTriggerSamples"): + return [ + TriggerSample.from_capnp(sample) + for sample in capnp_value.vectorTriggerSamples + ], None + if hasattr(capnp_value, "none"): return None, None - if capnp_type == "streamingError": - raise error_from_capnp(capnp_value.streamingError) - msg = f"Unknown capnp type: {capnp_type}" + if hasattr(capnp_value, "streamingError"): + raise get_streaming_error(capnp_value.streamingError) + msg = f"Unknown capnp type: {dir(capnp_value)}" raise ValueError(msg) @@ -305,8 +295,7 @@ def _numpy_vector_to_capnp_vector( np_vector: np.ndarray, *, path: LabOneNodePath, - reflection: ReflectionServer, -) -> capnp.lib.capnp._DynamicStructBuilder: +) -> CapnpInput: """Convert a numpy vector to a capnp vector. Args: @@ -320,30 +309,29 @@ def _numpy_vector_to_capnp_vector( LabOneCoreError: If the numpy type has no corresponding VectorElementType. """ + request_value: dict[str, t.Any] = {} + request_value["extraHeaderInfo"] = 0 + request_value["valueType"] = VectorValueType.VECTOR_DATA.value np_data = np_vector np_vector_type = np_vector.dtype if np.iscomplexobj(np_vector) and "waveforms" in path.lower(): np_data, np_vector_type = preprocess_complex_shf_waveform_vector(np_vector) - data = np_data.tobytes() + request_value["data"] = np_data.tobytes() try: - capnp_vector_type = VectorElementType.from_numpy_type(np_vector_type).value + request_value["vectorElementType"] = VectorElementType.from_numpy_type( + np_vector_type, + ).value except ValueError as e: msg = f"Unsupported numpy type: {np_vector_type}" raise ValueError(msg) from e - return reflection.VectorData( # type: ignore[attr-defined] - valueType=VectorValueType.VECTOR_DATA.value, - extraHeaderInfo=0, - vectorElementType=capnp_vector_type, - data=data, - ) + return request_value -def _value_from_python_types( +def value_from_python_types( value: t.Any, # noqa: ANN401 *, path: LabOneNodePath, - reflection: ReflectionServer, -) -> capnp.lib.capnp._DynamicStructBuilder: +) -> CapnpInput: """Create `Value` builder from Python types. Args: @@ -357,32 +345,28 @@ def _value_from_python_types( Raises: LabOneCoreError: If the data type of the value to be set is not supported. """ - request_value = reflection.Value.new_message() # type: ignore[attr-defined] + request_value: dict[str, t.Any] = {} if isinstance(value, bool): - request_value.int64 = int(value) + request_value["int64"] = int(value) elif np.issubdtype(type(value), np.integer): - request_value.int64 = value + request_value["int64"] = value elif np.issubdtype(type(value), np.floating): - request_value.double = value + request_value["double"] = value elif isinstance(value, complex): - request_value.complex = reflection.Complex( # type: ignore[attr-defined] - real=value.real, - imag=value.imag, - ) + request_value["complex"] = {"real": value.real, "imag": value.imag} elif isinstance(value, str): - request_value.string = value + request_value["string"] = value elif isinstance(value, bytes): - request_value.vectorData = reflection.VectorData( # type: ignore[attr-defined] - valueType=VectorValueType.BYTE_ARRAY.value, - extraHeaderInfo=0, - vectorElementType=VectorElementType.UINT8.value, - data=value, - ) + request_value["vectorData"] = { + "valueType": VectorValueType.BYTE_ARRAY.value, + "extraHeaderInfo": 0, + "vectorElementType": VectorElementType.UINT8.value, + "data": value, + } elif isinstance(value, np.ndarray): - request_value.vectorData = _numpy_vector_to_capnp_vector( + request_value["vectorData"] = _numpy_vector_to_capnp_vector( value, path=path, - reflection=reflection, ) else: msg = f"The provided value has an invalid type: {type(value)}" @@ -392,9 +376,7 @@ def _value_from_python_types( return request_value -def value_from_python_types_dict( - annotated_value: AnnotatedValue, -) -> capnp.lib.capnp._DynamicStructBuilder: +def value_from_python_types_dict(annotated_value: AnnotatedValue) -> CapnpInput: """Create `Value` builder from Python types. Note: @@ -414,14 +396,18 @@ def value_from_python_types_dict( Raises: LabOneCoreError: If the data type of the value to be set is not supported. """ - if annotated_value.extra_header is not None and isinstance( - annotated_value.value, - (np.ndarray, SHFDemodSample), + if ( + annotated_value.extra_header is not None + and isinstance( + annotated_value.value, + (np.ndarray), + ) + or isinstance(annotated_value.value, SHFDemodSample) ): return { "vectorData": encode_shf_vector_data_struct( data=annotated_value.value, - extra_header=annotated_value.extra_header, + extra_header=annotated_value.extra_header, # type: ignore[union-attr, arg-type] ), } diff --git a/src/labone/dataserver.py b/src/labone/dataserver.py index e306253..44465f4 100644 --- a/src/labone/dataserver.py +++ b/src/labone/dataserver.py @@ -7,10 +7,11 @@ from labone.core import ( AnnotatedValue, + KernelInfo, KernelSession, ServerInfo, Session, - ZIKernelInfo, + ZIContext, ) from labone.errors import LabOneError from labone.nodetree import construct_nodetree @@ -115,6 +116,7 @@ async def create( *, custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, hide_zi_prefix: bool = True, + context: ZIContext | None = None, ) -> DataServer: """Create a new Session to a LabOne Data Server. @@ -128,6 +130,9 @@ async def create( annotated value. This function is applied to all values coming from the server. It is applied after the default enum parser, if applicable. + context: Context in which the session should run. If not provided + the default context will be used which is in most cases the + desired behavior. Returns: The connected DataServer. @@ -142,8 +147,9 @@ async def create( LabOneError: If an error appeared in the connection to the device. """ session = await KernelSession.create( - kernel_info=ZIKernelInfo(), + kernel_info=KernelInfo.zi_connection(), server_info=ServerInfo(host=host, port=port), + context=context, ) return await DataServer.create_from_session( diff --git a/src/labone/instrument.py b/src/labone/instrument.py index 7810d49..d5ba3d6 100644 --- a/src/labone/instrument.py +++ b/src/labone/instrument.py @@ -9,10 +9,11 @@ from labone.core import ( AnnotatedValue, - DeviceKernelInfo, + KernelInfo, KernelSession, ServerInfo, Session, + ZIContext, ) from labone.errors import LabOneError from labone.nodetree import construct_nodetree @@ -108,13 +109,14 @@ async def create_from_session( ) @staticmethod - async def create( + async def create( # noqa: PLR0913 serial: str, *, host: str, port: int = 8004, interface: str = "", custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, + context: ZIContext | None = None, ) -> Instrument: """Connect to a device. @@ -132,6 +134,9 @@ async def create( annotated value. This function is applied to all values coming from the server. It is applied after the default enum parser, if applicable. + context: Context in which the session should run. If not provided + the default context will be used which is in most cases the + desired behavior. Returns: The connected device. @@ -146,8 +151,12 @@ async def create( LabOneError: If an error appeared in the connection to the device. """ session = await KernelSession.create( - kernel_info=DeviceKernelInfo(device_id=serial, interface=interface), + kernel_info=KernelInfo.device_connection( + device_id=serial, + interface=interface, + ), server_info=ServerInfo(host=host, port=port), + context=context, ) return await Instrument.create_from_session( serial, diff --git a/src/labone/mock/__init__.py b/src/labone/mock/__init__.py index 2bad7eb..3e55689 100644 --- a/src/labone/mock/__init__.py +++ b/src/labone/mock/__init__.py @@ -7,19 +7,14 @@ from scratch. Example: - >>> mock_session = await spawn_hpk_mock( - AutomaticSessionFunctionality(paths_to_info) - ) - + >>> mock_session = await AutomaticLabOneServer.start_pipe(paths_to_info) >>> queue = await session.subscribe("/a/b/c") >>> print(await session.set(AnnotatedValue(path="/a/b/c", value=123, timestamp=0))) >>> print(await session.get("/a/b/t")) """ -from labone.mock.automatic_session_functionality import AutomaticSessionFunctionality -from labone.mock.entry_point import spawn_hpk_mock +from labone.mock.automatic_server import AutomaticLabOneServer __all__ = [ - "spawn_hpk_mock", - "AutomaticSessionFunctionality", + "AutomaticLabOneServer", ] diff --git a/src/labone/mock/automatic_session_functionality.py b/src/labone/mock/automatic_server.py similarity index 97% rename from src/labone/mock/automatic_session_functionality.py rename to src/labone/mock/automatic_server.py index b6c9c69..e2c06ac 100644 --- a/src/labone/mock/automatic_session_functionality.py +++ b/src/labone/mock/automatic_server.py @@ -43,7 +43,7 @@ ) from labone.mock.errors import LabOneMockError from labone.node_info import NodeInfo -from labone.server.session import SessionFunctionality, Subscription +from labone.server.session import LabOneServerBase, Subscription if t.TYPE_CHECKING: from labone.core.helper import LabOneNodePath @@ -59,7 +59,7 @@ class PathData: streaming_handles: list[Subscription] -class AutomaticSessionFunctionality(SessionFunctionality): +class AutomaticLabOneServer(LabOneServerBase): """Predefined behaviour for HPK mock. Args: @@ -70,6 +70,7 @@ def __init__( self, paths_to_info: dict[LabOneNodePath, NodeInfoType], ) -> None: + super().__init__() # storing state and tree structure, info and subscriptions # set all existing paths to 0. common_prefix_raw = ( @@ -281,8 +282,8 @@ async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValu raise LabOneCoreError(msg) return result - async def subscribe_logic(self, subscription: Subscription) -> None: - """Predefined behaviour for subscribe_logic. + async def subscribe(self, subscription: Subscription) -> None: + """Predefined behaviour for subscribe. Stores the subscription. Whenever an update event happens they are distributed to all registered handles, diff --git a/src/labone/mock/entry_point.py b/src/labone/mock/entry_point.py deleted file mode 100644 index 2d5f07c..0000000 --- a/src/labone/mock/entry_point.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Simplifying the creation of a mock server.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from labone.core.reflection.server import ReflectionServer -from labone.core.session import Session -from labone.mock.hpk_schema import get_schema -from labone.server.server import start_local_server -from labone.server.session import SessionInterface - -if TYPE_CHECKING: - import capnp - - from labone.core.helper import CapnpCapability - from labone.server.session import SessionFunctionality - - -class MockSession(Session): - """Regular Session holding a mock server. - - This class is designed for holding the mock server. - This is needed, because otherwise, - there would be no reference to the capnp objects, which would go out of scope. - This way, the correct lifetime of the capnp objects is ensured, by attaching it to - its client. - - Args: - mock_server: Mock server. - capnp_session: Capnp session. - reflection: Reflection server. - """ - - def __init__( - self, - mock_server: capnp.TwoPartyServer, - capnp_session: CapnpCapability, - *, - reflection: ReflectionServer, - ): - super().__init__(capnp_session, reflection_server=reflection) - self._mock_server = mock_server - - -async def spawn_hpk_mock( - functionality: SessionFunctionality, -) -> MockSession: - """Shortcut for creating a mock server. - - ```python - mock_server = await spawn_hpk_mock( - AutomaticSessionFunctionality(paths_to_info) - ) - ``` - - Args: - functionality: Functionality to be mocked. - - Returns: - Mock server. - - Raises: - FileNotFoundError: If the file does not exist. - PermissionError: If the file cannot be read. - capnp.lib.capnp.KjException: If the schema is invalid. Or the id - of the concrete server is not in the schema. - """ - server, client = await start_local_server( - schema=get_schema(), - server=SessionInterface(functionality), - ) - reflection = await ReflectionServer.create_from_connection(client) - return MockSession( - server, - reflection.session, # type: ignore[attr-defined] - reflection=reflection, - ) diff --git a/src/labone/mock/hpk_schema.py b/src/labone/mock/hpk_schema.py deleted file mode 100644 index 9e69953..0000000 --- a/src/labone/mock/hpk_schema.py +++ /dev/null @@ -1,115 +0,0 @@ -# ruff: noqa: E501 Line too long -"""This file is generated and contains a capnproto schema in packed binary format. - -specifically a serialized reflection_capnp.CapSchema message with the typeId field -not set. - -The schema has been compiled from the file zhinst/io/protocol/capnp/hpk/hpk.capnp by the simplon-capnp-bundler -capnpc plugin. - -This file contains the following schema nodes: - - zhinst/io/protocol/capnp/hpk/hpk.capnp @0xe5b41bb10b86ce3e - - capnp/c++.capnp:allowCancellation @0xac7096ff8cfc9dce - - capnp/c++.capnp @0xbdf87d7bb8304e81 - - capnp/c++.capnp:namespace @0xb9c6f99ebf805f2c - - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection @0xf9a52e68104bc776 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session @0xb9d445582da4a55c - - zhinst/io/protocol/capnp/hpk/hpk.capnp:Hpk @0xa621130a90860008 - - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Params @0x86a77be57bfa08f7 - - zhinst/io/protocol/capnp/reflection/reflection.capnp:CapSchema @0xcb31ef7a76eb85cf - - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Results @0xc9db2193e1883dd1 - - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Params @0xe3e32fd4d93c1199 - - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Results @0xfb31a67fd905411a - - capnp/schema.capnp:Node @0xe682ab4cf923a417 - - capnp/schema.capnp:ElementSize @0xd1958f7dba521926 - - capnp/schema.capnp:Field @0x9aad50a41f4af45f - - capnp/schema.capnp:Node.struct @0x9ea0b19b37fb4435 - - capnp/schema.capnp:Enumerant @0x978a7cebdc549a4d - - capnp/schema.capnp:Node.enum @0xb54ab3364333f598 - - capnp/schema.capnp:Method @0x9500cce23b334d80 - - capnp/schema.capnp:Superclass @0xa9962a9ed0a4d7f8 - - capnp/schema.capnp:Node.interface @0xe82753cff0c2218f - - capnp/schema.capnp:Type @0xd07378ede1f9cc60 - - capnp/schema.capnp:Value @0xce23dcd2d7b00c9b - - capnp/schema.capnp:Node.const @0xb18aa5ac7a0d9420 - - capnp/schema.capnp:Node.annotation @0xec1619d4400a0290 - - capnp/schema.capnp:Node.NestedNode @0xdebf55bbfa0fc242 - - capnp/schema.capnp:Annotation @0xf1c8950dab257542 - - capnp/schema.capnp:Node.Parameter @0xb9521bccf10fa3b1 - - capnp/schema.capnp:Brand @0x903455f06065422b - - capnp/schema.capnp @0xa93fc509624c72d9 - - capnp/schema.capnp:Brand.Scope @0xabd73485a9636bc9 - - capnp/schema.capnp:Brand.Binding @0xc863cd16969ee7fc - - capnp/schema.capnp:Type.list @0x87e739250a60ea97 - - capnp/schema.capnp:Type.enum @0x9e0e78711a7f87a9 - - capnp/schema.capnp:Type.struct @0xac3a6f60ef4cc6d3 - - capnp/schema.capnp:Type.interface @0xed8bca69f7fb0cbf - - capnp/schema.capnp:Type.anyPointer.unconstrained @0x8e3b5f79fe593656 - - capnp/schema.capnp:Type.anyPointer.parameter @0x9dd1f724f4614a85 - - capnp/schema.capnp:Type.anyPointer.implicitMethodParameter @0xbaefc9120c56e274 - - capnp/schema.capnp:Type.anyPointer @0xc2573fe8a23e49f1 - - capnp/schema.capnp:Field.slot @0xc42305476bb4746f - - capnp/schema.capnp:Field.group @0xcafccddb68db1d11 - - capnp/schema.capnp:Field.ordinal @0xbb90d5c287870be6 - - zhinst/io/protocol/capnp/reflection/reflection.capnp @0x8461c2916c39ad0e - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Params @0x92035429255ef5a8 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Results @0xebd34bdb74c961b0 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Params @0xcd242e1bb990dfe7 - - zhinst/io/protocol/capnp/common/result.capnp:Result @0xbab0f33e1934323d - - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue @0xf408ee376e837cdc - - zhinst/io/protocol/capnp/common/error.capnp:Error @0xc4e34e4c517d11d9 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Results @0xe948518c129575bb - - zhinst/io/protocol/capnp/common/value.capnp:Value @0xb1838b4771be75ac - - zhinst/io/protocol/capnp/session/session_protocol.capnp:ReturnFromSetWhen @0xdd2da53aac55edf9 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Params @0xa8f6fded40f38065 - - zhinst/io/protocol/capnp/common/value.capnp:Void @0xdf7fe8e981437816 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Results @0x8aef0146a9595df9 - - zhinst/io/protocol/capnp/streaming/streaming.capnp:Subscription @0xedac21a53de1b1d4 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Params @0xb9b18be9afb1b3e4 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Results @0xa0ca6692df48c93f - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.disconnectDevice$Params @0xe682e60b2cd90782 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Params @0xcd837fe86531f6c1 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Results @0xa8151642645bfa32 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.unsubscribe$Params @0xb16a1640db91e61c - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Params @0xd77d14cf14b3405a - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Results @0xcc68434da57cf6b3 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Params @0xdae05cd32680aba1 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Results @0xfd47648e56c0e689 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:LookupMode @0xda5049b5e072f425 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Params @0x9fe29a10493e27c7 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Results @0x9474d85f26f63900 - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Params @0xc5e872236cc4917b - - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Results @0xce98719dbad0a401 - - zhinst/io/protocol/capnp/common/value.capnp @0xfca5cbb23425bcc7 - - zhinst/io/protocol/capnp/common/result.capnp @0xf7e8e31fdca4abd5 - - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue.Metadata @0xad53d8ca57af3018 - - zhinst/io/protocol/capnp/common/value.capnp:Complex @0xaaf1afaf97b4b157 - - zhinst/io/protocol/capnp/common/value.capnp:VectorData @0x994c65b80df38978 - - zhinst/io/protocol/capnp/common/value.capnp:CntSample @0xe9370bd8287d6065 - - zhinst/io/protocol/capnp/common/value.capnp:TriggerSample @0xdeb72097c27d0d95 - - zhinst/io/protocol/capnp/common/error.capnp:ErrorKind @0xb7e671e24a9802bd - - zhinst/io/protocol/capnp/common/error.capnp @0xb97062d62cb99beb - - zhinst/io/protocol/capnp/session/session_protocol.capnp @0xb6c21289d5437347 - - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle @0xf51e28a7f5b41574 - - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle.sendValues$Params @0xa30bad0f967c47b6 - - capnp/stream.capnp:StreamResult @0x995f9a3377c0b16e - - capnp/stream.capnp @0x86c366a91393f3f8 - - zhinst/io/protocol/capnp/streaming/streaming.capnp @0xcc93f4056b9c74c8 -""" - -from labone.core.helper import CapnpStructReader -from labone.core.reflection.server import reflection_capnp - -_CAPNP_BINARY_SCHEMA = b"\x21\x03\x04\x22\x04\x08\x03\xeb\x06\x50\x01\x01\x00\x00\x31\x01\xef\x1d\x53\x5c\x01\x05\x06\xff\x3e\xce\x86\x0b\xb1\x1b\xb4\xe5\x00\x01\x21\x00\x02\x33\xdd\x0e\x3a\x01\x13\xed\x0e\x17\x13\xf9\x0e\x37\x00\x02\xff\xce\x9d\xfc\x8c\xff\x96\x70\xac\x00\xd1\x10\x05\x01\x03\xff\x81\x4e\x30\xb8\x7b\x7d\xf8\xbd\x00\x00\x01\x33\x19\x0f\x12\x01\x13\x29\x0f\x07\x00\x00\x53\x24\x0f\x03\x01\x00\x01\xff\x81\x4e\x30\xb8\x7b\x7d\xf8\xbd\x00\x01\x0a\x00\x02\x13\x15\x0f\x82\x13\x19\x0f\x37\x13\x49\x0f\x1f\x00\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x10\x05\x01\xff\x81\x4e\x30\xb8\x7b\x7d\xf8\xbd\x00\x00\x01\x11\x02\x01\x13\x4d\x0f\x07\x00\x00\x11\x2a\x01\x00\x01\xff\x76\xc7\x4b\x10\x68\x2e\xa5\xf9\x00\x11\x35\x03\xff\x0e\xad\x39\x6c\x91\xc2\x61\x84\x00\x00\x01\x11\x52\x01\x11\x9a\x01\x00\x00\x11\xd2\x01\x13\x9a\x01\x01\x00\x00\xff\x5c\xa5\xa4\x2d\x58\x45\xd4\xb9\x00\x11\x38\x03\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x00\x01\x13\xaa\x01\x01\x13\xf2\x01\x01\x00\x00\x13\x2a\x02\x01\x13\x2a\x06\x01\x00\x00\xfd\x08\x86\x90\x0a\x13\x21\xa6\x11\x27\x03\xff\x3e\xce\x86\x0b\xb1\x1b\xb4\xe5\x00\x00\x01\x13\x3a\x06\x01\x13\x72\x06\x01\x00\x00\x13\x82\x06\x01\x13\x92\x06\x01\x00\x00\xff\xf7\x08\xfa\x7b\xe5\x7b\xa7\x86\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\xc2\x06\x01\x00\x04\xff\xcf\x85\xeb\x76\x7a\xef\x31\xcb\x00\x51\x35\x01\x01\xff\x0e\xad\x39\x6c\x91\xc2\x61\x84\x00\x05\x01\x07\x00\x00\x13\x22\x07\x01\x13\x6a\x07\x01\x00\x00\x13\x7a\x07\x01\x00\x01\xff\xd1\x3d\x88\xe1\x93\x21\xdb\xc9\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xa2\x08\x01\x00\x01\x13\x02\x09\x01\x00\x01\xff\x99\x11\x3c\xd9\xd4\x2f\xe3\xe3\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\x92\x09\x01\x00\x04\xff\x1a\x41\x05\xd9\x7f\xa6\x31\xfb\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xfa\x09\x01\x00\x01\x13\x62\x0a\x01\x00\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x51\x13\x01\x05\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x06\x07\x06\x01\x06\x13\xea\x0a\x01\x13\x0a\x0b\x01\x00\x00\x13\x7a\x0b\x01\x00\x01\xff\x26\x19\x52\xba\x7d\x8f\x95\xd1\x00\x11\x13\x02\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x00\x01\x13\xaa\x11\x01\x13\xd2\x11\x01\x00\x00\x13\xe2\x11\x01\x00\x01\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x04\x07\x02\x01\x04\x13\x12\x13\x01\x13\x3a\x13\x01\x00\x00\x13\x6a\x13\x01\x00\x01\xff\x35\x44\xfb\x37\x9b\xb1\xa0\x9e\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x5a\x16\x01\x00\x01\x13\x82\x16\x01\x00\x01\xff\x4d\x9a\x54\xdc\xeb\x7c\x8a\x97\x00\x51\x13\x01\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x02\x07\x00\x00\x13\x3a\x1a\x01\x13\x62\x1a\x01\x00\x00\x13\x72\x1a\x01\x00\x01\xff\x98\xf5\x33\x43\x36\xb3\x4a\xb5\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x1a\x1c\x01\x00\x01\x13\x42\x1c\x01\x00\x01\xbf\x80\x4d\x33\x3b\xe2\xcc\x95\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x05\x07\x00\x00\x13\xf2\x1c\x01\x13\x1a\x1d\x01\x00\x00\x13\x2a\x1d\x01\x00\x01\xff\xf8\xd7\xa4\xd0\x9e\x2a\x96\xa9\x00\x51\x13\x01\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x01\x07\x00\x00\x13\xea\x01\x02\x13\x12\x02\x02\x00\x00\x13\x22\x02\x02\x00\x01\xff\x8f\x21\xc2\xf0\xcf\x53\x27\xe8\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x22\x03\x02\x00\x01\x13\x52\x03\x02\x00\x01\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x51\x13\x01\x03\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x01\x07\x13\x00\x00\x13\x9a\x04\x02\x13\xba\x04\x02\x00\x00\x13\xca\x04\x02\x00\x01\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x51\x13\x01\x02\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x45\x01\x07\x13\x00\x00\x13\xba\x0c\x02\x13\xe2\x0c\x02\x00\x00\x13\xf2\x0c\x02\x00\x01\xff\x20\x94\x0d\x7a\xac\xa5\x8a\xb1\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\xfa\x15\x02\x00\x01\x13\x22\x16\x02\x00\x01\xff\x90\x02\x0a\x40\xd4\x19\x16\xec\x00\x51\x18\x01\x05\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x15\x06\x07\x01\x00\x00\x13\x22\x17\x02\x00\x01\x13\x52\x17\x02\x00\x01\xff\x42\xc2\x0f\xfa\xbb\x55\xbf\xde\x00\x51\x18\x01\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x05\x01\x07\x00\x00\x13\xf2\x1d\x02\x13\x22\x1e\x02\x00\x00\x13\x32\x1e\x02\x00\x01\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x51\x13\x01\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x02\x07\x00\x00\x13\x32\x1f\x02\x13\x5a\x1f\x02\x00\x00\x13\x6a\x1f\x02\x00\x01\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x11\x18\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x05\x01\x07\x00\x00\x13\xe2\x20\x02\x13\x12\x21\x02\x00\x00\x13\x22\x21\x02\x00\x01\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x11\x13\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x05\x01\x07\x00\x00\x13\xaa\x21\x02\x13\xd2\x21\x02\x00\x00\x13\x12\x22\x02\x00\x01\xff\xd9\x72\x4c\x62\x09\xc5\x3f\xa9\x00\x01\x0d\x00\x02\x13\xba\x22\x02\x13\xda\x22\x02\x13\x42\x24\x02\x00\x02\xff\xc9\x6b\x63\xa9\x85\x34\xd7\xab\x00\x51\x19\x01\x02\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x45\x01\x07\x02\x01\x04\x13\x9a\x24\x02\x13\xc2\x24\x02\x00\x00\x13\xd2\x24\x02\x00\x01\xff\xfc\xe7\x9e\x96\x16\xcd\x63\xc8\x00\x51\x19\x01\x01\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x45\x01\x07\x02\x00\x00\x13\x6a\x26\x02\x13\x9a\x26\x02\x00\x00\x13\xaa\x26\x02\x00\x01\xff\x97\xea\x60\x0a\x25\x39\xe7\x87\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\xaa\x27\x02\x00\x01\x13\xd2\x27\x02\x00\x01\xff\xa9\x87\x7f\x1a\x71\x78\x0e\x9e\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\x62\x28\x02\x00\x01\x13\x8a\x28\x02\x00\x01\xff\xd3\xc6\x4c\xef\x60\x6f\x3a\xac\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\x8a\x29\x02\x00\x01\x13\xb2\x29\x02\x00\x01\xff\xbf\x0c\xfb\xf7\x69\xca\x8b\xed\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x15\x01\x07\x01\x00\x00\x13\xb2\x2a\x02\x00\x01\x13\xe2\x2a\x02\x00\x01\xff\x56\x36\x59\xfe\x79\x5f\x3b\x8e\x00\x51\x23\x01\x03\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x55\x01\x07\x01\x04\x01\x05\x13\xe2\x2b\x02\x00\x01\x13\x22\x2c\x02\x00\x01\xff\x85\x4a\x61\xf4\x24\xf7\xd1\x9d\x00\x51\x23\x01\x03\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x15\x01\x07\x01\x00\x00\x13\x1a\x2e\x02\x00\x01\x13\x52\x2e\x02\x00\x01\xff\x74\xe2\x56\x0c\x12\xc9\xef\xba\x00\x51\x23\x01\x03\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x15\x01\x07\x01\x00\x00\x13\x5a\x2f\x02\x00\x01\x13\xa2\x2f\x02\x00\x01\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x51\x18\x01\x03\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x55\x01\x07\x01\x03\x01\x04\x13\x32\x30\x02\x00\x01\x13\x62\x30\x02\x00\x01\xff\x6f\x74\xb4\x6b\x47\x05\x23\xc4\x00\x51\x19\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x15\x04\x07\x01\x00\x00\x13\x52\x31\x02\x00\x01\x13\x7a\x31\x02\x00\x01\xff\x11\x1d\xdb\x68\xdb\xcd\xfc\xca\x00\x51\x19\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x15\x04\x07\x01\x00\x00\x13\x82\x33\x02\x00\x01\x13\xaa\x33\x02\x00\x01\xff\xe6\x0b\x87\x87\xc2\xd5\x90\xbb\x00\x51\x19\x01\x03\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x55\x04\x07\x01\x02\x01\x05\x13\x32\x34\x02\x00\x01\x13\x62\x34\x02\x00\x01\xff\x0e\xad\x39\x6c\x91\xc2\x61\x84\x00\x01\x2f\x00\x02\x13\x72\x35\x02\x13\xb2\x35\x02\x13\x02\x36\x02\x00\x02\xff\xa8\xf5\x5e\x25\x29\x54\x03\x92\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\x5a\x36\x02\x00\x01\x13\xba\x36\x02\x00\x01\xff\xb0\x61\xc9\x74\xdb\x4b\xd3\xeb\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x3a\x38\x02\x00\x01\x13\x9a\x38\x02\x00\x01\xff\xe7\xdf\x90\xb9\x1b\x2e\x24\xcd\x00\x11\x40\x01\x00\x00\x05\x02\x07\x00\x00\x13\x42\x39\x02\x00\x01\x13\xaa\x39\x02\x00\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x51\x2d\x01\x01\xff\xd5\xab\xa4\xdc\x1f\xe3\xe8\xf7\x00\x45\x01\x07\x02\x10\x01\x13\xca\x3a\x02\x13\x0a\x3b\x02\x00\x00\x13\x1a\x3b\x02\x00\x00\x13\x1a\x3c\x02\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x11\x2c\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x05\x02\x07\x00\x00\x13\x4a\x3c\x02\x13\x92\x3c\x02\x00\x00\x13\xc2\x3c\x02\x00\x01\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x51\x2c\x01\x01\xff\xeb\x9b\xb9\x2c\xd6\x62\x70\xb9\x00\x05\x03\x07\x00\x00\x13\xca\x3d\x02\x13\x0a\x3e\x02\x00\x00\x13\x1a\x3e\x02\x00\x01\xff\xbb\x75\x95\x12\x8c\x51\x48\xe9\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x11\xb2\x03\x00\x01\x13\x1a\x01\x03\x00\x01\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x51\x2c\x01\x02\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x45\x01\x07\x08\x01\x04\x13\x52\x02\x03\x13\x92\x02\x03\x00\x00\x13\xa2\x02\x03\x00\x01\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x11\x38\x02\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x00\x01\x13\x8a\x06\x03\x13\xe2\x06\x03\x00\x00\x13\xf2\x06\x03\x00\x01\xff\x65\x80\xf3\x40\xed\xfd\xf6\xa8\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\xa2\x07\x03\x00\x01\x13\x0a\x08\x03\x00\x01\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x11\x2c\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\x8a\x09\x03\x13\xca\x09\x03\x00\x03\xff\xf9\x5d\x59\xa9\x46\x01\xef\x8a\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xda\x09\x03\x00\x01\x13\x42\x0a\x03\x00\x01\xff\xd4\xb1\xe1\x3d\xa5\x21\xac\xed\x00\x11\x33\x01\xff\xc8\x74\x9c\x6b\x05\xf4\x93\xcc\x00\x05\x03\x07\x00\x00\x13\x5a\x0b\x03\x13\xa2\x0b\x03\x00\x00\x13\xb2\x0b\x03\x00\x01\xff\xe4\xb3\xb1\xaf\xe9\x8b\xb1\xb9\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x3a\x0d\x03\x00\x01\x13\x9a\x0d\x03\x00\x01\xff\x3f\xc9\x48\xdf\x92\x66\xca\xa0\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x2a\x0e\x03\x00\x01\x13\x8a\x0e\x03\x00\x01\xff\x82\x07\xd9\x2c\x0b\xe6\x82\xe6\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\xa2\x0f\x03\x00\x04\xff\xc1\xf6\x31\x65\xe8\x7f\x83\xcd\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\x02\x10\x03\x00\x01\x13\x62\x10\x03\x00\x01\xff\x32\xfa\x5b\x64\x42\x16\x15\xa8\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xe2\x11\x03\x00\x01\x13\x42\x12\x03\x00\x01\xff\x1c\xe6\x91\xdb\x40\x16\x6a\xb1\x00\x11\x40\x01\x00\x00\x05\x02\x07\x00\x00\x13\xd2\x12\x03\x00\x01\x13\x32\x13\x03\x00\x01\xff\x5a\x40\xb3\x14\xcf\x14\x7d\xd7\x00\x11\x40\x01\x00\x00\x04\x07\x00\x00\x13\x5a\x14\x03\x00\x04\xff\xb3\xf6\x7c\xa5\x4d\x43\x68\xcc\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\xc2\x14\x03\x00\x01\x13\x2a\x15\x03\x00\x01\xff\xa1\xab\x80\x26\xd3\x5c\xe0\xda\x00\x51\x40\x01\x01\x00\x00\x05\x03\x07\x00\x00\x13\xb2\x15\x03\x00\x01\x13\x1a\x16\x03\x00\x01\xff\x89\xe6\xc0\x56\x8e\x64\x47\xfd\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x12\x18\x03\x00\x01\x13\x7a\x18\x03\x00\x01\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x11\x38\x02\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x00\x01\x13\x92\x19\x03\x13\xe2\x19\x03\x00\x00\x13\xf2\x19\x03\x00\x01\xff\xc7\x27\x3e\x49\x10\x9a\xe2\x9f\x00\x51\x40\x01\x01\x00\x00\x05\x03\x07\x00\x00\x13\x52\x1a\x03\x00\x01\x13\xaa\x1a\x03\x00\x01\xfe\x39\xf6\x26\x5f\xd8\x74\x94\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x2a\x1d\x03\x00\x01\x13\x8a\x1d\x03\x00\x01\xff\x7b\x91\xc4\x6c\x23\x72\xe8\xc5\x00\x51\x40\x01\x01\x00\x00\x05\x02\x07\x00\x00\x13\xc2\x1e\x03\x00\x01\x13\x1a\x1f\x03\x00\x01\xff\x01\xa4\xd0\xba\x9d\x71\x98\xce\x00\x11\x40\x01\x00\x00\x05\x01\x07\x00\x00\x13\x1a\x21\x03\x00\x01\x13\x7a\x21\x03\x00\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x01\x26\x00\x02\x13\xb2\x22\x03\x13\xea\x22\x03\x13\xc2\x23\x03\x00\x02\xff\xd5\xab\xa4\xdc\x1f\xe3\xe8\xf7\x00\x01\x27\x00\x02\x13\x1a\x24\x03\x13\x52\x24\x03\x13\x7a\x24\x03\x00\x02\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x51\x3b\x01\x01\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x05\x01\x07\x00\x00\x13\xd2\x24\x03\x13\x22\x25\x03\x00\x00\x13\x32\x25\x03\x00\x01\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x51\x2c\x01\x02\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\x3a\x26\x03\x13\x7a\x26\x03\x00\x00\x13\x8a\x26\x03\x00\x01\xff\x78\x89\xf3\x0d\xb8\x65\x4c\x99\x00\x51\x2c\x01\x01\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x05\x01\x07\x00\x00\x13\x8a\x27\x03\x13\xca\x27\x03\x00\x00\x13\xda\x27\x03\x00\x01\xff\x65\x60\x7d\x28\xd8\x0b\x37\xe9\x00\x51\x2c\x01\x02\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\xea\x29\x03\x13\x2a\x2a\x03\x00\x00\x13\x3a\x2a\x03\x00\x01\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x51\x2c\x01\x05\xff\xc7\xbc\x25\x34\xb2\xcb\xa5\xfc\x00\x04\x07\x00\x00\x13\xba\x2b\x03\x13\x02\x2c\x03\x00\x00\x13\x12\x2c\x03\x00\x01\xff\xbd\x02\x98\x4a\xe2\x71\xe6\xb7\x00\x11\x2c\x02\xff\xeb\x9b\xb9\x2c\xd6\x62\x70\xb9\x00\x00\x01\x13\x92\x2f\x03\x13\xd2\x2f\x03\x00\x00\x13\xe2\x2f\x03\x00\x01\xff\xeb\x9b\xb9\x2c\xd6\x62\x70\xb9\x00\x01\x26\x00\x02\x13\x6a\x31\x03\x13\xa2\x31\x03\x13\xea\x31\x03\x00\x02\xff\x47\x73\x43\xd5\x89\x12\xc2\xb6\x00\x01\x32\x00\x02\x13\x42\x32\x03\x13\x82\x32\x03\x13\xf2\x32\x03\x00\x02\xff\x74\x15\xb4\xf5\xa7\x28\x1e\xf5\x00\x11\x33\x03\xff\xc8\x74\x9c\x6b\x05\xf4\x93\xcc\x00\x00\x01\x13\x82\x33\x03\x13\xd2\x33\x03\x00\x00\x13\xe2\x33\x03\x13\x4a\x34\x03\x00\x00\xff\xb6\x47\x7c\x96\x0f\xad\x0b\xa3\x00\x11\x43\x01\x00\x00\x05\x01\x07\x00\x00\x13\x5a\x34\x03\x00\x01\x13\xba\x34\x03\x00\x01\xff\x6e\xb1\xc0\x77\x33\x9a\x5f\x99\x00\x11\x13\x01\xff\xf8\xf3\x93\x13\xa9\x66\xc3\x86\x00\x04\x07\x00\x00\x13\x62\x35\x03\x13\x8a\x35\x03\x00\x03\xff\xf8\xf3\x93\x13\xa9\x66\xc3\x86\x00\x01\x0d\x00\x02\x13\x9a\x35\x03\x13\xba\x35\x03\x13\xea\x35\x03\x00\x02\xff\xc8\x74\x9c\x6b\x05\xf4\x93\xcc\x00\x01\x2d\x00\x02\x13\x3a\x36\x03\x13\x7a\x36\x03\x13\xca\x36\x03\x00\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x03\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x68\x70\x6b\x2f\x68\x70\x6b\x3f\x2e\x63\x61\x70\x6e\x70\x51\x04\x01\x01\xfd\x08\x86\x90\x0a\x13\x21\xa6\x11\x01\x22\x07\x48\x70\x6b\x51\x08\x01\x02\xff\xce\x9d\xfc\x8c\xff\x96\x70\xac\x00\x51\x10\x02\x01\x41\x18\x01\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x14\x02\x01\x41\x24\x01\x00\x03\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\xff\x63\x61\x70\x6e\x70\x2f\x63\x2b\x03\x2b\x2e\x63\x61\x70\x6e\x70\x3a\x61\x6c\x6c\x6f\x77\x43\x61\x6e\x63\x65\x6c\x6c\x61\x74\x69\x6f\x01\x6e\x50\x01\x01\x00\x03\xff\x63\x61\x70\x6e\x70\x2f\x63\x2b\x01\x2b\x2e\x63\x61\x70\x6e\x70\x00\x51\x0c\x01\x01\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x11\x11\x52\xff\xce\x91\xf1\xfe\x79\xa7\x64\xf2\x00\x11\x11\x2a\xff\xce\x9d\xfc\x8c\xff\x96\x70\xac\x00\x11\x0d\x92\xff\x6e\x61\x6d\x65\x73\x70\x61\x63\x00\x01\x65\x0f\x6e\x61\x6d\x65\xff\x61\x6c\x6c\x6f\x77\x43\x61\x6e\x01\x63\x65\x6c\x6c\x61\x74\x69\x6f\x01\x6e\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x18\x01\x01\x0c\x00\x00\x11\x01\x9a\xff\x63\x61\x70\x6e\x70\x3a\x3a\x61\x01\x6e\x6e\x6f\x74\x61\x74\x69\x6f\x03\x6e\x73\x00\x00\x50\x01\x01\x11\x01\xd2\xff\x63\x61\x70\x6e\x70\x2f\x63\x2b\x02\x2b\x2e\x63\x61\x70\x6e\x70\x3a\x6e\x61\x6d\x65\x73\x70\x61\x63\x01\x65\x50\x03\x01\x01\x0c\x00\x02\x31\x01\x02\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x00\x11\x01\x17\x51\x04\x01\x01\xff\x5a\xfd\x0f\x06\x75\xe5\xcb\x8c\x00\x11\x01\x92\xff\x63\x61\x70\x61\x62\x69\x6c\x69\x01\x74\x79\x56\x65\x72\x73\x69\x6f\x01\x6e\x11\x01\x87\x51\x08\x03\x05\x01\x01\xff\xf7\x08\xfa\x7b\xe5\x7b\xa7\x86\x01\xd1\x3d\x88\xe1\x93\x21\xdb\xc9\x11\x31\x6a\x00\x02\x11\x29\x07\x00\x00\xff\x99\x11\x3c\xd9\xd4\x2f\xe3\xe3\x01\x1a\x41\x05\xd9\x7f\xa6\x31\xfb\x11\x1d\xaa\x00\x02\x11\x19\x07\xff\x67\x65\x74\x54\x68\x65\x53\x63\x00\x0f\x68\x65\x6d\x61\x40\x01\xff\x67\x65\x74\x52\x65\x66\x6c\x65\x01\x63\x74\x69\x6f\x6e\x56\x65\x72\x0f\x73\x69\x6f\x6e\x40\x01\x11\x01\x07\x50\x01\x01\x31\x01\x02\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x00\x11\x01\x17\x51\x04\x01\x01\xff\xd0\x02\x2f\x96\x1c\xae\x50\xb7\x00\x11\x01\x92\xff\x63\x61\x70\x61\x62\x69\x6c\x69\x01\x74\x79\x56\x65\x72\x73\x69\x6f\x01\x6e\x31\x01\xc7\x02\x51\x2c\x03\x05\x01\x01\xff\xa8\xf5\x5e\x25\x29\x54\x03\x92\x01\xb0\x61\xc9\x74\xdb\x4b\xd3\xeb\x13\x51\x01\x52\x00\x02\x13\x49\x01\x07\x01\x0a\xff\xe7\xdf\x90\xb9\x1b\x2e\x24\xcd\x01\xbb\x75\x95\x12\x8c\x51\x48\xe9\x13\x3d\x01\xa2\x00\x02\x13\x39\x01\x07\x01\x08\xff\x65\x80\xf3\x40\xed\xfd\xf6\xa8\x01\xf9\x5d\x59\xa9\x46\x01\xef\x8a\x13\x2d\x01\x9a\x00\x02\x13\x29\x01\x07\x01\x04\xff\xe4\xb3\xb1\xaf\xe9\x8b\xb1\xb9\x01\x3f\xc9\x48\xdf\x92\x66\xca\xa0\x13\x1d\x01\x52\x00\x02\x13\x15\x01\x07\x01\x06\xff\x82\x07\xd9\x2c\x0b\xe6\x82\xe6\x01\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x13\x09\x01\x8a\x00\x02\x13\x05\x01\x07\x01\x07\xff\xc1\xf6\x31\x65\xe8\x7f\x83\xcd\x01\x32\xfa\x5b\x64\x42\x16\x15\xa8\x11\xf9\x72\x00\x02\x11\xf1\x07\x01\x05\xff\x1c\xe6\x91\xdb\x40\x16\x6a\xb1\x01\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x11\xe5\x62\x00\x02\x11\xdd\x07\x00\x00\xff\x5a\x40\xb3\x14\xcf\x14\x7d\xd7\x01\xb3\xf6\x7c\xa5\x4d\x43\x68\xcc\x11\xd1\x92\x00\x02\x11\xcd\x07\x01\x09\xff\xa1\xab\x80\x26\xd3\x5c\xe0\xda\x01\x89\xe6\xc0\x56\x8e\x64\x47\xfd\x11\xc1\xa2\x00\x02\x11\xbd\x07\x01\x03\xff\xc7\x27\x3e\x49\x10\x9a\xe2\x9f\x01\x00\x39\xf6\x26\x5f\xd8\x74\x94\x11\xb1\x4a\x00\x02\x11\xa9\x07\x01\x02\xff\x7b\x91\xc4\x6c\x23\x72\xe8\xc5\x01\x01\xa4\xd0\xba\x9d\x71\x98\xce\x11\x9d\x4a\x00\x02\x11\x95\x07\xff\x6c\x69\x73\x74\x4e\x6f\x64\x65\x00\x01\x73\x40\x01\xff\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x47\x65\x74\x56\x61\x6c\x07\x75\x65\x73\x40\x01\xff\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x53\x65\x74\x56\x61\x6c\x03\x75\x65\x40\x01\xff\x73\x75\x62\x73\x63\x72\x69\x62\x00\x01\x65\x40\x01\xff\x64\x69\x73\x63\x6f\x6e\x6e\x65\x01\x63\x74\x44\x65\x76\x69\x63\x65\x00\x00\x40\x01\xff\x6c\x69\x73\x74\x4e\x6f\x64\x65\x00\x1f\x73\x4a\x73\x6f\x6e\x40\x01\xff\x75\x6e\x73\x75\x62\x73\x63\x72\x00\x07\x69\x62\x65\x40\x01\xff\x67\x65\x74\x53\x65\x73\x73\x69\x01\x6f\x6e\x56\x65\x72\x73\x69\x6f\x01\x6e\x40\x01\xff\x64\x65\x70\x72\x65\x63\x61\x74\x01\x65\x64\x53\x65\x74\x56\x61\x6c\x07\x75\x65\x32\x40\x01\xff\x73\x65\x74\x56\x61\x6c\x75\x65\x00\x00\x00\x40\x01\xff\x67\x65\x74\x56\x61\x6c\x75\x65\x00\x00\x00\x40\x01\x11\x01\x07\x50\x01\x01\x31\x01\x5a\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x04\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x68\x70\x6b\x2f\x68\x70\x6b\x2e\x63\x61\x70\x6e\x70\x3a\x48\x03\x70\x6b\x11\x01\x07\x50\x01\x01\x11\x01\x07\x50\x03\x05\x11\x01\x27\x51\x08\x01\x01\xff\x76\xc7\x4b\x10\x68\x2e\xa5\xf9\x00\x00\x00\xff\x5c\xa5\xa4\x2d\x58\x45\xd4\xb9\x00\x00\x00\x31\x01\xa2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x54\x68\x65\x53\x63\x68\x65\x6d\x61\x24\x50\x61\x72\x07\x61\x6d\x73\x31\x01\xfa\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x43\x61\x70\x3f\x53\x63\x68\x65\x6d\x61\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x3a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x52\x00\x00\x51\x2c\x03\x01\x51\x48\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x74\x68\x65\x53\x63\x68\x65\x6d\x00\x01\x61\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x00\x01\x01\x0e\x00\x01\x31\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x54\x68\x65\x53\x63\x68\x65\x6d\x61\x24\x52\x65\x73\x0f\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x52\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x74\x68\x65\x53\x63\x68\x65\x6d\x00\x01\x61\x01\x10\xff\xcf\x85\xeb\x76\x7a\xef\x31\xcb\x00\x00\x01\x01\x10\x00\x01\x31\x01\xe2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x50\x61\x72\x07\x61\x6d\x73\x31\x01\xea\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x67\x65\x74\x52\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x52\x65\x73\x0f\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x42\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x7f\x76\x65\x72\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x11\x01\xc2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x00\x11\x01\x37\x51\x0c\x01\x01\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x11\x11\x52\xff\x42\xc2\x0f\xfa\xbb\x55\xbf\xde\x00\x11\x11\x5a\xff\xae\x57\x13\x04\xe3\x1d\x8e\xf3\x00\x11\x11\x5a\xff\x50\x61\x72\x61\x6d\x65\x74\x65\x00\x01\x72\xff\x4e\x65\x73\x74\x65\x64\x4e\x6f\x00\x03\x64\x65\xff\x53\x6f\x75\x72\x63\x65\x49\x6e\x00\x03\x66\x6f\x31\x01\x17\x03\x51\x38\x03\x04\x00\x00\x04\x01\x00\x00\x13\x79\x01\x1a\x00\x00\x53\x74\x01\x03\x01\x53\x80\x01\x02\x01\x01\x01\x14\x01\x01\x00\x00\x13\x7d\x01\x62\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\x01\x11\x02\x02\x14\x01\x02\x00\x00\x13\x85\x01\xc2\x00\x00\x53\x88\x01\x03\x01\x53\x94\x01\x02\x01\x11\x03\x02\x14\x01\x03\x00\x00\x13\x91\x01\x42\x00\x00\x53\x8c\x01\x03\x01\x53\x98\x01\x02\x01\x11\x06\x01\x14\x01\x04\x00\x00\x13\x95\x01\x62\x00\x00\x53\x94\x01\x03\x01\x53\xb0\x01\x02\x01\x11\x07\x02\x14\x01\x05\x00\x00\x13\xad\x01\x62\x00\x00\x53\xac\x01\x03\x01\x53\xc8\x01\x02\x01\x0d\x08\xff\xff\x14\x01\x06\x00\x00\x13\xc5\x01\x2a\x00\x00\x53\xc0\x01\x03\x01\x53\xcc\x01\x02\x01\x0d\x09\xfe\xff\x01\x01\xff\x35\x44\xfb\x37\x9b\xb1\xa0\x9e\x00\x13\xc9\x01\x3a\x00\x02\x0d\x0a\xfd\xff\x01\x01\xff\x98\xf5\x33\x43\x36\xb3\x4a\xb5\x00\x13\xb1\x01\x2a\x00\x02\x0d\x0b\xfc\xff\x01\x01\xff\x8f\x21\xc2\xf0\xcf\x53\x27\xe8\x00\x13\x99\x01\x52\x00\x02\x0d\x0c\xfb\xff\x01\x01\xff\x20\x94\x0d\x7a\xac\xa5\x8a\xb1\x00\x13\x85\x01\x32\x00\x02\x0d\x0d\xfa\xff\x01\x01\xff\x90\x02\x0a\x40\xd4\x19\x16\xec\x00\x13\x6d\x01\x5a\x00\x02\x11\x04\x05\x14\x01\x20\x00\x00\x13\x59\x01\x5a\x00\x00\x53\x58\x01\x03\x01\x53\x74\x01\x02\x01\x31\x05\x20\x01\x14\x01\x21\x00\x00\x13\x71\x01\x52\x00\x00\x53\x70\x01\x03\x01\x53\x7c\x01\x02\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x64\x69\x73\x70\x6c\x61\x79\x4e\x00\x07\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x64\x69\x73\x70\x6c\x61\x79\x4e\x02\x61\x6d\x65\x50\x72\x65\x66\x69\x78\x4c\x65\x6e\x67\x74\x68\x00\x01\x08\x00\x02\x01\x08\x00\x01\x7f\x73\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x6e\x65\x73\x74\x65\x64\x4e\x6f\x00\x07\x64\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\xc2\x0f\xfa\xbb\x55\xbf\xde\x00\x00\x01\x01\x0e\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\x0f\x66\x69\x6c\x65\x00\x06\x3f\x73\x74\x72\x75\x63\x74\x0f\x65\x6e\x75\x6d\xff\x69\x6e\x74\x65\x72\x66\x61\x63\x00\x01\x65\x1f\x63\x6f\x6e\x73\x74\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x03\x6f\x6e\xff\x70\x61\x72\x61\x6d\x65\x74\x65\x00\x03\x72\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x00\x01\x01\x0e\x00\x01\xff\x69\x73\x47\x65\x6e\x65\x72\x69\x00\x01\x63\x01\x01\x00\x02\x01\x01\x00\x01\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x45\x6c\x65\x6d\x65\x3f\x6e\x74\x53\x69\x7a\x65\x11\x01\x07\x50\x01\x01\x11\x01\xc7\x51\x20\x01\x02\x00\x00\x11\x59\x32\x00\x00\x01\x01\x11\x51\x22\x00\x00\x01\x02\x11\x49\x2a\x00\x00\x01\x03\x11\x41\x4a\x00\x00\x01\x04\x11\x3d\x52\x00\x00\x01\x05\x11\x39\x5a\x00\x00\x01\x06\x11\x35\x42\x00\x00\x01\x07\x11\x2d\x82\x00\x00\x1f\x65\x6d\x70\x74\x79\x07\x62\x69\x74\x0f\x62\x79\x74\x65\xff\x74\x77\x6f\x42\x79\x74\x65\x73\x00\x00\x00\xff\x66\x6f\x75\x72\x42\x79\x74\x65\x00\x01\x73\xff\x65\x69\x67\x68\x74\x42\x79\x74\x00\x03\x65\x73\x7f\x70\x6f\x69\x6e\x74\x65\x72\xff\x69\x6e\x6c\x69\x6e\x65\x43\x6f\x01\x6d\x70\x6f\x73\x69\x74\x65\x00\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x00\x00\x11\x01\x17\x51\x04\x01\x01\xff\x12\xc7\xfe\x7c\xbe\x4c\xb1\x97\x00\x11\x01\x7a\xff\x6e\x6f\x44\x69\x73\x63\x72\x69\x00\x3f\x6d\x69\x6e\x61\x6e\x74\x31\x01\x8f\x01\x51\x1c\x03\x04\x00\x00\x04\x01\x00\x00\x11\xb5\x2a\x00\x00\x51\xb0\x03\x01\x51\xbc\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\xb9\x52\x00\x00\x51\xb8\x03\x01\x51\xc4\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\xc1\x62\x00\x00\x51\xc0\x03\x01\x51\xdc\x02\x01\x11\x03\x01\x14\x01\x03\x01\x01\x11\xd9\x92\x00\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\x0d\x04\xff\xff\x01\x01\xff\x6f\x74\xb4\x6b\x47\x05\x23\xc4\x00\x11\xe5\x2a\x00\x02\x0d\x05\xfe\xff\x01\x01\xff\x11\x1d\xdb\x68\xdb\xcd\xfc\xca\x00\x11\xcd\x32\x00\x02\x01\x06\x01\x01\xff\xe6\x0b\x87\x87\xc2\xd5\x90\xbb\x00\x11\xb5\x42\x00\x02\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\x65\x4f\x72\x64\x65\x00\x01\x72\x01\x07\x00\x02\x01\x07\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\x69\x01\x6e\x61\x6e\x74\x56\x61\x6c\x75\x01\x65\x01\x07\x00\x02\x0d\x07\xff\xff\x00\x01\x0f\x73\x6c\x6f\x74\x1f\x67\x72\x6f\x75\x70\x7f\x6f\x72\x64\x69\x6e\x61\x6c\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x3f\x73\x74\x72\x75\x63\x74\x31\x01\x8f\x01\x51\x1c\x03\x04\x10\x07\x14\x01\x07\x00\x00\x11\xb5\x72\x00\x00\x51\xb4\x03\x01\x51\xc0\x02\x01\x11\x01\x0c\x14\x01\x08\x00\x00\x11\xbd\x6a\x00\x00\x51\xbc\x03\x01\x51\xc8\x02\x01\x11\x02\x0d\x14\x01\x09\x00\x00\x11\xc5\xb2\x00\x00\x51\xc8\x03\x01\x51\xd4\x02\x01\x11\x03\xe0\x14\x01\x0a\x00\x00\x11\xd1\x42\x00\x00\x51\xcc\x03\x01\x51\xd8\x02\x01\x11\x04\x0f\x14\x01\x0b\x00\x00\x11\xd5\x92\x00\x00\x51\xd8\x03\x01\x51\xe4\x02\x01\x11\x05\x08\x14\x01\x0c\x00\x00\x11\xe1\x9a\x00\x00\x51\xe4\x03\x01\x51\xf0\x02\x01\x11\x06\x03\x14\x01\x0d\x00\x00\x11\xed\x3a\x00\x00\x51\xe8\x03\x01\x53\x04\x01\x02\x01\xff\x64\x61\x74\x61\x57\x6f\x72\x64\x00\x1f\x43\x6f\x75\x6e\x74\x01\x07\x00\x02\x01\x07\x00\x01\xff\x70\x6f\x69\x6e\x74\x65\x72\x43\x00\x0f\x6f\x75\x6e\x74\x01\x07\x00\x02\x01\x07\x00\x01\xff\x70\x72\x65\x66\x65\x72\x72\x65\x01\x64\x4c\x69\x73\x74\x45\x6e\x63\x1f\x6f\x64\x69\x6e\x67\x01\x0f\xff\x26\x19\x52\xba\x7d\x8f\x95\xd1\x00\x00\x01\x01\x0f\x00\x01\x7f\x69\x73\x47\x72\x6f\x75\x70\x01\x01\x00\x02\x01\x01\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\x69\x01\x6e\x61\x6e\x74\x43\x6f\x75\x6e\x01\x74\x01\x07\x00\x02\x01\x07\x00\x01\xff\x64\x69\x73\x63\x72\x69\x6d\x69\x01\x6e\x61\x6e\x74\x4f\x66\x66\x73\x03\x65\x74\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x66\x69\x65\x6c\x64\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x45\x6e\x75\x6d\x65\x0f\x72\x61\x6e\x74\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x49\x52\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x51\x62\x00\x00\x51\x50\x03\x01\x51\x6c\x02\x01\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\x65\x4f\x72\x64\x65\x00\x01\x72\x01\x07\x00\x02\x01\x07\x00\x01\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x0f\x65\x6e\x75\x6d\x11\x01\x3f\x51\x04\x03\x04\x10\x03\x14\x01\x0e\x00\x00\x11\x0d\x5a\x00\x00\x51\x0c\x03\x01\x51\x28\x02\x01\xff\x65\x6e\x75\x6d\x65\x72\x61\x6e\x00\x03\x74\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x4d\x9a\x54\xdc\xeb\x7c\x8a\x97\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xd2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4d\x65\x74\x68\x6f\x01\x64\x11\x01\x07\x50\x01\x01\x31\x01\xc7\x01\x51\x20\x03\x04\x00\x00\x04\x01\x00\x00\x11\xd1\x2a\x00\x00\x51\xcc\x03\x01\x51\xd8\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\xd5\x52\x00\x00\x51\xd4\x03\x01\x51\xe0\x02\x01\x11\x03\x01\x14\x01\x02\x00\x00\x11\xdd\x82\x00\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\x11\x05\x02\x14\x01\x03\x00\x00\x11\xe5\x8a\x00\x00\x51\xe8\x03\x01\x11\x02\x02\x11\x07\x01\x14\x01\x04\x00\x00\x11\x22\x02\x00\x00\x11\x3a\x02\x11\x82\x02\x11\x04\x02\x14\x01\x05\x00\x00\x11\xa2\x02\x00\x00\x11\xba\x02\x11\xe2\x02\x11\x06\x03\x14\x01\x06\x00\x00\x13\x02\x01\x02\x00\x00\x13\x1a\x01\x02\x13\x42\x01\x02\x11\x02\x04\x14\x01\x07\x00\x00\x13\x62\x01\x02\x00\x00\x13\x82\x01\x02\x13\xca\x01\x02\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x6f\x64\x65\x4f\x72\x64\x65\x00\x01\x72\x01\x07\x00\x02\x01\x07\x00\x01\xff\x70\x61\x72\x61\x6d\x53\x74\x72\x01\x75\x63\x74\x54\x79\x70\x65\x00\x01\x09\x00\x02\x01\x09\x00\x01\xff\x72\x65\x73\x75\x6c\x74\x53\x74\x01\x72\x75\x63\x74\x54\x79\x70\x65\x00\x00\x01\x09\x00\x02\x50\x02\x01\x01\x09\x00\x01\x11\x01\x62\xff\x61\x6e\x6e\x6f\x74\x61\x74\x69\x00\x07\x6f\x6e\x73\x50\x03\x01\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x00\x01\x50\x02\x01\x01\x0e\x00\x01\x11\x01\x5a\xff\x70\x61\x72\x61\x6d\x42\x72\x61\x00\x03\x6e\x64\x50\x03\x01\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\x62\xff\x72\x65\x73\x75\x6c\x74\x42\x72\x00\x07\x61\x6e\x64\x50\x03\x01\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x50\x02\x01\x01\x10\x00\x01\x11\x01\x9a\xff\x69\x6d\x70\x6c\x69\x63\x69\x74\x01\x50\x61\x72\x61\x6d\x65\x74\x65\x03\x72\x73\x50\x03\x01\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xb1\xa3\x0f\xf1\xcc\x1b\x52\xb9\x00\x00\x01\x50\x02\x01\x01\x0e\x00\x01\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x53\x75\x70\x65\x72\x1f\x63\x6c\x61\x73\x73\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x1a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x69\x6e\x74\x65\x72\x66\x61\x63\x01\x65\x11\x01\x77\x51\x08\x03\x04\x10\x03\x14\x01\x0f\x00\x00\x11\x29\x42\x00\x00\x51\x24\x03\x01\x51\x40\x02\x01\x11\x01\x04\x14\x01\x1f\x00\x00\x11\x3d\x6a\x00\x00\x51\x3c\x03\x01\x51\x58\x02\x01\x7f\x6d\x65\x74\x68\x6f\x64\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xbf\x80\x4d\x33\x3b\xe2\xcc\x95\x00\x01\x01\x0e\x00\x01\xff\x73\x75\x70\x65\x72\x63\x6c\x61\x00\x0f\x73\x73\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xf8\xd7\xa4\xd0\x9e\x2a\x96\xa9\x00\x00\x01\x01\x0e\x00\x01\x11\x01\xc2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x00\x11\x01\x07\x50\x01\x01\x31\x01\x2f\x04\x51\x4c\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x13\x05\x02\x2a\x00\x00\x52\x02\x03\x01\x53\x0c\x02\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x13\x09\x02\x2a\x00\x00\x53\x04\x02\x03\x01\x53\x10\x02\x02\x01\x0d\x02\xfd\xff\x14\x01\x02\x00\x00\x13\x0d\x02\x2a\x00\x00\x53\x08\x02\x03\x01\x53\x14\x02\x02\x01\x0d\x03\xfc\xff\x14\x01\x03\x00\x00\x13\x11\x02\x32\x00\x00\x53\x0c\x02\x03\x01\x53\x18\x02\x02\x01\x0d\x04\xfb\xff\x14\x01\x04\x00\x00\x13\x15\x02\x32\x00\x00\x53\x10\x02\x03\x01\x53\x1c\x02\x02\x01\x0d\x05\xfa\xff\x14\x01\x05\x00\x00\x13\x19\x02\x32\x00\x00\x53\x14\x02\x03\x01\x53\x20\x02\x02\x01\x0d\x06\xf9\xff\x14\x01\x06\x00\x00\x13\x1d\x02\x32\x00\x00\x53\x18\x02\x03\x01\x53\x24\x02\x02\x01\x0d\x07\xf8\xff\x14\x01\x07\x00\x00\x13\x21\x02\x3a\x00\x00\x53\x1c\x02\x03\x01\x53\x28\x02\x02\x01\x0d\x08\xf7\xff\x14\x01\x08\x00\x00\x13\x25\x02\x3a\x00\x00\x53\x20\x02\x03\x01\x53\x2c\x02\x02\x01\x0d\x09\xf6\xff\x14\x01\x09\x00\x00\x13\x29\x02\x3a\x00\x00\x53\x24\x02\x03\x01\x53\x30\x02\x02\x01\x0d\x0a\xf5\xff\x14\x01\x0a\x00\x00\x13\x2d\x02\x42\x00\x00\x53\x28\x02\x03\x01\x53\x34\x02\x02\x01\x0d\x0b\xf4\xff\x14\x01\x0b\x00\x00\x13\x31\x02\x42\x00\x00\x53\x2c\x02\x03\x01\x53\x38\x02\x02\x01\x0d\x0c\xf3\xff\x14\x01\x0c\x00\x00\x13\x35\x02\x2a\x00\x00\x53\x30\x02\x03\x01\x53\x3c\x02\x02\x01\x0d\x0d\xf2\xff\x14\x01\x0d\x00\x00\x13\x39\x02\x2a\x00\x00\x53\x34\x02\x03\x01\x53\x40\x02\x02\x01\x0d\x0e\xf1\xff\x01\x01\xff\x97\xea\x60\x0a\x25\x39\xe7\x87\x00\x13\x3d\x02\x2a\x00\x02\x0d\x0f\xf0\xff\x01\x01\xff\xa9\x87\x7f\x1a\x71\x78\x0e\x9e\x00\x13\x25\x02\x2a\x00\x02\x0d\x10\xef\xff\x01\x01\xff\xd3\xc6\x4c\xef\x60\x6f\x3a\xac\x00\x13\x0d\x02\x3a\x00\x02\x0d\x11\xee\xff\x01\x01\xff\xbf\x0c\xfb\xf7\x69\xca\x8b\xed\x00\x13\xf5\x01\x52\x00\x02\x0d\x12\xed\xff\x01\x01\xff\xf1\x49\x3e\xa2\xe8\x3f\x57\xc2\x00\x13\xe1\x01\x5a\x00\x02\x0f\x76\x6f\x69\x64\x00\x06\x0f\x62\x6f\x6f\x6c\x00\x06\x0f\x69\x6e\x74\x38\x00\x06\x1f\x69\x6e\x74\x31\x36\x00\x06\x1f\x69\x6e\x74\x33\x32\x00\x06\x1f\x69\x6e\x74\x36\x34\x00\x06\x1f\x75\x69\x6e\x74\x38\x00\x06\x3f\x75\x69\x6e\x74\x31\x36\x00\x06\x3f\x75\x69\x6e\x74\x33\x32\x00\x06\x3f\x75\x69\x6e\x74\x36\x34\x00\x06\x7f\x66\x6c\x6f\x61\x74\x33\x32\x00\x06\x7f\x66\x6c\x6f\x61\x74\x36\x34\x00\x06\x0f\x74\x65\x78\x74\x00\x06\x0f\x64\x61\x74\x61\x00\x06\x0f\x6c\x69\x73\x74\x0f\x65\x6e\x75\x6d\x3f\x73\x74\x72\x75\x63\x74\xff\x69\x6e\x74\x65\x72\x66\x61\x63\x00\x01\x65\xff\x61\x6e\x79\x50\x6f\x69\x6e\x74\x00\x03\x65\x72\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x56\x61\x6c\x75\x65\x00\x00\x11\x01\x07\x50\x01\x01\x31\x01\x2f\x04\x51\x4c\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x13\x05\x02\x2a\x00\x00\x52\x02\x03\x01\x53\x0c\x02\x02\x01\x1d\x01\xfe\xff\x10\x14\x01\x01\x00\x00\x13\x09\x02\x2a\x00\x00\x53\x04\x02\x03\x01\x53\x10\x02\x02\x01\x1d\x02\xfd\xff\x02\x14\x01\x02\x00\x00\x13\x0d\x02\x2a\x00\x00\x53\x08\x02\x03\x01\x53\x14\x02\x02\x01\x1d\x03\xfc\xff\x01\x14\x01\x03\x00\x00\x13\x11\x02\x32\x00\x00\x53\x0c\x02\x03\x01\x53\x18\x02\x02\x01\x1d\x04\xfb\xff\x01\x14\x01\x04\x00\x00\x13\x15\x02\x32\x00\x00\x53\x10\x02\x03\x01\x53\x1c\x02\x02\x01\x1d\x05\xfa\xff\x01\x14\x01\x05\x00\x00\x13\x19\x02\x32\x00\x00\x53\x14\x02\x03\x01\x53\x20\x02\x02\x01\x1d\x06\xf9\xff\x02\x14\x01\x06\x00\x00\x13\x1d\x02\x32\x00\x00\x53\x18\x02\x03\x01\x53\x24\x02\x02\x01\x1d\x07\xf8\xff\x01\x14\x01\x07\x00\x00\x13\x21\x02\x3a\x00\x00\x53\x1c\x02\x03\x01\x53\x28\x02\x02\x01\x1d\x08\xf7\xff\x01\x14\x01\x08\x00\x00\x13\x25\x02\x3a\x00\x00\x53\x20\x02\x03\x01\x53\x2c\x02\x02\x01\x1d\x09\xf6\xff\x01\x14\x01\x09\x00\x00\x13\x29\x02\x3a\x00\x00\x53\x24\x02\x03\x01\x53\x30\x02\x02\x01\x1d\x0a\xf5\xff\x01\x14\x01\x0a\x00\x00\x13\x2d\x02\x42\x00\x00\x53\x28\x02\x03\x01\x53\x34\x02\x02\x01\x1d\x0b\xf4\xff\x01\x14\x01\x0b\x00\x00\x13\x31\x02\x42\x00\x00\x53\x2c\x02\x03\x01\x53\x38\x02\x02\x01\x0d\x0c\xf3\xff\x14\x01\x0c\x00\x00\x13\x35\x02\x2a\x00\x00\x53\x30\x02\x03\x01\x53\x3c\x02\x02\x01\x0d\x0d\xf2\xff\x14\x01\x0d\x00\x00\x13\x39\x02\x2a\x00\x00\x53\x34\x02\x03\x01\x53\x40\x02\x02\x01\x0d\x0e\xf1\xff\x14\x01\x0e\x00\x00\x13\x3d\x02\x2a\x00\x00\x53\x38\x02\x03\x01\x53\x44\x02\x02\x01\x1d\x0f\xf0\xff\x01\x14\x01\x0f\x00\x00\x13\x41\x02\x2a\x00\x00\x53\x3c\x02\x03\x01\x53\x48\x02\x02\x01\x0d\x10\xef\xff\x14\x01\x10\x00\x00\x13\x45\x02\x3a\x00\x00\x53\x40\x02\x03\x01\x53\x4c\x02\x02\x01\x0d\x11\xee\xff\x14\x01\x11\x00\x00\x13\x49\x02\x52\x00\x00\x53\x48\x02\x03\x01\x53\x54\x02\x02\x01\x0d\x12\xed\xff\x14\x01\x12\x00\x00\x13\x51\x02\x5a\x00\x00\x53\x50\x02\x03\x01\x53\x5c\x02\x02\x01\x0f\x76\x6f\x69\x64\x00\x06\x0f\x62\x6f\x6f\x6c\x01\x01\x00\x02\x01\x01\x00\x01\x0f\x69\x6e\x74\x38\x01\x02\x00\x02\x01\x02\x00\x01\x1f\x69\x6e\x74\x31\x36\x01\x03\x00\x02\x01\x03\x00\x01\x1f\x69\x6e\x74\x33\x32\x01\x04\x00\x02\x01\x04\x00\x01\x1f\x69\x6e\x74\x36\x34\x01\x05\x00\x02\x01\x05\x00\x01\x1f\x75\x69\x6e\x74\x38\x01\x06\x00\x02\x01\x06\x00\x01\x3f\x75\x69\x6e\x74\x31\x36\x01\x07\x00\x02\x01\x07\x00\x01\x3f\x75\x69\x6e\x74\x33\x32\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x75\x69\x6e\x74\x36\x34\x01\x09\x00\x02\x01\x09\x00\x01\x7f\x66\x6c\x6f\x61\x74\x33\x32\x01\x0a\x00\x02\x01\x0a\x00\x01\x7f\x66\x6c\x6f\x61\x74\x36\x34\x01\x0b\x00\x02\x01\x0b\x00\x01\x0f\x74\x65\x78\x74\x01\x0c\x00\x02\x01\x0c\x00\x01\x0f\x64\x61\x74\x61\x01\x0d\x00\x02\x01\x0d\x00\x01\x0f\x6c\x69\x73\x74\x01\x12\x00\x02\x01\x12\x00\x01\x0f\x65\x6e\x75\x6d\x01\x07\x00\x02\x01\x07\x00\x01\x3f\x73\x74\x72\x75\x63\x74\x01\x12\x00\x02\x01\x12\x00\x01\xff\x69\x6e\x74\x65\x72\x66\x61\x63\x00\x01\x65\x00\x06\xff\x61\x6e\x79\x50\x6f\x69\x6e\x74\x00\x03\x65\x72\x01\x12\x00\x02\x01\x12\x00\x01\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x1f\x63\x6f\x6e\x73\x74\x11\x01\x77\x51\x08\x03\x04\x10\x03\x14\x01\x10\x00\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x04\x14\x01\x11\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x00\x01\x01\x10\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x61\x6e\x6e\x6f\x74\x61\x74\x69\x03\x6f\x6e\x31\x01\xdf\x02\x51\x34\x03\x04\x10\x03\x14\x01\x12\x00\x00\x13\x5d\x01\x2a\x00\x00\x53\x58\x01\x03\x01\x53\x64\x01\x02\x01\x11\x01\x70\x14\x01\x13\x00\x00\x13\x61\x01\x62\x00\x00\x53\x60\x01\x03\x01\x53\x6c\x01\x02\x01\x11\x02\x71\x14\x01\x14\x00\x00\x13\x69\x01\x6a\x00\x00\x53\x68\x01\x03\x01\x53\x74\x01\x02\x01\x11\x03\x72\x14\x01\x15\x00\x00\x13\x71\x01\x62\x00\x00\x53\x70\x01\x03\x01\x53\x7c\x01\x02\x01\x11\x04\x73\x14\x01\x16\x00\x00\x13\x79\x01\x8a\x00\x00\x53\x7c\x01\x03\x01\x53\x88\x01\x02\x01\x11\x05\x74\x14\x01\x17\x00\x00\x13\x85\x01\x72\x00\x00\x53\x84\x01\x03\x01\x53\x90\x01\x02\x01\x11\x06\x75\x14\x01\x18\x00\x00\x13\x8d\x01\x6a\x00\x00\x53\x8c\x01\x03\x01\x53\x98\x01\x02\x01\x11\x07\x76\x14\x01\x19\x00\x00\x13\x95\x01\x6a\x00\x00\x53\x94\x01\x03\x01\x53\xa0\x01\x02\x01\x11\x08\x77\x14\x01\x1a\x00\x00\x13\x9d\x01\x6a\x00\x00\x53\x9c\x01\x03\x01\x53\xa8\x01\x02\x01\x11\x09\x78\x14\x01\x1b\x00\x00\x13\xa5\x01\x8a\x00\x00\x53\xa8\x01\x03\x01\x53\xb4\x01\x02\x01\x11\x0a\x79\x14\x01\x1c\x00\x00\x13\xb1\x01\x72\x00\x00\x53\xb0\x01\x03\x01\x53\xbc\x01\x02\x01\x11\x0b\x7a\x14\x01\x1d\x00\x00\x13\xb9\x01\x6a\x00\x00\x53\xb8\x01\x03\x01\x53\xc4\x01\x02\x01\x11\x0c\x7b\x14\x01\x1e\x00\x00\x13\xc1\x01\x92\x00\x00\x53\xc4\x01\x03\x01\x53\xd0\x01\x02\x01\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x46\x00\x07\x69\x6c\x65\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x43\x00\x0f\x6f\x6e\x73\x74\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x45\x00\x07\x6e\x75\x6d\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x45\x01\x6e\x75\x6d\x65\x72\x61\x6e\x74\x00\x00\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x53\x00\x1f\x74\x72\x75\x63\x74\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x46\x00\x0f\x69\x65\x6c\x64\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x55\x00\x0f\x6e\x69\x6f\x6e\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x47\x00\x0f\x72\x6f\x75\x70\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x49\x01\x6e\x74\x65\x72\x66\x61\x63\x65\x00\x00\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x4d\x00\x1f\x65\x74\x68\x6f\x64\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x50\x00\x0f\x61\x72\x61\x6d\x01\x01\x00\x02\x01\x01\x00\x01\xff\x74\x61\x72\x67\x65\x74\x73\x41\x01\x6e\x6e\x6f\x74\x61\x74\x69\x6f\x01\x6e\x01\x01\x00\x02\x01\x01\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x4e\x65\x73\x74\x65\x64\x4e\x6f\x03\x64\x65\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x1a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\x6f\x74\x1f\x61\x74\x69\x6f\x6e\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x1a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x01\x02\x14\x01\x01\x00\x00\x11\x49\x32\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x11\x01\x01\x14\x01\x02\x00\x00\x11\x4d\x32\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x03\x69\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x00\x01\x01\x10\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x4e\x6f\x64\x65\x2e\x50\x61\x72\x61\x6d\x65\x74\x65\x01\x72\x11\x01\x07\x50\x01\x01\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x2a\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x0f\x6e\x61\x6d\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\x11\x01\xca\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x42\x72\x61\x6e\x64\x00\x00\x11\x01\x27\x51\x08\x01\x01\xff\xc9\x6b\x63\xa9\x85\x34\xd7\xab\x00\x11\x09\x32\xff\xfc\xe7\x9e\x96\x16\xcd\x63\xc8\x00\x11\x05\x42\x1f\x53\x63\x6f\x70\x65\x7f\x42\x69\x6e\x64\x69\x6e\x67\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x24\x02\x01\x3f\x73\x63\x6f\x70\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xc9\x6b\x63\xa9\x85\x34\xd7\xab\x00\x00\x01\x01\x0e\x00\x01\x11\x01\x9a\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x01\x68\x65\x6d\x61\x2e\x63\x61\x70\x03\x6e\x70\x11\x01\xc7\x51\x30\x01\x01\xff\x17\xa4\x23\xf9\x4c\xab\x82\xe6\x00\x11\x59\x2a\xff\x5f\xf4\x4a\x1f\xa4\x50\xad\x9a\x00\x11\x55\x32\xff\x4d\x9a\x54\xdc\xeb\x7c\x8a\x97\x00\x11\x51\x52\xff\xf8\xd7\xa4\xd0\x9e\x2a\x96\xa9\x00\x11\x51\x5a\xbf\x80\x4d\x33\x3b\xe2\xcc\x95\x11\x51\x3a\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x11\x4d\x2a\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x11\x49\x32\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x11\x45\x32\xff\x42\x75\x25\xab\x0d\x95\xc8\xf1\x00\x11\x41\x5a\xff\x26\x19\x52\xba\x7d\x8f\x95\xd1\x00\x11\x41\x62\xff\x63\x99\x83\x7d\x5b\x30\x5d\xd8\x00\x11\x41\x6a\xff\xce\xd7\x0a\x21\xf6\x46\xc5\xbf\x00\x11\x41\xaa\x0f\x4e\x6f\x64\x65\x1f\x46\x69\x65\x6c\x64\xff\x45\x6e\x75\x6d\x65\x72\x61\x6e\x00\x01\x74\xff\x53\x75\x70\x65\x72\x63\x6c\x61\x00\x03\x73\x73\x3f\x4d\x65\x74\x68\x6f\x64\x0f\x54\x79\x70\x65\x1f\x42\x72\x61\x6e\x64\x1f\x56\x61\x6c\x75\x65\xff\x41\x6e\x6e\x6f\x74\x61\x74\x69\x00\x03\x6f\x6e\xff\x45\x6c\x65\x6d\x65\x6e\x74\x53\x00\x07\x69\x7a\x65\xff\x43\x61\x70\x6e\x70\x56\x65\x72\x00\x0f\x73\x69\x6f\x6e\xff\x43\x6f\x64\x65\x47\x65\x6e\x65\x01\x72\x61\x74\x6f\x72\x52\x65\x71\x0f\x75\x65\x73\x74\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x14\x01\x01\x0c\x00\x00\x11\x01\x72\xff\x63\x61\x70\x6e\x70\x3a\x3a\x73\x00\x1f\x63\x68\x65\x6d\x61\x00\x00\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x42\x72\x61\x6e\x64\x3f\x2e\x53\x63\x6f\x70\x65\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x42\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x0d\x01\xff\xff\x14\x01\x01\x00\x00\x11\x49\x2a\x00\x00\x51\x44\x03\x01\x51\x60\x02\x01\x0d\x02\xfe\xff\x14\x01\x02\x00\x00\x11\x5d\x42\x00\x00\x51\x58\x03\x01\x51\x64\x02\x01\x7f\x73\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x0f\x62\x69\x6e\x64\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xfc\xe7\x9e\x96\x16\xcd\x63\xc8\x00\x00\x01\x01\x0e\x00\x01\x7f\x69\x6e\x68\x65\x72\x69\x74\x00\x06\x31\x01\x0a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x42\x72\x61\x6e\x64\x2e\x42\x69\x6e\x64\x69\x6e\x67\x00\x00\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x11\x29\x42\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x11\x2d\x2a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x7f\x75\x6e\x62\x6f\x75\x6e\x64\x00\x06\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x0f\x6c\x69\x73\x74\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x14\x01\x0e\x00\x00\x11\x0d\x62\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x65\x6c\x65\x6d\x65\x6e\x74\x54\x00\x07\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\x11\x01\xea\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x0f\x65\x6e\x75\x6d\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x0f\x00\x00\x11\x29\x3a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x15\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x3f\x73\x74\x72\x75\x63\x74\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x10\x00\x00\x11\x29\x3a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x16\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x12\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x69\x6e\x74\x65\x72\x66\x61\x63\x01\x65\x11\x01\x77\x51\x08\x03\x04\x10\x01\x14\x01\x11\x00\x00\x11\x29\x3a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x01\x01\x14\x01\x17\x00\x00\x11\x2d\x32\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x1f\x62\x72\x61\x6e\x64\x01\x10\xff\x2b\x42\x65\x60\xf0\x55\x34\x90\x00\x00\x01\x01\x10\x00\x01\x31\x01\x8a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x05\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\x75\x6e\x63\x6f\x6e\x73\x74\x72\x61\x69\x6e\x65\x64\x00\x00\x11\x01\xe7\x51\x10\x03\x04\x0c\xff\xff\x14\x01\x12\x00\x00\x11\x61\x42\x00\x00\x51\x5c\x03\x01\x51\x68\x02\x01\x0d\x01\xfe\xff\x14\x01\x19\x00\x00\x11\x65\x3a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x0d\x02\xfd\xff\x14\x01\x1a\x00\x00\x11\x69\x2a\x00\x00\x51\x64\x03\x01\x51\x70\x02\x01\x0d\x03\xfc\xff\x14\x01\x1b\x00\x00\x11\x6d\x5a\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x7f\x61\x6e\x79\x4b\x69\x6e\x64\x00\x06\x3f\x73\x74\x72\x75\x63\x74\x00\x06\x0f\x6c\x69\x73\x74\x00\x06\xff\x63\x61\x70\x61\x62\x69\x6c\x69\x00\x03\x74\x79\x00\x06\x31\x01\x6a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x04\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\x70\x61\x72\x61\x6d\x0f\x65\x74\x65\x72\x11\x01\x77\x51\x08\x03\x04\x10\x02\x14\x01\x13\x00\x00\x11\x29\x42\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x05\x14\x01\x14\x00\x00\x11\x2d\x7a\x00\x00\x51\x2c\x03\x01\x51\x38\x02\x01\x7f\x73\x63\x6f\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\xff\x70\x61\x72\x61\x6d\x65\x74\x65\x00\x3f\x72\x49\x6e\x64\x65\x78\x01\x07\x00\x02\x01\x07\x00\x01\x31\x01\xda\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x06\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x65\x72\x2e\x69\x6d\x70\x6c\x69\x63\x69\x74\x4d\x65\x74\x68\x6f\x64\x50\x61\x72\x61\x6d\x65\x74\x03\x65\x72\x11\x01\x3f\x51\x04\x03\x04\x10\x05\x14\x01\x18\x00\x00\x11\x0d\x7a\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x70\x61\x72\x61\x6d\x65\x74\x65\x00\x3f\x72\x49\x6e\x64\x65\x78\x01\x07\x00\x02\x01\x07\x00\x01\x31\x01\x1a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x54\x79\x70\x65\x2e\x61\x6e\x79\x50\x6f\x69\x6e\x74\x03\x65\x72\x11\x01\xaf\x51\x0c\x03\x04\x0c\xff\xff\x01\x01\xff\x56\x36\x59\xfe\x79\x5f\x3b\x8e\x00\x11\x45\x72\x00\x02\x0d\x01\xfe\xff\x01\x01\xff\x85\x4a\x61\xf4\x24\xf7\xd1\x9d\x00\x11\x31\x52\x00\x02\x0d\x02\xfd\xff\x01\x01\xff\x74\xe2\x56\x0c\x12\xc9\xef\xba\x00\x11\x1d\xc2\x00\x02\xff\x75\x6e\x63\x6f\x6e\x73\x74\x72\x00\x1f\x61\x69\x6e\x65\x64\xff\x70\x61\x72\x61\x6d\x65\x74\x65\x00\x01\x72\xff\x69\x6d\x70\x6c\x69\x63\x69\x74\x02\x4d\x65\x74\x68\x6f\x64\x50\x61\x72\x61\x6d\x65\x74\x65\x72\x00\x11\x01\xf2\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x1f\x2e\x73\x6c\x6f\x74\x11\x01\xe7\x51\x10\x03\x04\x10\x01\x14\x01\x04\x00\x00\x11\x61\x3a\x00\x00\x51\x5c\x03\x01\x51\x68\x02\x01\x11\x01\x02\x14\x01\x05\x00\x00\x11\x65\x2a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x11\x02\x03\x14\x01\x06\x00\x00\x11\x69\x6a\x00\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x03\x80\x14\x01\x0a\x00\x00\x11\x71\x9a\x00\x00\x51\x74\x03\x01\x51\x80\x02\x01\x3f\x6f\x66\x66\x73\x65\x74\x01\x08\x00\x02\x01\x08\x00\x01\x0f\x74\x79\x70\x65\x01\x10\xff\x60\xcc\xf9\xe1\xed\x78\x73\xd0\x00\x00\x01\x01\x10\x00\x01\xff\x64\x65\x66\x61\x75\x6c\x74\x56\x00\x0f\x61\x6c\x75\x65\x01\x10\xff\x9b\x0c\xb0\xd7\xd2\xdc\x23\xce\x00\x00\x01\x01\x10\x00\x01\xff\x68\x61\x64\x45\x78\x70\x6c\x69\x01\x63\x69\x74\x44\x65\x66\x61\x75\x03\x6c\x74\x01\x01\x00\x02\x01\x01\x00\x01\x11\x01\xfa\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x02\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x3f\x2e\x67\x72\x6f\x75\x70\x11\x01\x3f\x51\x04\x03\x04\x10\x02\x14\x01\x07\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x3f\x74\x79\x70\x65\x49\x64\x01\x09\x00\x02\x01\x09\x00\x01\x31\x01\x0a\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x63\x03\x68\x65\x6d\x61\x2e\x63\x61\x70\x6e\x70\x3a\x46\x69\x65\x6c\x64\x2e\x6f\x72\x64\x69\x6e\x61\x6c\x00\x00\x11\x01\x77\x51\x08\x03\x04\x0c\xff\xff\x14\x01\x08\x00\x00\x11\x29\x4a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x1d\x01\xfe\xff\x06\x14\x01\x09\x00\x00\x11\x31\x4a\x00\x00\x51\x30\x03\x01\x51\x3c\x02\x01\xff\x69\x6d\x70\x6c\x69\x63\x69\x74\x00\x00\x07\xff\x65\x78\x70\x6c\x69\x63\x69\x74\x00\x00\x00\x01\x07\x00\x02\x01\x07\x00\x01\x31\x01\xaa\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2f\x72\x65\x66\x6c\x65\x63\x74\x69\x6f\x6e\x2e\x63\x0f\x61\x70\x6e\x70\x11\x01\x27\x51\x08\x01\x01\xff\xcf\x85\xeb\x76\x7a\xef\x31\xcb\x00\x11\x09\x52\xff\x76\xc7\x4b\x10\x68\x2e\xa5\xf9\x00\x11\x09\x5a\xff\x43\x61\x70\x53\x63\x68\x65\x6d\x00\x01\x61\xff\x52\x65\x66\x6c\x65\x63\x74\x69\x00\x03\x6f\x6e\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x14\x01\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x24\x50\x61\x72\x61\x6d\x73\x00\x00\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x7a\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x4d\x32\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x51\x3a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\x92\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x24\x52\x65\x73\x75\x6c\x74\x01\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x32\x00\x00\x51\x08\x03\x01\x51\x24\x02\x01\x1f\x70\x61\x74\x68\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\x31\x01\xda\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x47\x65\x74\x56\x61\x6c\x75\x65\x73\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x32\x00\x00\x51\x24\x03\x01\x51\x40\x02\x01\x11\x01\x01\x14\x01\x01\x01\x01\x11\x3d\x3a\x00\x00\x51\x38\x03\x01\x51\x44\x02\x01\x1f\x70\x61\x74\x68\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\xa2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x72\x65\x73\x75\x6c\x74\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x73\x07\x75\x6c\x74\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x11\x29\x1a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x11\x2d\x22\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x03\x6f\x6b\x01\x12\x01\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x01\x12\x00\x01\x07\x65\x72\x72\x01\x12\x05\x01\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x01\x12\x00\x01\x11\x01\x17\x41\x08\x01\x11\x05\x2a\x11\x05\x32\x0f\x54\x79\x70\x65\x1f\x45\x72\x72\x6f\x72\x31\x01\xda\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\x6f\x74\x61\x74\x65\x64\x56\x61\x6c\x03\x75\x65\x11\x01\x17\x51\x04\x01\x01\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x11\x01\x4a\xff\x4d\x65\x74\x61\x64\x61\x74\x61\x00\x00\x00\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x4a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x31\x32\x00\x00\x51\x2c\x03\x01\x51\x38\x02\x01\xff\x6d\x65\x74\x61\x64\x61\x74\x61\x00\x00\x00\x01\x10\xff\x18\x30\xaf\x57\xca\xd8\x53\xad\x00\x00\x01\x01\x10\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\x31\x01\x92\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x65\x72\x72\x6f\x72\x2e\x63\x61\x70\x6e\x70\x3a\x45\x72\x72\x6f\x01\x72\x11\x01\x07\x50\x01\x01\x31\x01\x1f\x01\x51\x14\x03\x04\x00\x00\x04\x01\x00\x00\x11\x7d\x2a\x00\x00\x51\x78\x03\x01\x51\x84\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x81\x42\x00\x00\x51\x7c\x03\x01\x51\x88\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x85\x4a\x00\x00\x51\x84\x03\x01\x11\x02\x03\x11\x03\x02\x14\x01\x03\x01\x01\x11\x81\x2a\x00\x00\x11\x22\x03\x11\x4a\x03\x11\x04\x02\x14\x01\x04\x00\x00\x11\x69\x3a\x00\x00\x11\x6a\x03\x11\x92\x03\x0f\x63\x6f\x64\x65\x01\x08\x00\x02\x01\x08\x00\x01\x7f\x6d\x65\x73\x73\x61\x67\x65\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x63\x61\x74\x65\x67\x6f\x72\x79\x00\x00\x00\x01\x0c\x00\x02\x0f\x6b\x69\x6e\x64\x3f\x73\x6f\x75\x72\x63\x65\x50\x02\x01\x01\x0c\x00\x01\x50\x03\x01\x01\x0f\xff\xbd\x02\x98\x4a\xe2\x71\xe6\xb7\x00\x00\x01\x50\x02\x01\x05\x0f\x02\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x50\x02\x01\x01\x0c\x00\x01\x31\x01\xe2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x47\x65\x74\x56\x61\x6c\x75\x65\x73\x24\x52\x65\x73\x75\x07\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x92\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\x61\x6c\x75\x01\x65\x11\x01\x07\x50\x01\x01\x31\x01\xc7\x01\x51\x20\x03\x04\x0c\xff\xff\x04\x01\x00\x00\x11\xd1\x32\x00\x00\x51\xcc\x03\x01\x51\xd8\x02\x01\x0d\x01\xfe\xff\x14\x01\x01\x00\x00\x11\xd5\x3a\x00\x00\x51\xd0\x03\x01\x51\xdc\x02\x01\x0d\x02\xfd\xff\x14\x01\x02\x00\x00\x11\xd9\x42\x00\x00\x51\xd4\x03\x01\x51\xe0\x02\x01\x0d\x03\xfc\xff\x14\x01\x03\x00\x00\x11\xdd\x3a\x00\x00\x51\xd8\x03\x01\x51\xe4\x02\x01\x0d\x04\xfb\xff\x14\x01\x04\x00\x00\x11\xe1\x5a\x00\x00\x51\xe0\x03\x01\x51\xec\x02\x01\x0d\x05\xfa\xff\x14\x01\x05\x00\x00\x11\xe9\x52\x00\x00\x51\xe8\x03\x01\x51\xf4\x02\x01\x0d\x06\xf9\xff\x14\x01\x06\x00\x00\x11\xf1\x72\x00\x00\x51\xf0\x03\x01\x51\xfc\x02\x01\x0d\x07\xf8\xff\x14\x01\x07\x00\x00\x11\xf9\x2a\x00\x00\x51\xf4\x03\x01\x52\x01\x02\x01\x1f\x69\x6e\x74\x36\x34\x01\x05\x00\x02\x01\x05\x00\x01\x3f\x64\x6f\x75\x62\x6c\x65\x01\x0b\x00\x02\x01\x0b\x00\x01\x7f\x63\x6f\x6d\x70\x6c\x65\x78\x01\x10\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x00\x01\x01\x10\x00\x01\x3f\x73\x74\x72\x69\x6e\x67\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x76\x65\x63\x74\x6f\x72\x44\x61\x00\x03\x74\x61\x01\x10\xff\x78\x89\xf3\x0d\xb8\x65\x4c\x99\x00\x00\x01\x01\x10\x00\x01\xff\x63\x6e\x74\x53\x61\x6d\x70\x6c\x00\x01\x65\x01\x10\xff\x65\x60\x7d\x28\xd8\x0b\x37\xe9\x00\x00\x01\x01\x10\x00\x01\xff\x74\x72\x69\x67\x67\x65\x72\x53\x00\x1f\x61\x6d\x70\x6c\x65\x01\x10\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x00\x01\x01\x10\x00\x01\x0f\x6e\x6f\x6e\x65\x01\x10\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x00\x01\x01\x10\x00\x01\x31\x01\x52\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x08\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x52\x65\x74\x75\x72\x6e\x46\x72\x6f\x6d\x53\x65\x74\x57\x68\x65\x01\x6e\x11\x01\x07\x50\x01\x01\x11\x01\x67\x51\x10\x01\x02\x00\x00\x11\x29\x2a\x00\x00\x01\x01\x11\x21\x52\x00\x00\x01\x02\x11\x1d\x62\x00\x00\x01\x03\x11\x19\xa2\x00\x00\x0f\x61\x73\x61\x70\xff\x64\x65\x76\x69\x63\x65\x41\x63\x00\x01\x6b\xff\x75\x6e\x75\x73\x65\x64\x41\x73\x00\x07\x79\x6e\x63\xff\x75\x6e\x75\x73\x65\x64\x54\x72\x01\x61\x6e\x73\x61\x63\x74\x69\x6f\x07\x6e\x61\x6c\x31\x01\xd2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\x61\x6d\x01\x73\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x49\x32\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\x02\x14\x01\x02\x01\x01\x11\x4d\x6a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\xff\x63\x6f\x6d\x70\x6c\x65\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x00\x01\x05\x0f\x01\x00\x01\x31\x01\x8a\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\x6f\x69\x64\x00\x00\x11\x01\x07\x50\x01\x01\x31\x01\xda\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x24\x52\x65\x73\x75\x6c\x03\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\x02\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x6e\x70\x3a\x53\x75\x62\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x00\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x2a\x00\x00\x51\x40\x03\x01\x51\x4c\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x49\x82\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x02\x14\x01\x02\x00\x00\x11\x51\x6a\x00\x00\x51\x50\x03\x01\x51\x5c\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x73\x74\x72\x65\x61\x6d\x69\x6e\x01\x67\x48\x61\x6e\x64\x6c\x65\x00\x01\x11\xff\x74\x15\xb4\xf5\xa7\x28\x1e\xf5\x00\x00\x01\x01\x11\x00\x01\xff\x73\x75\x62\x73\x63\x72\x69\x62\x00\x0f\x65\x72\x49\x64\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x00\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x6a\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x73\x75\x62\x73\x63\x72\x69\x70\x00\x0f\x74\x69\x6f\x6e\x01\x10\xff\xd4\xb1\xe1\x3d\xa5\x21\xac\xed\x00\x00\x01\x01\x10\x00\x01\x31\x01\x92\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x52\x65\x73\x75\x6c\x74\x01\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\xc2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x69\x73\x63\x6f\x6e\x6e\x65\x63\x74\x44\x65\x76\x69\x63\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x31\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x4a\x73\x6f\x6e\x24\x50\x61\x0f\x72\x61\x6d\x73\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x7a\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x4d\x32\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x51\x3a\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\xb2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x6c\x69\x73\x74\x4e\x6f\x64\x65\x73\x4a\x73\x6f\x6e\x24\x52\x65\x1f\x73\x75\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x52\x00\x00\x51\x0c\x03\x01\x51\x18\x02\x01\xff\x6e\x6f\x64\x65\x50\x72\x6f\x70\x00\x01\x73\x01\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\x9a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x75\x6e\x73\x75\x62\x73\x63\x72\x69\x62\x65\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x6a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x31\x32\x00\x00\x51\x2c\x03\x01\x51\x48\x02\x01\xff\x73\x75\x62\x73\x63\x72\x69\x62\x00\x0f\x65\x72\x49\x64\x01\x0d\x00\x02\x01\x0d\x00\x01\x1f\x70\x61\x74\x68\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x0c\x00\x02\x01\x0e\x00\x01\x31\x01\xca\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x53\x65\x73\x73\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x50\x61\x72\x61\x6d\x73\x00\x00\x31\x01\xd2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x53\x65\x73\x73\x69\x6f\x6e\x56\x65\x72\x73\x69\x6f\x6e\x24\x52\x65\x73\x75\x6c\x74\x01\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x42\x00\x00\x51\x08\x03\x01\x51\x14\x02\x01\x7f\x76\x65\x72\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\xda\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x32\x24\x50\x61\x72\x61\x03\x6d\x73\x11\x01\xe7\x51\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x2a\x00\x00\x51\x5c\x03\x01\x51\x68\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x65\x32\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x01\x02\x14\x01\x02\x01\x01\x11\x69\x6a\x00\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x03\x02\x14\x01\x03\x01\x01\x11\x71\x3a\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\xff\x63\x6f\x6d\x70\x6c\x65\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x00\x01\x05\x0f\x01\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\xe2\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x0a\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x64\x65\x70\x72\x65\x63\x61\x74\x65\x64\x53\x65\x74\x56\x61\x6c\x75\x65\x32\x24\x52\x65\x73\x75\x07\x6c\x74\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x5c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x10\x00\x01\x31\x01\x1a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x4c\x6f\x6f\x6b\x75\x70\x4d\x6f\x03\x64\x65\x11\x01\x07\x50\x01\x01\x11\x01\x37\x51\x08\x01\x02\x00\x00\x11\x11\x6a\x00\x00\x01\x01\x11\x0d\x72\x00\x00\xff\x64\x69\x72\x65\x63\x74\x4c\x6f\x00\x0f\x6f\x6b\x75\x70\xff\x77\x69\x74\x68\x45\x78\x70\x61\x00\x1f\x6e\x73\x69\x6f\x6e\x31\x01\x82\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x31\x01\x1f\x01\x51\x14\x03\x04\x00\x00\x04\x01\x00\x00\x11\x7d\x7a\x00\x00\x51\x7c\x03\x01\x51\x88\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x85\x32\x00\x00\x51\x80\x03\x01\x51\x8c\x02\x01\x01\x02\x14\x01\x02\x01\x01\x11\x89\x5a\x00\x00\x51\x88\x03\x01\x51\x94\x02\x01\x11\x03\x01\x14\x01\x03\x01\x01\x11\x91\x6a\x00\x00\x51\x90\x03\x01\x51\x9c\x02\x01\x11\x04\x02\x14\x01\x04\x01\x01\x11\x99\x3a\x00\x00\x51\x94\x03\x01\x51\xa0\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\x1f\x76\x61\x6c\x75\x65\x01\x10\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x00\x01\x01\x10\x00\x01\xff\x6c\x6f\x6f\x6b\x75\x70\x4d\x6f\x00\x03\x64\x65\x01\x0f\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x00\x01\x01\x0f\x00\x01\xff\x63\x6f\x6d\x70\x6c\x65\x74\x65\x00\x0f\x57\x68\x65\x6e\x01\x0f\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x00\x01\x05\x0f\x01\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x73\x65\x74\x56\x61\x6c\x75\x65\x24\x52\x65\x73\x75\x6c\x74\x73\x00\x00\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x82\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x56\x61\x6c\x75\x65\x24\x50\x61\x72\x61\x6d\x73\x00\x11\x01\xe7\x51\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x7a\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x01\x01\x14\x01\x01\x01\x01\x11\x69\x5a\x00\x00\x51\x68\x03\x01\x51\x74\x02\x01\x11\x02\x01\x14\x01\x02\x01\x01\x11\x71\x32\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x11\x03\x01\x14\x01\x03\x01\x01\x11\x75\x3a\x00\x00\x51\x70\x03\x01\x51\x7c\x02\x01\xff\x70\x61\x74\x68\x45\x78\x70\x72\x00\x3f\x65\x73\x73\x69\x6f\x6e\x01\x0c\x00\x02\x01\x0c\x00\x01\xff\x6c\x6f\x6f\x6b\x75\x70\x4d\x6f\x00\x03\x64\x65\x01\x0f\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x00\x01\x01\x0f\x00\x01\x1f\x66\x6c\x61\x67\x73\x01\x08\x00\x02\x01\x08\x00\x01\x3f\x63\x6c\x69\x65\x6e\x74\x01\x0d\x00\x02\x01\x0d\x00\x00\x11\x01\x02\x31\x01\x8a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x3a\x53\x65\x73\x73\x69\x6f\x6e\x2e\x67\x65\x74\x56\x61\x6c\x75\x65\x24\x52\x65\x73\x75\x6c\x74\x73\x00\x00\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x6c\x02\x01\x3f\x72\x65\x73\x75\x6c\x74\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x40\x01\x11\x01\x1f\x51\x04\x02\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x00\x00\x11\x01\x27\x51\x08\x01\x01\x01\x01\x51\x08\x03\x01\x01\x01\x51\x10\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x10\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x62\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x04\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x07\x70\x6e\x70\x11\x01\x77\x51\x1c\x01\x01\xff\x16\x78\x43\x81\xe9\xe8\x7f\xdf\x00\x11\x31\x2a\xff\x78\x89\xf3\x0d\xb8\x65\x4c\x99\x00\x11\x2d\x5a\xff\x57\xb1\xb4\x97\xaf\xaf\xf1\xaa\x00\x11\x2d\x42\xff\x65\x60\x7d\x28\xd8\x0b\x37\xe9\x00\x11\x29\x52\xff\x95\x0d\x7d\xc2\x97\x20\xb7\xde\x00\x11\x29\x72\xff\xac\x75\xbe\x71\x47\x8b\x83\xb1\x00\x11\x29\x32\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x11\x25\x7a\x0f\x56\x6f\x69\x64\xff\x56\x65\x63\x74\x6f\x72\x44\x61\x00\x03\x74\x61\x7f\x43\x6f\x6d\x70\x6c\x65\x78\xff\x43\x6e\x74\x53\x61\x6d\x70\x6c\x00\x01\x65\xff\x54\x72\x69\x67\x67\x65\x72\x53\x00\x1f\x61\x6d\x70\x6c\x65\x1f\x56\x61\x6c\x75\x65\xff\x41\x6e\x6e\x6f\x74\x61\x74\x65\x00\x3f\x64\x56\x61\x6c\x75\x65\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x14\x01\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\x31\x01\x6a\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x04\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x72\x65\x73\x75\x6c\x74\x2e\x63\x0f\x61\x70\x6e\x70\x11\x01\x17\x51\x04\x01\x01\xff\x3d\x32\x34\x19\x3e\xf3\xb0\xba\x00\x11\x01\x3a\x3f\x52\x65\x73\x75\x6c\x74\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x14\x01\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\x31\x01\x22\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x41\x6e\x6e\x6f\x74\x61\x74\x65\x64\x56\x61\x6c\x75\x65\x2e\x4d\x65\x74\x61\x64\x07\x61\x74\x61\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x52\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x01\x01\x14\x01\x01\x00\x00\x11\x31\x2a\x00\x00\x51\x2c\x03\x01\x51\x38\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\x02\x01\x09\x00\x01\x0f\x70\x61\x74\x68\x01\x0c\x00\x02\x01\x0c\x00\x01\x31\x01\xa2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x43\x6f\x6d\x70\x07\x6c\x65\x78\x11\x01\x07\x50\x01\x01\x11\x01\x77\x51\x08\x03\x04\x00\x00\x04\x01\x00\x00\x11\x29\x2a\x00\x00\x51\x24\x03\x01\x51\x30\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\x2d\x2a\x00\x00\x51\x28\x03\x01\x51\x34\x02\x01\x0f\x72\x65\x61\x6c\x01\x0b\x00\x02\x01\x0b\x00\x01\x0f\x69\x6d\x61\x67\x01\x0b\x00\x02\x01\x0b\x00\x01\x31\x01\xba\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x56\x65\x63\x74\x3f\x6f\x72\x44\x61\x74\x61\x11\x01\x07\x50\x01\x01\x11\x01\xe7\x51\x10\x03\x04\x00\x00\x04\x01\x00\x00\x11\x61\x52\x00\x00\x51\x60\x03\x01\x51\x6c\x02\x01\x11\x01\x02\x14\x01\x01\x00\x00\x11\x69\x92\x00\x00\x51\x6c\x03\x01\x51\x78\x02\x01\x11\x02\x01\x14\x01\x02\x00\x00\x11\x75\x82\x00\x00\x51\x74\x03\x01\x51\x80\x02\x01\x01\x03\x14\x01\x03\x00\x00\x11\x7d\x2a\x00\x00\x51\x78\x03\x01\x51\x84\x02\x01\xff\x76\x61\x6c\x75\x65\x54\x79\x70\x00\x01\x65\x01\x07\x00\x02\x01\x07\x00\x01\xff\x76\x65\x63\x74\x6f\x72\x45\x6c\x01\x65\x6d\x65\x6e\x74\x54\x79\x70\x01\x65\x01\x06\x00\x02\x01\x06\x00\x01\xff\x65\x78\x74\x72\x61\x48\x65\x61\x01\x64\x65\x72\x49\x6e\x66\x6f\x00\x01\x08\x00\x02\x01\x08\x00\x01\x0f\x64\x61\x74\x61\x01\x0d\x00\x02\x01\x0d\x00\x01\x31\x01\xb2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x43\x6e\x74\x53\x1f\x61\x6d\x70\x6c\x65\x11\x01\x07\x50\x01\x01\x11\x01\xaf\x51\x0c\x03\x04\x00\x00\x04\x01\x00\x00\x11\x45\x52\x00\x00\x51\x44\x03\x01\x51\x50\x02\x01\x11\x01\x02\x14\x01\x01\x00\x00\x11\x4d\x42\x00\x00\x51\x48\x03\x01\x51\x54\x02\x01\x11\x02\x03\x14\x01\x02\x00\x00\x11\x51\x42\x00\x00\x51\x4c\x03\x01\x51\x58\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\x02\x01\x09\x00\x01\x7f\x63\x6f\x75\x6e\x74\x65\x72\x01\x04\x00\x02\x01\x04\x00\x01\x7f\x74\x72\x69\x67\x67\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\x31\x01\xd2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x76\x61\x6c\x75\x65\x2e\x63\x61\x70\x6e\x70\x3a\x54\x72\x69\x67\x67\x65\x72\x53\x61\x6d\x70\x6c\x01\x65\x11\x01\x07\x50\x01\x01\x31\x01\x8f\x01\x51\x1c\x03\x04\x00\x00\x04\x01\x00\x00\x11\xb5\x52\x00\x00\x51\xb4\x03\x01\x51\xc0\x02\x01\x11\x01\x01\x14\x01\x01\x00\x00\x11\xbd\x5a\x00\x00\x51\xbc\x03\x01\x51\xc8\x02\x01\x11\x02\x04\x14\x01\x02\x00\x00\x11\xc5\x42\x00\x00\x51\xc0\x03\x01\x51\xcc\x02\x01\x11\x03\x05\x14\x01\x03\x00\x00\x11\xc9\x7a\x00\x00\x51\xc8\x03\x01\x51\xd4\x02\x01\x11\x04\x06\x14\x01\x04\x00\x00\x11\xd1\x5a\x00\x00\x51\xd0\x03\x01\x51\xdc\x02\x01\x11\x05\x07\x14\x01\x05\x00\x00\x11\xd9\x22\x00\x00\x51\xd4\x03\x01\x51\xe0\x02\x01\x11\x06\x08\x14\x01\x06\x00\x00\x11\xdd\x72\x00\x00\x51\xdc\x03\x01\x51\xe8\x02\x01\xff\x74\x69\x6d\x65\x73\x74\x61\x6d\x00\x01\x70\x01\x09\x00\x02\x01\x09\x00\x01\xff\x73\x61\x6d\x70\x6c\x65\x54\x69\x00\x03\x63\x6b\x01\x09\x00\x02\x01\x09\x00\x01\x7f\x74\x72\x69\x67\x67\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\xff\x6d\x69\x73\x73\x65\x64\x54\x72\x00\x3f\x69\x67\x67\x65\x72\x73\x01\x08\x00\x02\x01\x08\x00\x01\xff\x61\x77\x67\x54\x72\x69\x67\x67\x00\x03\x65\x72\x01\x08\x00\x02\x01\x08\x00\x01\x07\x64\x69\x6f\x01\x08\x00\x02\x01\x08\x00\x01\xff\x73\x65\x71\x75\x65\x6e\x63\x65\x00\x1f\x49\x6e\x64\x65\x78\x01\x08\x00\x02\x01\x08\x00\x01\x31\x01\xb2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x65\x72\x72\x6f\x72\x2e\x63\x61\x70\x6e\x70\x3a\x45\x72\x72\x6f\x1f\x72\x4b\x69\x6e\x64\x11\x01\x07\x50\x01\x01\x11\x01\xf7\x51\x28\x01\x02\x00\x00\x11\x71\x1a\x00\x00\x01\x01\x11\x69\x52\x00\x00\x01\x02\x11\x65\x42\x00\x00\x01\x03\x11\x5d\x4a\x00\x00\x01\x04\x11\x59\x62\x00\x00\x01\x05\x11\x55\x5a\x00\x00\x01\x06\x11\x51\x72\x00\x00\x01\x07\x11\x4d\x4a\x00\x00\x01\x08\x11\x49\x62\x00\x00\x01\x09\x11\x45\x42\x00\x00\x03\x6f\x6b\xff\x63\x61\x6e\x63\x65\x6c\x6c\x65\x00\x01\x64\x7f\x75\x6e\x6b\x6e\x6f\x77\x6e\xff\x6e\x6f\x74\x46\x6f\x75\x6e\x64\x00\x00\x00\xff\x6f\x76\x65\x72\x77\x68\x65\x6c\x00\x07\x6d\x65\x64\xff\x62\x61\x64\x52\x65\x71\x75\x65\x00\x03\x73\x74\xff\x75\x6e\x69\x6d\x70\x6c\x65\x6d\x00\x1f\x65\x6e\x74\x65\x64\xff\x69\x6e\x74\x65\x72\x6e\x61\x6c\x00\x00\x00\xff\x75\x6e\x61\x76\x61\x69\x6c\x61\x00\x07\x62\x6c\x65\x7f\x74\x69\x6d\x65\x6f\x75\x74\x31\x01\x62\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x04\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x65\x72\x72\x6f\x72\x2e\x63\x61\x07\x70\x6e\x70\x11\x01\x27\x51\x08\x01\x01\xff\xbd\x02\x98\x4a\xe2\x71\xe6\xb7\x00\x11\x09\x52\xff\xd9\x11\x7d\x51\x4c\x4e\xe3\xc4\x00\x11\x09\x32\xff\x45\x72\x72\x6f\x72\x4b\x69\x6e\x00\x01\x64\x1f\x45\x72\x72\x6f\x72\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x14\x01\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\x31\x01\xc2\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x06\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x65\x73\x73\x69\x6f\x6e\x2f\x73\x65\x73\x73\x69\x6f\x6e\x5f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2e\x63\x61\x70\x6e\x70\x00\x11\x01\x37\x51\x0c\x01\x01\xff\x5c\xa5\xa4\x2d\x58\x45\xd4\xb9\x00\x11\x11\x42\xff\xf9\xed\x55\xac\x3a\xa5\x2d\xdd\x00\x11\x0d\x92\xff\x25\xf4\x72\xe0\xb5\x49\x50\xda\x00\x11\x11\x5a\x7f\x53\x65\x73\x73\x69\x6f\x6e\xff\x52\x65\x74\x75\x72\x6e\x46\x72\x01\x6f\x6d\x53\x65\x74\x57\x68\x65\x01\x6e\xff\x4c\x6f\x6f\x6b\x75\x70\x4d\x6f\x00\x03\x64\x65\x11\x01\x37\x51\x08\x01\x02\xff\xce\x9d\xfc\x8c\xff\x96\x70\xac\x00\x51\x10\x02\x01\x41\x18\x01\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x14\x02\x01\x41\x24\x01\x00\x03\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00\x31\x01\x1a\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x07\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x6e\x70\x3a\x53\x74\x72\x65\x61\x6d\x69\x6e\x67\x48\x61\x6e\x64\x03\x6c\x65\x11\x01\x07\x50\x01\x01\x11\x01\x47\x51\x04\x03\x05\x00\x00\xff\xb6\x47\x7c\x96\x0f\xad\x0b\xa3\x01\x6e\xb1\xc0\x77\x33\x9a\x5f\x99\x11\x11\x5a\x00\x02\x11\x09\x07\xff\x73\x65\x6e\x64\x56\x61\x6c\x75\x00\x03\x65\x73\x40\x01\x11\x01\x07\x50\x01\x01\x31\x01\xaa\x02\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x09\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x6e\x70\x3a\x53\x74\x72\x65\x61\x6d\x69\x6e\x67\x48\x61\x6e\x64\x6c\x65\x2e\x73\x65\x6e\x64\x56\x61\x6c\x75\x65\x73\x24\x50\x61\x0f\x72\x61\x6d\x73\x11\x01\x3f\x51\x04\x03\x04\x00\x00\x04\x01\x00\x00\x11\x0d\x3a\x00\x00\x51\x08\x03\x01\x51\x24\x02\x01\x3f\x76\x61\x6c\x75\x65\x73\x01\x0e\x00\x01\x50\x03\x01\x01\x10\xff\xdc\x7c\x83\x6e\x37\xee\x08\xf4\x00\x00\x01\x01\x0e\x00\x01\x31\x01\x02\x01\xff\x63\x61\x70\x6e\x70\x2f\x73\x74\x03\x72\x65\x61\x6d\x2e\x63\x61\x70\x6e\x70\x3a\x53\x74\x72\x65\x61\x6d\x52\x65\x73\x75\x6c\x74\x00\x11\x01\x07\x50\x01\x01\x11\x01\x9a\xff\x63\x61\x70\x6e\x70\x2f\x73\x74\x01\x72\x65\x61\x6d\x2e\x63\x61\x70\x03\x6e\x70\x11\x01\x17\x51\x04\x01\x01\xff\x6e\xb1\xc0\x77\x33\x9a\x5f\x99\x00\x11\x01\x6a\xff\x53\x74\x72\x65\x61\x6d\x52\x65\x00\x0f\x73\x75\x6c\x74\x11\x01\x1f\x51\x04\x01\x02\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x04\x02\x01\x41\x10\x01\x01\x0c\x00\x00\x11\x01\x32\x1f\x63\x61\x70\x6e\x70\x00\x00\x31\x01\x9a\x01\xff\x7a\x68\x69\x6e\x73\x74\x2f\x69\x05\x6f\x2f\x70\x72\x6f\x74\x6f\x63\x6f\x6c\x2f\x63\x61\x70\x6e\x70\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\x2e\x63\x61\x70\x03\x6e\x70\x11\x01\x27\x51\x08\x01\x01\xff\x74\x15\xb4\xf5\xa7\x28\x1e\xf5\x00\x11\x09\x82\xff\xd4\xb1\xe1\x3d\xa5\x21\xac\xed\x00\x11\x09\x6a\xff\x53\x74\x72\x65\x61\x6d\x69\x6e\x02\x67\x48\x61\x6e\x64\x6c\x65\x00\x53\x75\x62\x73\x63\x72\x69\x70\x0f\x74\x69\x6f\x6e\x11\x01\x37\x51\x08\x01\x02\xff\xce\x9d\xfc\x8c\xff\x96\x70\xac\x00\x51\x10\x02\x01\x41\x18\x01\xff\x2c\x5f\x80\xbf\x9e\xf9\xc6\xb9\x00\x51\x14\x02\x01\x41\x24\x01\x00\x03\x01\x0c\x00\x00\x11\x01\x6a\xff\x7a\x68\x69\x6e\x73\x74\x5f\x63\x00\x0f\x61\x70\x6e\x70\x00\x00" - - -def get_schema() -> CapnpStructReader: - """Get the loaded capnp schema as a reflection_capnp.CapSchema reader. - - Note that only the `theSchema` field is loaded. The rest of the fields - (e.g. TypeId) are set to their default values. - - Returns: - The loaded capnp schema. - """ - return reflection_capnp.CapSchema.from_bytes_packed(_CAPNP_BINARY_SCHEMA) diff --git a/src/labone/nodetree/enum.py b/src/labone/nodetree/enum.py index 73c90af..6ad928e 100644 --- a/src/labone/nodetree/enum.py +++ b/src/labone/nodetree/enum.py @@ -94,7 +94,7 @@ def __reduce_ex__( return NodeEnumMeta, ( self._value_, self.__class__.__name__, - {key: int(value) for key, value in self.__class__._member_map_.items()}, # type: ignore[call-overload] # noqa: SLF001 + {key: int(value) for key, value in self.__class__._member_map_.items()}, # type: ignore[call-overload] self.__class__.__module__, ) diff --git a/src/labone/nodetree/helper.py b/src/labone/nodetree/helper.py index 7086d92..9485136 100644 --- a/src/labone/nodetree/helper.py +++ b/src/labone/nodetree/helper.py @@ -55,40 +55,43 @@ def __iter__(self) -> t.Iterator[str]: class Session(t.Protocol): """Interface for communication with a data-server.""" - async def list_nodes( + def list_nodes( self, path: LabOneNodePath = "", *, flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, - ) -> list[LabOneNodePath]: + ) -> t.Awaitable[list[LabOneNodePath]]: """List the nodes found at a given path.""" ... - async def list_nodes_info( + def list_nodes_info( self, path: LabOneNodePath = "", *, flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, - ) -> dict[LabOneNodePath, NodeInfo]: + ) -> t.Awaitable[dict[LabOneNodePath, NodeInfo]]: """List the nodes and their information found at a given path.""" ... - async def set(self, value: AnnotatedValue) -> AnnotatedValue: + def set(self, value: AnnotatedValue) -> t.Awaitable[AnnotatedValue]: """Set the value of a node.""" ... - async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValue]: + def set_with_expression( + self, + value: AnnotatedValue, + ) -> t.Awaitable[list[AnnotatedValue]]: """Set the value of all nodes matching the path expression.""" ... - async def get( + def get( self, path: LabOneNodePath, - ) -> AnnotatedValue: + ) -> t.Awaitable[AnnotatedValue]: """Get the value of a node.""" ... - async def get_with_expression( + def get_with_expression( self, path_expression: LabOneNodePath, flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE @@ -96,7 +99,7 @@ async def get_with_expression( | ListNodesFlags.LEAVES_ONLY | ListNodesFlags.EXCLUDE_STREAMING | ListNodesFlags.GET_ONLY, - ) -> list[AnnotatedValue]: + ) -> t.Awaitable[list[AnnotatedValue]]: """Get the value of all nodes matching the path expression.""" ... diff --git a/src/labone/nodetree/node.py b/src/labone/nodetree/node.py index 7ba8510..594f98a 100644 --- a/src/labone/nodetree/node.py +++ b/src/labone/nodetree/node.py @@ -14,8 +14,6 @@ from abc import ABC, abstractmethod from functools import cached_property -from deprecation import deprecated - from labone.core.subscription import DataQueue from labone.core.value import AnnotatedValue, Value from labone.node_info import NodeInfo @@ -490,9 +488,11 @@ def path_segments(self) -> tuple[NormalizedPathSegment, ...]: return self._path_segments @property - @deprecated(details="use 'path_segments' instead.") def raw_tree(self) -> tuple[NormalizedPathSegment, ...]: - """The underlying segments of the path, this node corresponds to.""" + """The underlying segments of the path, this node corresponds to. + + Deprecated: use 'path_segments' instead. + """ return self.path_segments @property @@ -883,10 +883,10 @@ def __contains__(self, item: str | int | Node) -> bool: return self.is_child_node(item) return normalize_path_segment(item) in self._subtree_paths - async def __call__( + def __call__( self, value: Value | None = None, - ) -> AnnotatedValue | ResultNode: + ) -> t.Awaitable[AnnotatedValue | ResultNode]: """Call with or without a value for setting/getting the node. Args: @@ -909,20 +909,20 @@ async def __call__( mapped to one of the other errors. """ if value is None: - return await self._get() + return self._get() - return await self._set(value) + return self._set(value) @abstractmethod - async def _get( + def _get( self, - ) -> AnnotatedValue | ResultNode: ... + ) -> t.Awaitable[AnnotatedValue | ResultNode]: ... @abstractmethod - async def _set( + def _set( self, value: Value, - ) -> AnnotatedValue | ResultNode: ... + ) -> t.Awaitable[AnnotatedValue | ResultNode]: ... @classmethod def build( @@ -1083,7 +1083,13 @@ def root(self) -> Node: class LeafNode(Node): """Node corresponding to a leaf in the path-structure.""" - async def _get(self) -> AnnotatedValue: + async def _value_postprocessing( + self, + result: t.Awaitable[AnnotatedValue], + ) -> AnnotatedValue: + return self._tree_manager.parser(await result) + + def _get(self) -> t.Awaitable[AnnotatedValue]: """Get the value of the node. Returns: @@ -1098,14 +1104,12 @@ async def _get(self) -> AnnotatedValue: LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - return self._tree_manager.parser( - await self._tree_manager.session.get(self.path), - ) + return self._value_postprocessing(self._tree_manager.session.get(self.path)) - async def _set( + def _set( self, value: Value, - ) -> AnnotatedValue: + ) -> t.Awaitable[AnnotatedValue]: """Set the value of the node. Args: @@ -1123,10 +1127,8 @@ async def _set( LabOneCoreError: If something else went wrong that can not be mapped to one of the other errors. """ - return self._tree_manager.parser( - await self._tree_manager.session.set( - AnnotatedValue(value=value, path=self.path), - ), + return self._value_postprocessing( + self._tree_manager.session.set(AnnotatedValue(value=value, path=self.path)), ) def try_generate_subnode( @@ -1242,9 +1244,9 @@ def node_info(self) -> NodeInfo: class WildcardOrPartialNode(Node, ABC): """Common functionality for wildcard and partial nodes.""" - async def _get( + def _get( self, - ) -> ResultNode: + ) -> t.Awaitable[ResultNode]: """Get the value of the node. Raises: @@ -1257,13 +1259,13 @@ async def _get( mapped to one of the other errors. """ return self._package_response( - await self._tree_manager.session.get_with_expression(self.path), + self._tree_manager.session.get_with_expression(self.path), ) - async def _set( + def _set( self, value: Value, - ) -> ResultNode: + ) -> t.Awaitable[ResultNode]: """Set the value of the node. Args: @@ -1279,14 +1281,14 @@ async def _set( mapped to one of the other errors. """ return self._package_response( - await self._tree_manager.session.set_with_expression( + self._tree_manager.session.set_with_expression( AnnotatedValue(value=value, path=self.path), ), ) - def _package_response( + async def _package_response( self, - raw_response: list[AnnotatedValue], + result: t.Awaitable[list[AnnotatedValue]], ) -> ResultNode: """Package server-response of wildcard or partial get-request. @@ -1299,11 +1301,12 @@ def _package_response( will be available in the result node. Args: - raw_response: server-response to get (or set) request + result: server-response to get (or set) request Returns: Node-structure, representing the results. """ + raw_response = await result timestamp = raw_response[0].timestamp if raw_response else None path_segments = ( self.tree_manager.root.path_segments diff --git a/src/labone/resources/reflection.capnp b/src/labone/resources/reflection.capnp deleted file mode 100644 index be8453e..0000000 --- a/src/labone/resources/reflection.capnp +++ /dev/null @@ -1,13 +0,0 @@ -@0x8461c2916c39ad0e; - -using Schema = import "/capnp/schema.capnp"; - -struct CapSchema @0xcb31ef7a76eb85cf { - typeId @0 :UInt64; - theSchema @1 :List(Schema.Node); -} - -interface Reflection @0xf9a52e68104bc776 { - getTheSchema @0 () -> (theSchema :CapSchema); -} - diff --git a/src/labone/server/__init__.py b/src/labone/server/__init__.py index 2cdc63b..4bb2c9e 100644 --- a/src/labone/server/__init__.py +++ b/src/labone/server/__init__.py @@ -3,17 +3,7 @@ This subpackage allows to create a data server through capnp. """ -from labone.server.server import CapnpServer, start_server -from labone.server.session import ( - SessionFunctionality, - SessionInterface, - Subscription, -) +from labone.server.server import CapnpServer +from labone.server.session import LabOneServerBase, MockSession, Subscription -__all__ = [ - "CapnpServer", - "start_server", - "SessionFunctionality", - "SessionInterface", - "Subscription", -] +__all__ = ["CapnpServer", "LabOneServerBase", "Subscription", "MockSession"] diff --git a/src/labone/server/server.py b/src/labone/server/server.py index b2bf4ae..a7bfcda 100644 --- a/src/labone/server/server.py +++ b/src/labone/server/server.py @@ -8,174 +8,116 @@ from __future__ import annotations -import argparse -import asyncio -import socket -from abc import ABC -from contextlib import suppress -from typing import TYPE_CHECKING +import typing as t -import capnp +import zhinst.comms +from typing_extensions import TypeAlias -from labone.core.helper import CapnpStructReader, ensure_capnp_event_loop -from labone.core.reflection.parsed_wire_schema import ParsedWireSchema +from labone.core.helper import get_default_context -if TYPE_CHECKING: - from capnp.lib.capnp import _CallContext, _DynamicStructBuilder +CapnpResult: TypeAlias = dict[str, t.Any] -class CapnpServer(ABC): - """Common interface for concrete server implementations. +def capnp_method(interface: int, method_index: int) -> t.Callable: + """A decorator indicate that a function is capnp callback.""" - Servers will implement this interface. - It stands for the actual functionality of the server, which - will be defined in the subclasses. - - The id_ attribute stands for the unique capnp id of the - concrete server. - """ + def inner(func: t.Callable) -> t.Callable: + func._capnp_info = ( # type:ignore[attr-defined] # noqa: SLF001 + interface, + method_index, + ) + return func - server_id: int - type_id: int + return inner -def capnp_server_factory( - stream: capnp.AsyncIoStream, - schema: CapnpStructReader, - server: CapnpServer, -) -> capnp.TwoPartyServer: - """Dynamically create a capnp server. +class CapnpServer: + """Basic capnp server. - As a reflection schema is used, the concrete server interface - is only known at runtime. This function is the - at-runtime-approach to creating the concrete server. + Todo ... Args: - stream: Stream for the server. - schema: Parsed capnp schema (`reflection_capnp.CapSchema`). - server: The concrete server implementation. - - Returns: - Dynamically created capnp server. + schema: schema to use. """ - schema_parsed_dict = schema.to_dict() - parsed_schema = ParsedWireSchema(schema.theSchema) - capnp_interface = capnp.lib.capnp._InterfaceModule( # noqa: SLF001 - parsed_schema.full_schema[server.server_id].schema.as_interface(), - parsed_schema.full_schema[server.server_id].name, - ) - - class ServerImpl(capnp_interface.Server): # type: ignore[name-defined] - """Dynamically created capnp server. - Redirects all calls (except getTheSchema) to the concrete server implementation. - """ + def __init__(self, schema: zhinst.comms.SchemaLoader): + self._schema = schema + self._registered_callbacks: dict[tuple[int, int], t.Callable] = {} + self._load_callbacks() + + def _load_callbacks(self) -> None: + """Load all methods with the capnp_method decorator.""" + for method_name in dir(self): + method = getattr(self, method_name) + capnp_info = getattr(method, "_capnp_info", None) + if capnp_info: + self._registered_callbacks[capnp_info] = method + + async def _capnp_callback( + self, + interface: int, + method_index: int, + call_input: zhinst.comms.DynamicStructBase, + fulfiller: zhinst.comms.Fulfiller, + ) -> None: + """Entrypoint for all capnp calls. - def __init__(self) -> None: - self._server = server - # parsed schema needs to stay alive as long as the server is. - self._parsed_schema = parsed_schema - - def __getattr__( - self, - name: str, - ) -> _DynamicStructBuilder | list[_DynamicStructBuilder] | str | list[str]: - """Redirecting all calls to the concrete server implementation.""" - if hasattr(self._server, name): - return getattr(self._server, name) - return getattr(super(), name) - - async def getTheSchema( # noqa: N802 - self, - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> _DynamicStructBuilder: - """Reflection: Capnp method to get the schema. - - Will be called by capnp as reaction to a getTheSchema request. - Do not call this method directly. - - Args: - _context: Capnp context. - kwargs: Additional arguments. - - Returns: - The parsed schema as a capnp object. - """ - # Use `from_dict` to benefit from pycapnp lifetime management - # Otherwise the underlying capnp object need to be copied manually to avoid - # segfaults - _context.results.theSchema.from_dict(schema_parsed_dict) - _context.results.theSchema.typeId = server.type_id - - return capnp.TwoPartyServer(stream, bootstrap=ServerImpl()) - - -async def start_local_server( - schema: CapnpStructReader, - server: CapnpServer, -) -> tuple[capnp.TwoPartyServer, capnp.AsyncIoStream]: - """Starting a local server. - - This is equivalent to the `capnp_server_factory` but with the addition that - a local socket pair is created for the server. + This method called by capnp whenever a new request is received. - Args: - schema: Parsed capnp schema (`reflection_capnp.CapSchema`). - server: The concrete server implementation. - - Returns: - The server and the client connection. - """ - await ensure_capnp_event_loop() - # create local socket pair - # Since there is only a single client there is no need to use a asyncio server - read, write = socket.socketpair() - reader = await capnp.AsyncIoStream.create_connection(sock=read) - writer = await capnp.AsyncIoStream.create_connection(sock=write) - # create server for the local socket pair - return capnp_server_factory(writer, schema, server), reader - - -def start_server( - schema: CapnpStructReader, - server: CapnpServer, -) -> None: - """Start the server. - - Start the capnp server with the given schema and server instance. - The server address and port can be specified via command line arguments. - The server keeps running until it is interrupted. - - Args: - schema: Parsed capnp schema (`reflection_capnp.CapSchema`). - server: The concrete server implementation. - """ + Args: + interface: Interface of the call. + method_index: Method index of the call. + call_input: Input of the call. + fulfiller: Fulfiller to fulfill or reject the call. + """ + target_info = (interface, method_index) + if target_info not in self._registered_callbacks: + fulfiller.reject( + zhinst.comms.Fulfiller.UNIMPLEMENTED, + f"Function {interface}:{method_index} not implemented", + ) + return + try: + fulfiller.fulfill(await self._registered_callbacks[target_info](call_input)) + except Exception as e: # noqa: BLE001 + fulfiller.reject(zhinst.comms.Fulfiller.DISCONNECTED, str(e.args[0])) + + async def start( + self, + port: int, + *, + open_overwrite: bool = False, + context: zhinst.comms.CapnpContext, + ) -> None: + """Start the server on a given port. - def _host_port() -> tuple[str, int]: - parser = argparse.ArgumentParser( - usage="""Runs the server bound to the given address/port ADDRESS. """, + Args: + context: context to use. + port: port to listen on. + open_overwrite: Flag if the server should be reachable from outside. + """ + self._capnp_server = await context.listen( + port=port, + openOverride=open_overwrite, + callback=self._capnp_callback, + schema=self._schema, ) - parser.add_argument("address", help="ADDRESS:PORT") + async def start_pipe( + self, + context: zhinst.comms.CapnpContext | None = None, + ) -> zhinst.comms.DynamicClient: + """Create a local pipe to the server. - return parser.parse_args().address.split(":") + A pipe is a local single connection to the server. - async def _new_connection( - stream: capnp.AsyncIoStream, - ) -> None: - await capnp_server_factory( - stream=stream, - schema=schema, - server=server, - ).on_disconnect() - - async def _run_server() -> None: - await ensure_capnp_event_loop() - host, port = _host_port() - server = await capnp.AsyncIoStream.create_server(_new_connection, host, port) - async with server: - await server.serve_forever() - - with suppress(KeyboardInterrupt): - asyncio.run(_run_server()) + Args: + context: context to use. + """ + if context is None: + context = get_default_context() + self._capnp_server, client = await context.create_pipe( + server_callback=self._capnp_callback, + schema=self._schema, + ) + return client diff --git a/src/labone/server/session.py b/src/labone/server/session.py index e1079d7..c1b2526 100644 --- a/src/labone/server/session.py +++ b/src/labone/server/session.py @@ -15,32 +15,28 @@ from typing import TYPE_CHECKING from labone.core import ListNodesFlags, ListNodesInfoFlags +from labone.core.helper import get_default_context +from labone.core.hpk_schema import get_schema_loader from labone.core.session import Session from labone.core.value import ( AnnotatedValue, _capnp_value_to_python_value, value_from_python_types_dict, ) -from labone.server.server import CapnpServer +from labone.server.server import CapnpResult, CapnpServer, capnp_method if TYPE_CHECKING: - from capnp.lib.capnp import ( - _CallContext, - _DynamicEnum, - _DynamicStructBuilder, - _DynamicStructReader, - ) + import zhinst.comms + from zhinst.comms import DynamicStructBase -HPK_SCHEMA_ID = 11970870220622790664 -SESSION_SCHEMA_ID = 13390403837104530780 +SESSION_SCHEMA_ID = 0xB9D445582DA4A55C SERVER_ERROR = "SERVER_ERROR" if TYPE_CHECKING: from labone.core.helper import LabOneNodePath from labone.core.session import NodeInfo - from labone.core.subscription import StreamingHandle class Subscription: @@ -58,7 +54,7 @@ class Subscription: def __init__( self, path: LabOneNodePath, - streaming_handle: StreamingHandle, + streaming_handle: zhinst.comms.DynamicClient, subscriber_id: int, ): self._path = path @@ -71,14 +67,17 @@ async def send_value(self, value: AnnotatedValue) -> None: Args: value: Value to send. """ - capnp_response = { - "value": value_from_python_types_dict(value), - "metadata": { - "path": value.path, - "timestamp": value.timestamp, - }, - } - await self._streaming_handle.sendValues([capnp_response]) + await self._streaming_handle.sendValues( + values=[ + { + "value": value_from_python_types_dict(value), + "metadata": { + "path": value.path, + "timestamp": value.timestamp, + }, + }, + ], + ) @property def path(self) -> LabOneNodePath: @@ -86,7 +85,57 @@ def path(self) -> LabOneNodePath: return self._path -class SessionFunctionality(ABC): +class MockSession(Session): + """Regular Session holding a mock server. + + This class is designed for holding the mock server. + This is needed, because otherwise, + there would be no reference to the capnp objects, which would go out of scope. + This way, the correct lifetime of the capnp objects is ensured, by attaching it to + its client. + + Args: + mock_server: Mock server. + capnp_session: Capnp session. + reflection: Reflection server. + """ + + def __init__( + self, + server: LabOneServerBase, + client: zhinst.comms.DynamicClient, + *, + context: zhinst.comms.CapnpContext, + ): + super().__init__(client, context=context) + self._mock_server = server + + @property + def mock_server(self) -> LabOneServerBase: + """Mock server.""" + return self._mock_server + + +def build_capnp_error(error: Exception) -> CapnpResult: + """Helper function to build a capnp error message. + + Args: + error: Caught python exception to be converted. + + Returns: + Capnp Type dictionary for Result(Error). + """ + return { + "err": { + "code": 2, + "message": f"{error}", + "category": SERVER_ERROR, + "source": __name__, + }, + } + + +class LabOneServerBase(ABC, CapnpServer): """Blueprint for defining the session behavior. The SessionFunctionality class offers a interface between @@ -106,6 +155,9 @@ class SessionFunctionality(ABC): Both approaches can be combined. """ + def __init__(self): + CapnpServer.__init__(self, schema=get_schema_loader()) + @abstractmethod async def get(self, path: LabOneNodePath) -> AnnotatedValue: """Override this method for defining get behavior. @@ -171,7 +223,7 @@ async def list_nodes( self, path: LabOneNodePath = "", *, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, + flags: ListNodesFlags, ) -> list[LabOneNodePath]: """Override this method for defining list_nodes behavior. @@ -188,9 +240,9 @@ async def list_nodes( @abstractmethod async def list_nodes_info( self, - path: LabOneNodePath = "", + path: LabOneNodePath, *, - flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, + flags: ListNodesInfoFlags, ) -> dict[LabOneNodePath, NodeInfo]: """Override this method for defining list_nodes_info behavior. @@ -205,7 +257,7 @@ async def list_nodes_info( ... @abstractmethod - async def subscribe_logic(self, subscription: Subscription) -> None: + async def subscribe(self, subscription: Subscription) -> None: """Override this method for defining subscription behavior. Args: @@ -214,145 +266,83 @@ async def subscribe_logic(self, subscription: Subscription) -> None: """ ... - -def build_capnp_error(error: Exception) -> _DynamicStructBuilder: - """Helper function to build a capnp error message. - - Args: - error: Caught python exception to be converted. - - Returns: - Capnp Type dictionary for Result(Error). - """ - return { - "err": { - "code": 2, - "message": f"{error}", - "category": SERVER_ERROR, - "source": __name__, - }, - } - - -class SessionInterface(CapnpServer): - """Capnp session interface. - - The logic for answering capnp requests is outsourced and taken as an argument. - This allows for custom server definition while keeping this classes - code static. - - Note: - Methods within serve for capnp to answer requests. They should not be - called directly. They should not be overritten in order to define - custom behavior. Instead, override the methods of SessionFunctionality. - - Args: - functionality: The implementation of the server behavior. - """ - - server_id = HPK_SCHEMA_ID - type_id = SESSION_SCHEMA_ID - - def __init__(self, functionality: SessionFunctionality) -> None: - self._functionality = functionality - - async def getSessionVersion( # noqa: N802 + @capnp_method(SESSION_SCHEMA_ID, 7) + async def _get_session_version_interface( self, - _context: _CallContext, - ) -> str: - """Capnp server method to get the session version. + _: DynamicStructBase, + ) -> CapnpResult: + """Capnp server method to get session version. Returns: - Session version. + Capnp result. """ - return str(Session.TESTED_CAPABILITY_VERSION) + return {"version": str(Session.TESTED_CAPABILITY_VERSION)} - async def listNodes( # noqa: N802 + @capnp_method(SESSION_SCHEMA_ID, 0) + async def _list_nodes_interface( self, - pathExpression: str, # noqa: N803 - flags: ListNodesFlags, - client: bytes, # noqa: ARG002 - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> list[str]: + call_input: DynamicStructBase, + ) -> CapnpResult: """Capnp server method to list nodes. Args: - pathExpression: Path to narrow down which nodes should be listed. - Omitting the path will list all nodes by default. - flags: Flags to control the behaviour of the list_nodes method. - client: Capnp specific argument. - _context: Capnp specific argument. - **kwargs: Capnp specific arguments. + call_input: Arguments the server has been called with Returns: - List of paths. + Capnp result. """ - return await self._functionality.list_nodes(pathExpression, flags=flags) + return { + "paths": await self.list_nodes( + call_input.pathExpression, + flags=ListNodesFlags(call_input.flags), + ), + } - async def listNodesJson( # noqa: N802 + @capnp_method(SESSION_SCHEMA_ID, 5) + async def _list_nodes_json_interface( self, - pathExpression: str, # noqa: N803 - flags: ListNodesFlags, - client: bytes, # noqa: ARG002 - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> str: - """Capnp server method to list nodes plus additional informtion as json. + call_input: DynamicStructBase, + ) -> CapnpResult: + """Capnp server method to list nodes json. Args: - pathExpression: Path to narrow down which nodes should be listed. - Omitting the path will list all nodes by default. - flags: Flags to control the behaviour of the list_nodes_info method. - client: Capnp specific argument. - _context: Capnp specific argument. - **kwargs: Capnp specific arguments. + call_input: Arguments the server has been called with Returns: - Json encoded dictionary of paths and node info. + Capnp result. """ - return json.dumps( - await self._functionality.list_nodes_info( - path=pathExpression, - flags=flags, + return { + "nodeProps": json.dumps( + await self.list_nodes_info( + call_input.pathExpression, + flags=ListNodesInfoFlags(call_input.flags), + ), ), - ) + } - async def getValue( # noqa: N802 - self, - pathExpression: str, # noqa: N803 - lookupMode: _DynamicEnum, # noqa: N803 - flags: int, - client: bytes, # noqa: ARG002 - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> list[_DynamicStructBuilder]: + @capnp_method(SESSION_SCHEMA_ID, 10) + async def _get_value_interface(self, call_input: DynamicStructBase) -> CapnpResult: """Capnp server method to get values. Args: - pathExpression: Path for which the value should be retrieved. - lookupMode: Defining whether a single path should be retrieved - or potentially multiple ones specified by a wildcard path. - flags: Flags to control the behaviour of wildcard path requests. - client: Capnp specific argument. - _context: Capnp specific argument. - **kwargs: Capnp specific arguments. + call_input: Arguments the server has been called with Returns: - List of read values. + Capnp result. """ + lookup_mode = call_input.lookupMode try: - if lookupMode == 0: # direct lookup - responses = [await self._functionality.get(pathExpression)] + if lookup_mode == 0: # direct lookup + responses = [await self.get(call_input.pathExpression)] else: - responses = await self._functionality.get_with_expression( - pathExpression, - flags=flags, + responses = await self.get_with_expression( + call_input.pathExpression, + flags=call_input.flags, ) except Exception as e: # noqa: BLE001 - return [build_capnp_error(e)] + return {"result": [build_capnp_error(e)]} - return [ + result = [ { "ok": { "value": value_from_python_types_dict(response), @@ -364,52 +354,38 @@ async def getValue( # noqa: N802 } for response in responses ] + return {"result": result} - async def setValue( # noqa: PLR0913, N802 - self, - pathExpression: str, # noqa: N803 - value: _DynamicStructReader, - lookupMode: _DynamicEnum, # noqa: N803 - completeWhen: _DynamicEnum, # noqa: N803, ARG002 - client: bytes, # noqa: ARG002 - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> list[_DynamicStructBuilder]: + @capnp_method(SESSION_SCHEMA_ID, 9) + async def _set_value_interface(self, call_input: DynamicStructBase) -> CapnpResult: """Capnp server method to set values. Args: - pathExpression: Path for which the value should be set. - value: Value to be set. - lookupMode: Defining whether a single path should be set - or potentially multiple ones specified by a wildcard path. - completeWhen: Capnp specific argument. - client: Capnp specific argument. - _context: Capnp specific argument. - **kwargs: Capnp specific arguments. + call_input: Arguments the server has been called with Returns: - List of acknowledged values. + Capnp result. """ - value, extra_header = _capnp_value_to_python_value(value) + value, extra_header = _capnp_value_to_python_value(call_input.value) annotated_value = AnnotatedValue( value=value, - path=pathExpression, + path=call_input.pathExpression, extra_header=extra_header, ) try: - if lookupMode == 0: # direct lookup + if call_input.lookupMode == 0: # direct lookup responses = [ - await self._functionality.set(annotated_value), + await self.set(annotated_value), ] else: - responses = await self._functionality.set_with_expression( + responses = await self.set_with_expression( annotated_value, ) except Exception as e: # noqa: BLE001 - return [build_capnp_error(e)] + return {"result": [build_capnp_error(e)]} - return [ + result = [ { "ok": { "value": value_from_python_types_dict(response), @@ -421,34 +397,41 @@ async def setValue( # noqa: PLR0913, N802 } for response in responses ] + return {"result": result} - async def subscribe( - self, - subscription: _DynamicStructReader, - _context: _CallContext, - **kwargs, # noqa: ARG002 - ) -> _DynamicStructBuilder: + @capnp_method(SESSION_SCHEMA_ID, 3) + async def _subscribe_interface(self, call_input: DynamicStructBase) -> CapnpResult: """Capnp server method to subscribe to nodes. - Do not override this method. Instead, override 'subscribe_logic' - of SessionFunctionality (or subclass). - Args: - subscription: Capnp object containing information on - where to distribute updates to. - _context: Capnp specific argument. - **kwargs: Capnp specific arguments. + call_input: Arguments the server has been called with Returns: - Capnp acknowledgement. + Capnp result. """ try: subscription = Subscription( - path=subscription.path, - streaming_handle=subscription.streamingHandle, - subscriber_id=subscription.subscriberId, + path=call_input.subscription.path, + streaming_handle=call_input.subscription.streamingHandle, + subscriber_id=call_input.subscription.subscriberId, ) - await self._functionality.subscribe_logic(subscription) + await self.subscribe(subscription) except Exception as e: # noqa: BLE001 - return build_capnp_error(e) - return {"ok": {}} + return {"result": [build_capnp_error(e)]} + return {"result": {"ok": {}}} + + async def start_pipe( # type: ignore[override] + self, + context: zhinst.comms.CapnpContext | None = None, + ) -> MockSession: + """Create a local pipe to the server. + + A pipe is a local single connection to the server. + + Args: + context: context to use. + """ + if context is None: + context = get_default_context() + client = await super().start_pipe(context) + return MockSession(self, client, context=context) diff --git a/tests/core/conftest.py b/tests/core/conftest.py deleted file mode 100644 index 009fc20..0000000 --- a/tests/core/conftest.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any - -import capnp -import pytest -import pytest_asyncio - -from .resources import ( - error_capnp, - path_capnp, - result_capnp, - session_protocol_capnp, - uuid_capnp, - value_capnp, -) - - -@pytest_asyncio.fixture(autouse=True) -async def kj_loop(): - """Ensures that the capnp event loop is running. - - Its important that every test creates and closes the kj_event loop. - This helps to avoid leaking errors and promises from one test to another. - """ - async with capnp.kj_loop(): - yield - - -class MockReflectionServer: - def __init__(self): - self._loaded_nodes = {} - self._available_schema = [ - error_capnp, - path_capnp, - result_capnp, - session_protocol_capnp, - uuid_capnp, - value_capnp, - ] - - def __getattr__(self, name: str) -> Any: - for schema in self._available_schema: - if hasattr(schema, name): - return getattr(schema, name) - msg = f"MockReflectionServer has no attribute {name}" - raise AttributeError(msg) - - -@pytest.fixture() -def reflection_server(): - """Returns a reflection server instance.""" - return MockReflectionServer() diff --git a/tests/core/test_annotated_value_from_capnp.py b/tests/core/test_annotated_value_from_capnp.py index 9641cce..01d7314 100644 --- a/tests/core/test_annotated_value_from_capnp.py +++ b/tests/core/test_annotated_value_from_capnp.py @@ -4,12 +4,11 @@ import numpy as np import pytest +from munch import Munch import labone.core.value as value_module from labone.core.errors import LabOneCoreError -from .resources import value_capnp - class IllegalAnnotatedValue: class IllegalValue: @@ -27,101 +26,92 @@ def test_illegal_type(): def test_void(): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": {"none": {}}, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": {"none": {}}, + }, + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None assert parsed_value.value is None def test_trigger_sample(): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "triggerSample": { - "timestamp": 1, - "sampleTick": 2, - "trigger": 3, - "missedTriggers": 4, - "awgTrigger": 5, - "dio": 6, - "sequenceIndex": 7, + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "triggerSample": { + "timestamp": 1, + "sampleTick": 2, + "trigger": 3, + "missedTriggers": 4, + "awgTrigger": 5, + "dio": 6, + "sequenceIndex": 7, + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None - assert ( - parsed_value.value.timestamp - == input_dict["value"]["triggerSample"]["timestamp"] - ) - assert ( - parsed_value.value.sample_tick - == input_dict["value"]["triggerSample"]["sampleTick"] - ) - assert parsed_value.value.trigger == input_dict["value"]["triggerSample"]["trigger"] + assert parsed_value.value.timestamp == msg["value"]["triggerSample"]["timestamp"] + assert parsed_value.value.sample_tick == msg["value"]["triggerSample"]["sampleTick"] + assert parsed_value.value.trigger == msg["value"]["triggerSample"]["trigger"] assert ( parsed_value.value.missed_triggers - == input_dict["value"]["triggerSample"]["missedTriggers"] - ) - assert ( - parsed_value.value.awg_trigger - == input_dict["value"]["triggerSample"]["awgTrigger"] + == msg["value"]["triggerSample"]["missedTriggers"] ) - assert parsed_value.value.dio == input_dict["value"]["triggerSample"]["dio"] + assert parsed_value.value.awg_trigger == msg["value"]["triggerSample"]["awgTrigger"] + assert parsed_value.value.dio == msg["value"]["triggerSample"]["dio"] assert ( parsed_value.value.sequence_index - == input_dict["value"]["triggerSample"]["sequenceIndex"] + == msg["value"]["triggerSample"]["sequenceIndex"] ) def test_cnt_sample(): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "cntSample": { - "timestamp": 1, - "counter": 2, - "trigger": 3, + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "cntSample": { + "timestamp": 1, + "counter": 2, + "trigger": 3, + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None - assert parsed_value.value.timestamp == input_dict["value"]["cntSample"]["timestamp"] - assert parsed_value.value.counter == input_dict["value"]["cntSample"]["counter"] - assert parsed_value.value.trigger == input_dict["value"]["cntSample"]["trigger"] + assert parsed_value.value.timestamp == msg["value"]["cntSample"]["timestamp"] + assert parsed_value.value.counter == msg["value"]["cntSample"]["counter"] + assert parsed_value.value.trigger == msg["value"]["cntSample"]["trigger"] def test_streaming_error(): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "streamingError": { - "code": 1, - "message": "Test message", - "category": "zi:test", - "kind": "timeout", - "source": "", + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "streamingError": { + "code": 1, + "message": "Test message", + "category": "zi:test", + "kind": "timeout", + "source": "", + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) with pytest.raises(LabOneCoreError): value_module.AnnotatedValue.from_capnp(msg) @@ -136,83 +126,83 @@ def test_streaming_error(): ], ) def test_generic_types(type_name, input_val, output_val): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": {type_name: input_val}, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": {type_name: input_val}, + }, + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None assert parsed_value.value == output_val def test_string_vector(): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "vectorData": { - "valueType": 7, - "vectorElementType": 6, - "extraHeaderInfo": 0, - "data": b"Hello World", + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "vectorData": { + "valueType": 7, + "vectorElementType": 6, + "extraHeaderInfo": 0, + "data": b"Hello World", + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None assert parsed_value.value == "Hello World" def test_generic_vector(): input_array = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint32) - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "vectorData": { - "valueType": 67, - "vectorElementType": 2, - "extraHeaderInfo": 0, - "data": input_array.tobytes(), + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "vectorData": { + "valueType": 67, + "vectorElementType": 2, + "extraHeaderInfo": 0, + "data": input_array.tobytes(), + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header is None assert np.array_equal(parsed_value.value, input_array) @patch("labone.core.value.parse_shf_vector_data_struct", autospec=True) def test_shf_vector(mock_method): - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "vectorData": { - "valueType": 69, - "vectorElementType": 2, - "extraHeaderInfo": 0, - "data": b"", + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "vectorData": { + "valueType": 69, + "vectorElementType": 2, + "extraHeaderInfo": 0, + "data": b"", + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) mock_method.return_value = "array", "extra_header" parsed_value = value_module.AnnotatedValue.from_capnp(msg) mock_method.assert_called_once() - assert mock_method.call_args[0][0].to_dict() == input_dict["value"]["vectorData"] - assert parsed_value.timestamp == input_dict["metadata"]["timestamp"] - assert parsed_value.path == input_dict["metadata"]["path"] + assert mock_method.call_args[0][0] == msg["value"]["vectorData"] + assert parsed_value.timestamp == msg["metadata"]["timestamp"] + assert parsed_value.path == msg["metadata"]["path"] assert parsed_value.extra_header == "extra_header" assert parsed_value.value == "array" @@ -224,21 +214,21 @@ def test_shf_vector(mock_method): ) def test_unknown_shf_vector(mock_method): input_array = np.linspace(0, 1, 200, dtype=np.uint32) - input_dict = { - "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, - "value": { - "vectorData": { - "valueType": 69, - "vectorElementType": 2, - "extraHeaderInfo": 32, - "data": input_array.tobytes(), + msg = Munch.fromDict( + { + "metadata": {"timestamp": 42, "path": "/non/of/your/business"}, + "value": { + "vectorData": { + "valueType": 69, + "vectorElementType": 2, + "extraHeaderInfo": 32, + "data": input_array.tobytes(), + }, }, }, - } - msg = value_capnp.AnnotatedValue.new_message() - msg.from_dict(input_dict) + ) mock_method.side_effect = ValueError("Unknown SHF vector type") with pytest.raises(ValueError): value_module.AnnotatedValue.from_capnp(msg) mock_method.assert_called_once() - assert mock_method.call_args[0][0].to_dict() == input_dict["value"]["vectorData"] + assert mock_method.call_args[0][0] == msg["value"]["vectorData"] diff --git a/tests/core/test_annotated_value_to_capnp.py b/tests/core/test_annotated_value_to_capnp.py index e5502b9..f7df784 100644 --- a/tests/core/test_annotated_value_to_capnp.py +++ b/tests/core/test_annotated_value_to_capnp.py @@ -16,181 +16,173 @@ VectorValueType, preprocess_complex_shf_waveform_vector, ) -from labone.core.value import AnnotatedValue +from labone.core.value import value_from_python_types @given(st.integers(min_value=-np.int64(), max_value=np.int64())) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_int64(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - assert value.value.int64 == inp +def test_value_from_python_types_int64(inp): + value = value_from_python_types(inp, path="") + assert value["int64"] == inp @pytest.mark.parametrize(("inp", "out"), [(False, 0), (True, 1)]) -def test_value_from_python_types_bool_to_int64(reflection_server, inp, out): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - assert value.value.int64 is out +def test_value_from_python_types_bool_to_int64(inp, out): + value = value_from_python_types(inp, path="") + assert value["int64"] == out @given(st.floats(allow_nan=False)) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_double(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - assert value.value.double == inp +def test_value_from_python_types_double(inp): + value = value_from_python_types(inp, path="") + assert value["double"] == inp -def test_value_from_python_types_np_nan(reflection_server): - value1 = AnnotatedValue(value=np.nan, path="").to_capnp( - reflection=reflection_server, - ) - assert np.isnan(value1.value.double) +def test_value_from_python_types_np_nan(): + value1 = value_from_python_types(np.nan, path="") + assert np.isnan(value1["double"]) inp = complex(real=0.0, imag=np.nan.imag) - value2 = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - assert value2.value.complex.real == inp.real - assert value2.value.complex.imag == inp.imag + value2 = value_from_python_types(inp, path="") + assert value2["complex"]["real"] == inp.real + assert value2["complex"]["imag"] == inp.imag @given(st.complex_numbers(allow_nan=False)) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_complex(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - expected = reflection_server.Complex( - real=inp.real, - imag=inp.imag, - ) - assert value.value.complex.real == expected.real - assert value.value.complex.imag == expected.imag +def test_value_from_python_types_complex(inp): + value = value_from_python_types(inp, path="") + assert value["complex"]["real"] == inp.real + assert value["complex"]["imag"] == inp.imag @given(st.text()) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_string(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - assert value.value.string == inp +def test_value_from_python_types_string(inp): + value = value_from_python_types(inp, path="") + assert value["string"] == inp @given(st.binary()) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_bytes(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.BYTE_ARRAY.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT8.value - assert vec_data.data == inp +def test_value_from_python_types_vector_data_bytes(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.BYTE_ARRAY.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT8.value + assert vec_data["data"] == inp @given(arrays(dtype=np.uint8, shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint8(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT8.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_uint8(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT8.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=np.uint16, shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint16(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT16.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_uint16(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT16.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=np.uint32, shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint32(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT32.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_uint32(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT32.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=(np.uint64, int), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint64(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT64.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_uint64(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT64.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=(float, np.double), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_double(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.DOUBLE.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_double(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.DOUBLE.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=(np.single), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_float(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.FLOAT.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_float(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.FLOAT.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=(np.csingle), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_complex_float(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.COMPLEX_FLOAT.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_complex_float(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.COMPLEX_FLOAT.value + assert vec_data["data"] == inp.tobytes() -def test_value_from_python_types_vector_data_complex_waveform(reflection_server): +def test_value_from_python_types_vector_data_complex_waveform(): inp = np.array([1 + 2j, 3 + 4j], dtype=np.complex128) - value = AnnotatedValue(value=inp, path="test/waveforms/0/wave").to_capnp( - reflection=reflection_server, - ) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.UINT32.value - assert vec_data.data == preprocess_complex_shf_waveform_vector(inp)[0].tobytes() + value = value_from_python_types(inp, path="test/waveforms/0/wave") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.UINT32.value + assert vec_data["data"] == preprocess_complex_shf_waveform_vector(inp)[0].tobytes() @given(arrays(dtype=(np.cdouble), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_complex_double(reflection_server, inp): - value = AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) - vec_data = value.value.vectorData - assert vec_data.valueType == VectorValueType.VECTOR_DATA.value - assert vec_data.extraHeaderInfo == 0 - assert vec_data.vectorElementType == VectorElementType.COMPLEX_DOUBLE.value - assert vec_data.data == inp.tobytes() +def test_value_from_python_types_vector_data_complex_double(inp): + value = value_from_python_types(inp, path="") + vec_data = value["vectorData"] + assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value + assert vec_data["extraHeaderInfo"] == 0 + assert vec_data["vectorElementType"] == VectorElementType.COMPLEX_DOUBLE.value + assert vec_data["data"] == inp.tobytes() @given(arrays(dtype=(np.bytes_), shape=(1, 2))) @settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_invalid(reflection_server, inp): +def test_value_from_python_types_vector_data_invalid(inp): with pytest.raises(ValueError): - AnnotatedValue(value=inp, path="").to_capnp(reflection=reflection_server) + value_from_python_types(inp, path="") -def test_value_from_python_types_invalid(reflection_server): +def test_value_from_python_types_invalid(): class FakeObject: pass with pytest.raises(ValueError): - AnnotatedValue(value=FakeObject, path="").to_capnp(reflection=reflection_server) + value_from_python_types(FakeObject, path="") diff --git a/tests/core/test_connection_layer.py b/tests/core/test_connection_layer.py deleted file mode 100644 index 6ef0d4b..0000000 --- a/tests/core/test_connection_layer.py +++ /dev/null @@ -1,475 +0,0 @@ -"""Tests for the labone.core.connection_layer module""" - -import json -import socket -from unittest.mock import patch - -import pytest -from packaging import version - -from labone.core import connection_layer, errors - -from .resources import hello_msg_capnp, orchestrator_capnp - - -def test_open_socket_ok(): - server_info = connection_layer.ServerInfo(host="127.0.0.1", port=1234) - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.bind((server_info.host, server_info.port)) - server.listen() - sock = connection_layer._open_socket(server_info) - host, port = sock.getpeername() - assert host == server_info.host - assert port == server_info.port - - -def test_open_socket_non_existing(): - server_info = connection_layer.ServerInfo(host="127.0.0.1", port=1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._open_socket(server_info) - assert "Connection refused" in err.value.args[0] - - -@pytest.fixture() -def hello_msg(): - hello_msg = hello_msg_capnp.HelloMsg.new_message() - hello_msg.kind = hello_msg_capnp.HelloMsg.Kind.orchestrator - hello_msg.protocol = hello_msg_capnp.HelloMsg.Protocol.http - hello_msg.l1Ver = "99.99.99" - hello_msg._set("schema", "1.6.0") - return hello_msg - - -def _json_to_bytes(json_dict): - hello_msg_raw = json.dumps(json_dict) - hello_msg_raw = hello_msg_raw.encode("utf-8") - return ( - hello_msg_raw - + b" " * (hello_msg_capnp.HelloMsg.fixedLength - len(hello_msg_raw) - 1) - + b"\x00" - ) - - -def _hello_msg_to_bytes(hello_msg): - return _json_to_bytes(hello_msg.to_dict()) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_ok(socket_mock, hello_msg): - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - received_hello_msg = connection_layer._client_handshake(socket_mock) - assert received_hello_msg == hello_msg.to_dict() - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_invalid_json(socket_mock, hello_msg): - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg)[1:] - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "Invalid JSON during Handshake" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_empty_json(socket_mock): - socket_mock.recv.return_value = _json_to_bytes({}) - socket_mock.getpeername.return_value = ("localhost", 1234) - # capnp is able to handle an empty json but the follwoing checks will fail - # because the default values should do not match the expected criteria - with pytest.raises(errors.LabOneCoreError): - connection_layer._client_handshake(socket_mock) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_additional_fields(socket_mock, hello_msg): - hello_msg_json = hello_msg.to_dict() - hello_msg_json["additional_field"] = "additional_value" - hello_msg_json["test"] = "additional_value" - socket_mock.recv.return_value = _json_to_bytes(hello_msg_json) - # additional fields should be filtered out an only the known field should be - # used. This helps outputting a more helpful error message. - received_hello_msg = connection_layer._client_handshake(socket_mock) - assert "additional_field" in received_hello_msg - del received_hello_msg["additional_field"] - assert "test" in received_hello_msg - del received_hello_msg["test"] - assert received_hello_msg == hello_msg.to_dict() - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_wrong_kind(socket_mock, hello_msg): - hello_msg.kind = hello_msg_capnp.HelloMsg.Kind.unknown - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "Reason: Invalid server kind: unknown" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_wrong_kind_no_check(socket_mock, hello_msg): - hello_msg.kind = hello_msg_capnp.HelloMsg.Kind.unknown - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - received_hello_msg = connection_layer._client_handshake(socket_mock, check=False) - assert received_hello_msg == hello_msg.to_dict() - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_wrong_protocol(socket_mock, hello_msg): - hello_msg.protocol = hello_msg_capnp.HelloMsg.Protocol.capnp - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "Invalid protocol: capnp" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_incompatible_capability_version(socket_mock, hello_msg): - hello_msg._set("schema", "1.3.0") - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "Unsupported LabOne Version: 99.99.99" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_higher_capability_version(socket_mock, hello_msg): - hello_msg._set("schema", "2.0.0") - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "newer LabOne version" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_client_handshake_illegal_capability_version(socket_mock, hello_msg): - hello_msg._set("schema", "unknown") - socket_mock.recv.return_value = _hello_msg_to_bytes(hello_msg) - socket_mock.getpeername.return_value = ("localhost", 1234) - with pytest.raises(errors.LabOneCoreError) as err: - connection_layer._client_handshake(socket_mock) - assert "Unsupported LabOne Version: 99.99.99" in err.value.args[0] - - -def test_raise_orchestrator_error(): - error_map = { - "ok": ValueError, - "unknown": errors.LabOneCoreError, - "kernelNotFound": errors.UnavailableError, - "illegalDeviceIdentifier": errors.BadRequestError, - "deviceNotFound": errors.UnavailableError, - "kernelLaunchFailure": errors.InternalError, - "firmwareUpdateRequired": errors.UnavailableError, - "interfaceMismatch": errors.UnavailableError, - "differentInterfaceInUse": errors.UnavailableError, - "deviceInUse": errors.UnavailableError, - "unsupportedApiLevel": errors.UnavailableError, - "badRequest": errors.BadRequestError, - } - for value in orchestrator_capnp.Orchestrator.ErrorCode.schema.enumerants: - try: - with pytest.raises(error_map[value]) as err: - connection_layer._raise_orchestrator_error(value, "test") - except KeyError as key_error: - msg = ( - f"Error `{value}` not mapped. Please add it to the test " - "and _raise_orchestrator_error" - ) - raise KeyError(msg) from key_error - if value != "ok": - assert "test" in err.value.args[0] - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_ok_dev(socket_mock): - response_lines = [ - b"HTTP/1.1 101 Switching Protocols\r\n", - b"Connection: Upgrade\r\n", - b"Upgrade: capnp\r\n", - b"content-Length: 0\r\n", - b"Zhinst-Kernel-Uid: 18c6d4e4-0a63-4a59-8c58-98a955683501\r\n", - b"Zhinst-Kernel-Version: 1.2.3\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - kernel_info = connection_layer.DeviceKernelInfo(device_id="dev1234") - - kernel_info_extended = connection_layer._protocol_upgrade( - socket_mock, - kernel_info=kernel_info, - ) - assert kernel_info_extended.name == "dev1234" - assert kernel_info_extended.capability_version == version.Version("1.2.3") - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_ok_zi(socket_mock): - response_lines = [ - b"HTTP/1.1 101 Switching Protocols\r\n", - b"Connection: Upgrade\r\n", - b"Upgrade: capnp\r\n", - b"content-Length: 0\r\n", - b"Zhinst-Kernel-Uid: 18c6d4e4-0a63-4a59-8c58-98a955683501\r\n", - b"Zhinst-Kernel-Version: 1.2.3\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - kernel_info = connection_layer.ZIKernelInfo() - - kernel_info_extended = connection_layer._protocol_upgrade( - socket_mock, - kernel_info=kernel_info, - ) - assert kernel_info_extended.name == "zi" - assert kernel_info_extended.capability_version == version.Version("1.2.3") - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_ok_no_capability_version(socket_mock): - response_lines = [ - b"HTTP/1.1 101 Switching Protocols\r\n", - b"Connection: Upgrade\r\n", - b"Upgrade: capnp\r\n", - b"content-Length: 0\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="1GbE", - ) - - kernel_info_extended = connection_layer._protocol_upgrade( - socket_mock, - kernel_info=kernel_info, - ) - assert kernel_info_extended.name == "dev1234" - assert kernel_info_extended.capability_version is None - assert kernel_info_extended.device_id == kernel_info.device_id - assert kernel_info_extended.interface == kernel_info.interface - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_device_not_found(socket_mock): - response_lines = [ - b"HTTP/1.1 404 Not Found\r\n", - b"Content-Length: 78\r\n", - b"Content-Type: application/capnp\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - socket_mock.makefile.return_value.read.return_value = ( - b'{"err":{"code":"deviceNotFound","message":"No device found with id ' - b'DEV1234."}}' - ) - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="1GbE", - ) - - with pytest.raises(errors.UnavailableError): - connection_layer._protocol_upgrade(socket_mock, kernel_info=kernel_info) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_device_different_interface(socket_mock): - response_lines = [ - b"HTTP/1.1 409 Conflict \r\n", - b"Content-Length: 166\r\n", - b"Content-Type: application/capnp\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - socket_mock.makefile.return_value.read.return_value = ( - b'{"err":{"code":"interfaceMismatch","message":"' - b"Cannot connect to DEV8563 through a USB interface. The device is " - b'available only through the following interfaces: 1GbE"}}' - ) - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="USB", - ) - - with pytest.raises(errors.UnavailableError): - connection_layer._protocol_upgrade(socket_mock, kernel_info=kernel_info) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_unsuported_api_level(socket_mock): - # This is only a hypothetical scenario ... the API level always must be 6 - response_lines = [ - b"HTTP/1.1 409 Conflict \r\n", - b"Content-Length: 54\r\n", - b"Content-Type: application/capnp\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - socket_mock.makefile.return_value.read.return_value = ( - b'{"err":{"code":"unsupportedApiLevel","message":"Test"}}' - ) - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="USB", - ) - - with pytest.raises(errors.LabOneCoreError): - connection_layer._protocol_upgrade(socket_mock, kernel_info=kernel_info) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_error_but_no_info(socket_mock): - response_lines = [ - b"HTTP/1.1 409 Conflict \r\n", - b"Content-Length: 0\r\n", - b"Content-Type: application/capnp\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="1GbE", - ) - - with pytest.raises(errors.LabOneCoreError): - connection_layer._protocol_upgrade(socket_mock, kernel_info=kernel_info) - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_protocol_upgrade_not_possible(socket_mock): - response_lines = [ - b"HTTP/1.1 200 OK\r\n", - b"Content-Length: 55\r\n", - b"Content-Type: application/capnp\r\n", - b"\r\n", - ] - - socket_mock.getpeername.return_value = ("localhost", 1234) - socket_mock.makefile.return_value.readline.side_effect = response_lines - socket_mock.makefile.return_value.read.return_value = ( - b'{"err":{"code":"unsupportedApiLevel","message":"Test"}}' - ) - kernel_info = connection_layer.DeviceKernelInfo( - device_id="dev1234", - interface="1GbE", - ) - - with pytest.raises(errors.LabOneCoreError): - connection_layer._protocol_upgrade(socket_mock, kernel_info=kernel_info) - - -@patch("labone.core.connection_layer._open_socket", autospec=True) -@patch("labone.core.connection_layer._client_handshake", autospec=True) -@patch("labone.core.connection_layer._protocol_upgrade", autospec=True) -def test_create_session_client_stream_ok_new_sock( - protocol_upgrade_mock, - handshake_mock, - open_socket_mock, -): - kernel_info = connection_layer.ZIKernelInfo() - server_info = connection_layer.ServerInfo(host="localhost", port=1234) - ( - sock, - kernel_info_extended, - server_info_extended, - ) = connection_layer.create_session_client_stream( - kernel_info=kernel_info, - server_info=server_info, - ) - protocol_upgrade_mock.assert_called_once() - handshake_mock.assert_called_once() - open_socket_mock.assert_called_once() - assert sock == open_socket_mock.return_value - assert kernel_info_extended == protocol_upgrade_mock.return_value - assert server_info_extended.hello_msg == handshake_mock.return_value - - -@patch("labone.core.connection_layer._open_socket", autospec=True) -@patch("labone.core.connection_layer._client_handshake", autospec=True) -@patch("labone.core.connection_layer._protocol_upgrade", autospec=True) -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_create_session_client_stream_ok_existing_sock( - socket_mock, - protocol_upgrade_mock, - handshake_mock, - open_socket_mock, -): - socket_mock.getpeername.return_value = ("localhost", 1234) - kernel_info = connection_layer.ZIKernelInfo() - ( - sock, - kernel_info_extended, - server_info_extended, - ) = connection_layer.create_session_client_stream( - sock=socket_mock, - kernel_info=kernel_info, - ) - protocol_upgrade_mock.assert_called_once() - handshake_mock.assert_called_once() - # Socket exists, so it should not be opened again - open_socket_mock.assert_not_called() - assert sock == socket_mock - assert kernel_info_extended == protocol_upgrade_mock.return_value - assert server_info_extended.hello_msg == handshake_mock.return_value - - -@patch("labone.core.connection_layer.socket.socket", autospec=True) -def test_create_session_client_stream_both_sock_and_sock_info(socket_mock): - kernel_info = connection_layer.ZIKernelInfo() - server_info = connection_layer.ServerInfo(host="localhost", port=1234) - with pytest.raises(ValueError): - connection_layer.create_session_client_stream( - sock=socket_mock, - kernel_info=kernel_info, - server_info=server_info, - ) - - -def test_create_session_client_stream_both_no_sock_and_no_sock_info(): - kernel_info = connection_layer.ZIKernelInfo() - with pytest.raises(ValueError): - connection_layer.create_session_client_stream(kernel_info=kernel_info) - - -@patch("labone.core.connection_layer._open_socket", autospec=True) -@patch("labone.core.connection_layer._client_handshake", autospec=True) -@patch("labone.core.connection_layer._protocol_upgrade", autospec=True) -def test_create_session_client_stream_no_handshake( - protocol_upgrade_mock, - handshake_mock, - open_socket_mock, -): - kernel_info = connection_layer.ZIKernelInfo() - server_info = connection_layer.ServerInfo(host="localhost", port=1234) - ( - sock, - kernel_info_extended, - server_info_extended, - ) = connection_layer.create_session_client_stream( - kernel_info=kernel_info, - server_info=server_info, - handshake=False, - ) - protocol_upgrade_mock.assert_called_once() - handshake_mock.assert_not_called() - open_socket_mock.assert_called_once() - assert sock == open_socket_mock.return_value - assert kernel_info_extended == protocol_upgrade_mock.return_value - assert server_info_extended.hello_msg is None diff --git a/tests/core/test_create_session.py b/tests/core/test_create_session.py deleted file mode 100644 index 69d358d..0000000 --- a/tests/core/test_create_session.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Tests the creation of a labone.core.session.Session object""" - -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest - -from labone.core import connection_layer, kernel_session -from labone.core.errors import LabOneCoreError, UnavailableError - - -@patch("labone.core.session._send_and_wait_request", autospec=True) -@patch("labone.core.kernel_session.create_session_client_stream", autospec=True) -@patch("labone.core.kernel_session.capnp", autospec=True) -@patch("labone.core.kernel_session.ReflectionServer", autospec=True) -@pytest.mark.asyncio() -async def test_session_create_ok_zi( - reflection_server, - capnp_mock, - create_session_client_stream, - send_request, -): - dummy_sock = MagicMock() - dummy_kernel_info_extended = MagicMock() - dummy_server_info_extended = MagicMock() - create_session_client_stream.return_value = ( - dummy_sock, - dummy_kernel_info_extended, - dummy_server_info_extended, - ) - send_request.return_value.version = str( - kernel_session.KernelSession.TESTED_CAPABILITY_VERSION, - ) - capnp_mock.AsyncIoStream.create_connection = AsyncMock() - kernel_info = connection_layer.ZIKernelInfo() - server_info = connection_layer.ServerInfo(host="localhost", port=8004) - created_session = await kernel_session.KernelSession.create( - kernel_info=kernel_info, - server_info=server_info, - ) - - create_session_client_stream.assert_called_once_with( - kernel_info=kernel_info, - server_info=server_info, - ) - capnp_mock.AsyncIoStream.create_connection.assert_called_once_with(sock=dummy_sock) - reflection_server.create_from_connection.assert_called_once() - - original_capnp_session = ( - reflection_server.create_from_connection.return_value.session.capnp_capability - ) - assert created_session._session == original_capnp_session - assert created_session.kernel_info == dummy_kernel_info_extended - assert created_session.server_info == dummy_server_info_extended - - -@patch("labone.core.kernel_session.create_session_client_stream", autospec=True) -@patch("labone.core.kernel_session.capnp", autospec=True) -@patch("labone.core.kernel_session.ReflectionServer", autospec=True) -@pytest.mark.asyncio() -async def test_session_create_err_zi( - reflection_server, - capnp_mock, - create_session_client_stream, -): - dummy_sock = MagicMock() - dummy_kernel_info_extended = MagicMock() - dummy_server_info_extended = MagicMock() - create_session_client_stream.return_value = ( - dummy_sock, - dummy_kernel_info_extended, - dummy_server_info_extended, - ) - capnp_mock.AsyncIoStream.create_connection = AsyncMock() - kernel_info = connection_layer.ZIKernelInfo() - server_info = connection_layer.ServerInfo(host="localhost", port=8004) - - reflection_server.create_from_connection.side_effect = LabOneCoreError("Test") - - with pytest.raises(UnavailableError): - await kernel_session.KernelSession.create( - kernel_info=kernel_info, - server_info=server_info, - ) diff --git a/tests/core/test_kernel_session.py b/tests/core/test_kernel_session.py new file mode 100644 index 0000000..9f7c446 --- /dev/null +++ b/tests/core/test_kernel_session.py @@ -0,0 +1,28 @@ +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from labone.core import KernelInfo, KernelSession, ServerInfo, hpk_schema + + +@pytest.mark.asyncio() +@patch("zhinst.comms.CapnpContext") +async def test_kernel_session(context): + session = Mock() + context.connect_labone = AsyncMock(return_value=session) + kernel_info = KernelInfo.zi_connection() + kernel_session = await KernelSession.create( + kernel_info=kernel_info, + server_info=ServerInfo(host="localhost", port=8004), + context=context, + ) + assert kernel_session.raw_session == session + assert context.connect_labone.await_count == 1 + context.connect_labone.assert_awaited_once_with( + "localhost", + 8004, + kernel_info, + schema=hpk_schema.get_schema_loader(), + ) + assert kernel_session.server_info.host == "localhost" + assert kernel_session.server_info.port == 8004 diff --git a/tests/core/test_kj_event_loop.py b/tests/core/test_kj_event_loop.py deleted file mode 100644 index 56bde5f..0000000 --- a/tests/core/test_kj_event_loop.py +++ /dev/null @@ -1,31 +0,0 @@ -import asyncio -from unittest.mock import patch - -import pytest -import pytest_asyncio - -import labone.core.helper - - -@pytest_asyncio.fixture(autouse=True) -async def kj_loop(): - return - - -@pytest.mark.asyncio() -@patch("labone.core.helper.capnp", autospec=True) -async def test_create_event_loop_ok(capnp): - capnp.kj_loop().__aenter__.return_value = asyncio.Future() - capnp.kj_loop().__aexit__.return_value = asyncio.Future() - await labone.core.helper.ensure_capnp_event_loop() - capnp.kj_loop().__aenter__.assert_called_once() - capnp.kj_loop().__aexit__.assert_not_called() - - -@patch("labone.core.helper.capnp", autospec=True) -def test_event_loop_exit_ok(capnp): - capnp.kj_loop().__aenter__.return_value = asyncio.Future() - capnp.kj_loop().__aexit__.return_value = asyncio.Future() - asyncio.run(labone.core.helper.ensure_capnp_event_loop()) - capnp.kj_loop().__aenter__.assert_called_once() - capnp.kj_loop().__aexit__.assert_called_once() diff --git a/tests/core/test_parse_wire_schema.py b/tests/core/test_parse_wire_schema.py deleted file mode 100644 index a768bf0..0000000 --- a/tests/core/test_parse_wire_schema.py +++ /dev/null @@ -1,42 +0,0 @@ -from unittest.mock import MagicMock, patch - -from labone.core.reflection import parsed_wire_schema - - -@patch("labone.core.reflection.parsed_wire_schema.capnp", autospec=True) -def test_reflection_server(capnp): - encoded_schema = MagicMock() - schema_loader = MagicMock() - capnp.SchemaLoader.return_value = schema_loader - - encoded_nodes = [MagicMock(), MagicMock(), MagicMock()] - - def iter_encoded_nodes(): - yield from encoded_nodes - - encoded_schema.__iter__.side_effect = iter_encoded_nodes - - nodes = [MagicMock(), MagicMock(), MagicMock()] - schema_loader.load_dynamic.side_effect = nodes - nodes[0].get_proto.return_value.displayName = "Test:a.b.c" - nodes[0].get_proto.return_value.id = "id1" - nodes[1].get_proto.return_value.displayName = "Test2:a.b.d" - nodes[1].get_proto.return_value.id = "id2" - nodes[1].get_proto.return_value.nestedNodes = [nodes[0].get_proto.return_value] - nodes[2].get_proto.return_value.displayName = "Test3:a.b.2" - nodes[2].get_proto.return_value.id = "id3" - - parsed_schema = parsed_wire_schema.ParsedWireSchema(encoded_schema) - assert len(parsed_schema.full_schema) == 3 - assert parsed_schema.full_schema["id1"].name == "c" - assert parsed_schema.full_schema["id2"].name == "d" - assert parsed_schema.full_schema["id3"].name == "2" - assert parsed_schema.full_schema["id1"].file_of_origin == "Test" - assert parsed_schema.full_schema["id2"].file_of_origin == "Test2" - assert parsed_schema.full_schema["id3"].file_of_origin == "Test3" - assert parsed_schema.full_schema["id1"].is_nested is True - assert parsed_schema.full_schema["id2"].is_nested is False - assert parsed_schema.full_schema["id3"].is_nested is False - assert parsed_schema.full_schema["id1"].schema == nodes[0] - assert parsed_schema.full_schema["id2"].schema == nodes[1] - assert parsed_schema.full_schema["id3"].schema == nodes[2] diff --git a/tests/core/test_reflection_server.py b/tests/core/test_reflection_server.py deleted file mode 100644 index c2af914..0000000 --- a/tests/core/test_reflection_server.py +++ /dev/null @@ -1,267 +0,0 @@ -from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch - -import capnp -import pytest - -import labone.core.reflection.server as reflection_server -from labone.core.errors import LabOneCoreError - - -@pytest.mark.asyncio() -@patch( - "labone.core.reflection.server.ReflectionServer.create_from_connection", - side_effect=AsyncMock(return_value="Test"), - autospec=True, -) -@patch("labone.core.reflection.server.capnp", autospec=True) -async def test_create_host_port(capnp, create_from_connection): - capnp.AsyncIoStream.create_connection = AsyncMock(return_value="connection") - assert await reflection_server.ReflectionServer.create("host", 1234) == "Test" - capnp.AsyncIoStream.create_connection.assert_called_once_with( - host="host", - port=1234, - ) - create_from_connection.assert_called_once_with("connection", unwrap_result=True) - - -@pytest.mark.asyncio() -@patch( - "labone.core.reflection.server.ReflectionServer.__init__", - autospec=True, - return_value=None, -) -@patch( - "labone.core.reflection.server._fetch_encoded_schema", - side_effect=AsyncMock( - return_value=("bootstrap_capability_id", "encoded_schema"), - ), - autospec=True, -) -@patch( - "labone.core.reflection.server.capnp.TwoPartyClient", - return_value="two_party_client", -) -async def test_create_from_connection( - two_party_client, - fetch_encoded_schema, - init_server, -): - created_server = await reflection_server.ReflectionServer.create_from_connection( - "dummy_connection", - ) - two_party_client.assert_called_once_with("dummy_connection") - fetch_encoded_schema.assert_called_once_with("two_party_client") - init_server.assert_called_once_with( - created_server, - connection="dummy_connection", - client="two_party_client", - encoded_schema="encoded_schema", - bootstrap_capability_id="bootstrap_capability_id", - unwrap_result=True, - ) - - -@pytest.mark.asyncio() -async def test_fetch_encoded_schema_ok(): - client = MagicMock() - schema = AsyncMock() - client.bootstrap().cast_as.return_value.getTheSchema.side_effect = schema - result = await reflection_server._fetch_encoded_schema(client) - assert result == ( - schema.return_value.theSchema.typeId, - schema.return_value.theSchema.theSchema, - ) - - -@pytest.mark.asyncio() -async def test_fetch_encoded_schema_err(): - client = MagicMock() - client.bootstrap.return_value.cast_as.return_value.getTheSchema.side_effect = ( - capnp.lib.capnp.KjException("test") - ) - with pytest.raises(LabOneCoreError): - await reflection_server._fetch_encoded_schema(client) - - -@patch( - "labone.core.reflection.server.ParsedWireSchema", - autospec=True, -) -@patch( - "labone.core.reflection.server.build_type_system", - autospec=True, -) -def test_reflection_server(build_type_system, parsed_schema): - parsed_schema.return_value.full_schema.__getitem__().name = "CapabilityName" - dummy_schema = MagicMock() - build_type_system.side_effect = lambda _, server: setattr( - server, - "CapabilityName", - dummy_schema, - ) - client = MagicMock() - server = reflection_server.ReflectionServer( - connection="connection", - client=client, - encoded_schema="encoded_schema", - bootstrap_capability_id="bootstrap_capability_id", - unwrap_result=False, - ) - assert server.capability_name == client.bootstrap().cast_as.return_value - assert server.CapabilityName == dummy_schema - parsed_schema.assert_called_once_with("encoded_schema") - build_type_system.assert_called_once_with( - parsed_schema.return_value.full_schema, - server, - ) - - -@patch( - "labone.core.reflection.server.ParsedWireSchema", - autospec=True, -) -@patch( - "labone.core.reflection.server.build_type_system", - autospec=True, -) -def test_reflection_server_unwrap(build_type_system, parsed_schema): - parsed_schema.return_value.full_schema.__getitem__().name = "CapabilityName" - dummy_schema = MagicMock() - build_type_system.side_effect = lambda _, server: setattr( - server, - "CapabilityName", - dummy_schema, - ) - client = MagicMock() - server = reflection_server.ReflectionServer( - connection="connection", - client=client, - encoded_schema="encoded_schema", - bootstrap_capability_id="bootstrap_capability_id", - unwrap_result=True, - ) - assert server.capability_name._capability == client.bootstrap().cast_as.return_value - assert server.CapabilityName == dummy_schema - parsed_schema.assert_called_once_with("encoded_schema") - build_type_system.assert_called_once_with( - parsed_schema.return_value.full_schema, - server, - ) - - -def test_capability_wrapper_dir(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - fake_capability.testResult = MagicMock() - - assert "testResult" in dir(wrapper) - assert "test_result" in dir(wrapper) - - -def test_capability_wrapper_get_unkown_attr(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - test = wrapper.setDummy - assert test == fake_capability.setDummy - - -def test_capability_wrapper_get_request_unkown_attr(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - test = wrapper.setDummy_request - assert test == fake_capability.setDummy_request - - -@pytest.mark.asyncio() -async def test_capability_wrapper_get_send(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - type(fake_capability.schema).method_names_inherited = PropertyMock( - return_value=["setDummy"], - ) - fake_capability._send = AsyncMock() - test = await wrapper.setDummy(123) - fake_capability._send.assert_awaited_once_with("setDummy", 123) - assert test == fake_capability._send.return_value - - -@pytest.mark.asyncio() -async def test_capability_wrapper_get_send_snake_case(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - type(fake_capability.schema).method_names_inherited = PropertyMock( - return_value=["setDummy"], - ) - fake_capability._send = AsyncMock() - test = await wrapper.set_dummy(123) - fake_capability._send.assert_awaited_once_with("setDummy", 123) - assert test == fake_capability._send.return_value - - -def test_capability_wrapper_get_request(): - fake_capability = MagicMock() - wrapper = reflection_server.CapabilityWrapper(fake_capability) - type(fake_capability.schema).method_names_inherited = PropertyMock( - return_value=["setDummy"], - ) - test = wrapper.setDummy_request() - assert isinstance(test, reflection_server.RequestWrapper) - fake_capability._request.assert_called_once() - - -@pytest.mark.asyncio() -async def test_request_wrapper_send(): - fake_capnp_request = AsyncMock() - wrapper = reflection_server.RequestWrapper(fake_capnp_request) - await wrapper.send() - fake_capnp_request.send.assert_called_once() - - -def test_request_wrapper_get(): - fake_capnp_request = MagicMock() - wrapper = reflection_server.RequestWrapper(fake_capnp_request) - assert wrapper.testValue == fake_capnp_request.testValue - assert wrapper.test_value == fake_capnp_request.testValue - - -def test_request_wrapper_set(): - fake_capnp_request = MagicMock() - wrapper = reflection_server.RequestWrapper(fake_capnp_request) - wrapper.testValue = 1 - assert fake_capnp_request.testValue == 1 - wrapper.test_value = 1 - assert fake_capnp_request.testValue == 1 - - -def test_request_wrapper_dir(): - fake_capnp_request = MagicMock() - wrapper = reflection_server.RequestWrapper(fake_capnp_request) - fake_capnp_request.testValue = MagicMock() - - assert "testValue" in dir(wrapper) - assert "test_value" in dir(wrapper) - - -def test_maybe_wrap_interface_passthrough(): - maybe_capability = MagicMock() - assert reflection_server._maybe_wrap_interface(maybe_capability) == maybe_capability - - -def test_maybe_wrap_interface_wrapped(): - maybe_capability = MagicMock(spec=capnp.lib.capnp._DynamicCapabilityClient) - - result = reflection_server._maybe_wrap_interface(maybe_capability) - assert isinstance(result, reflection_server.CapabilityWrapper) - - -def test_maybe_unwrap_passthrough(): - maybe_result = MagicMock() - assert reflection_server._maybe_unwrap(maybe_result) - - -def test_maybe_unwrap_unwrap(): - maybe_result = MagicMock() - maybe_result.schema.fields_list = [MagicMock()] - maybe_result.result = MagicMock() - result = reflection_server._maybe_unwrap(maybe_result) - assert result == maybe_result.result.ok diff --git a/tests/core/test_reflection_type_system.py b/tests/core/test_reflection_type_system.py deleted file mode 100644 index 22eab20..0000000 --- a/tests/core/test_reflection_type_system.py +++ /dev/null @@ -1,284 +0,0 @@ -from typing import Any -from unittest.mock import MagicMock - -import pytest -from capnp.lib.capnp import ( - _EnumModule, - _InterfaceModule, - _StructModule, -) - -from labone.core.reflection import capnp_dynamic_type_system -from labone.core.reflection.parsed_wire_schema import LoadedNode - - -class DummyServer: - def __init__(self): - self._assigned_attr = {} - - def __setattr__(self, name: str, value: Any) -> None: - if name.startswith("_"): - return super().__setattr__(name, value) - self._assigned_attr[name] = value - return None - - def assigned_attrs(self) -> dict: - return self._assigned_attr - - -def test_reflection_type_empty_name(): - full_schema = { - "id1": LoadedNode( - name="", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assert not root_module.assigned_attrs() - - -def test_reflection_type_nested(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=True, - ), - } - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assert not root_module.assigned_attrs() - - -def test_reflection_type_ignored_dollar_sign(): - full_schema = { - "id1": LoadedNode( - name="a$2", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - "id2": LoadedNode( - name="$a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - "id3": LoadedNode( - name="a$", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assert not root_module.assigned_attrs() - - -def test_reflection_type_ignored_skip_name(): - full_schema = { - "id1": LoadedNode( - name="Hello/1/2/3", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=True, - ), - "id2": LoadedNode( - name="/1/2/Hello", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=True, - ), - "id3": LoadedNode( - name="Hello", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=True, - ), - } - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system( - full_schema, - root_module, - skip_files=["Hello"], - ) - assert not root_module.assigned_attrs() - - -def test_reflection_type_struct(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = True - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = False - full_schema["id1"].schema.get_proto.return_value.isEnum = False - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert isinstance(assigned_attrs["a"], _StructModule) - with pytest.raises(TypeError): - assigned_attrs["a"].Reader() - with pytest.raises(TypeError): - assigned_attrs["a"].Builder() - - -def test_reflection_type_const(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = True - full_schema["id1"].schema.get_proto.return_value.isInterface = False - full_schema["id1"].schema.get_proto.return_value.isEnum = False - - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert assigned_attrs["a"] == full_schema["id1"].schema.as_const_value() - - -def test_reflection_type_interface(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = True - full_schema["id1"].schema.get_proto.return_value.isEnum = False - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert isinstance(assigned_attrs["a"], _InterfaceModule) - - -def test_reflection_type_enum(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = False - full_schema["id1"].schema.get_proto.return_value.isEnum = True - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert isinstance(assigned_attrs["a"], _EnumModule) - - -def test_reflection_type_unknown(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = False - full_schema["id1"].schema.get_proto.return_value.isEnum = False - root_module = DummyServer() - - with pytest.raises(RuntimeError): - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - - -def test_reflection_type_creation_nested(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - "id2": LoadedNode( - name="b", - schema=MagicMock(), - file_of_origin="Test2", - is_nested=True, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = True - full_schema["id1"].schema.get_proto.return_value.isEnum = False - nested_node = MagicMock() - full_schema["id1"].schema.get_proto.return_value.nestedNodes = [nested_node] - nested_node.id = "id2" - - full_schema["id2"].schema.get_proto.return_value.isStruct = False - full_schema["id2"].schema.get_proto.return_value.isConst = False - full_schema["id2"].schema.get_proto.return_value.isInterface = True - full_schema["id2"].schema.get_proto.return_value.isEnum = False - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert isinstance(assigned_attrs["a"], _InterfaceModule) - assert hasattr(assigned_attrs["a"], "b") - assert isinstance(assigned_attrs["a"].b, _InterfaceModule) - - -def test_reflection_type_creation_nested_unknown(): - full_schema = { - "id1": LoadedNode( - name="a", - schema=MagicMock(), - file_of_origin="Test1", - is_nested=False, - ), - } - full_schema["id1"].schema.get_proto.return_value.isStruct = False - full_schema["id1"].schema.get_proto.return_value.isConst = False - full_schema["id1"].schema.get_proto.return_value.isInterface = True - full_schema["id1"].schema.get_proto.return_value.isEnum = False - nested_node = MagicMock() - full_schema["id1"].schema.get_proto.return_value.nestedNodes = [nested_node] - nested_node.id = "id2" - root_module = DummyServer() - - capnp_dynamic_type_system.build_type_system(full_schema, root_module) - assigned_attrs = root_module.assigned_attrs() - assert len(assigned_attrs) == 1 - assert isinstance(assigned_attrs["a"], _InterfaceModule) - assert not hasattr(assigned_attrs["a"], "b") diff --git a/tests/core/test_result.py b/tests/core/test_result.py deleted file mode 100644 index 2ba6a8d..0000000 --- a/tests/core/test_result.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Tests unwrapping of a capnp result.""" - -from dataclasses import dataclass - -import capnp -import pytest - -from labone.core import errors -from labone.core.result import unwrap - - -@dataclass -class FakeError: - kind: int - message: str - code: int = 0 - category: str = "" - - -class FakeResult: - def __init__(self): - self._ok = None - self._err = None - - @property - def ok(self): - if self._err is not None or self._ok is None: - msg = "test" - raise capnp.KjException(msg) - return self._ok - - @ok.setter - def ok(self, value): - self._ok = value - - @property - def err(self): - if self._err is None: - msg = "test" - raise capnp.KjException(msg) - return self._err - - @err.setter - def err(self, value): - self._err = value - - -def test_unwrap_ok(): - msg = FakeResult() - msg.ok = "test" - assert unwrap(msg) == "test" - - -@pytest.mark.parametrize( - ("error_kind", "exception"), - [ - (1, errors.CancelledError), - (3, errors.NotFoundError), - (4, errors.OverwhelmedError), - (5, errors.BadRequestError), - (6, errors.UnimplementedError), - (7, errors.InternalError), - (8, errors.UnavailableError), - (9, errors.LabOneTimeoutError), - ], -) -def test_unwrap_error_generic(error_kind, exception): - msg = FakeResult() - msg.err = FakeError(kind=error_kind, message="test") - with pytest.raises(exception, match="test"): - unwrap(msg) - - -def test_invalid_capnp_response(): - msg = FakeResult() - with pytest.raises(errors.LabOneCoreError): - unwrap(msg) diff --git a/tests/core/test_session.py b/tests/core/test_session.py deleted file mode 100644 index 8727c5d..0000000 --- a/tests/core/test_session.py +++ /dev/null @@ -1,993 +0,0 @@ -"""Tests for `labone.core.session.Session` functionality that requires a server.""" - -from __future__ import annotations - -import asyncio -import json -import socket -import traceback -import typing -from contextlib import AsyncExitStack -from dataclasses import dataclass, field -from itertools import cycle -from typing import Any, Callable -from unittest.mock import AsyncMock, MagicMock - -import capnp -import pytest -import pytest_asyncio - -from labone.core import errors -from labone.core.connection_layer import ServerInfo, ZIKernelInfo -from labone.core.helper import request_field_type_description -from labone.core.kernel_session import KernelSession -from labone.core.reflection.server import ReflectionServer -from labone.core.session import ( - ListNodesFlags, - ListNodesInfoFlags, - Session, - _send_and_wait_request, -) -from labone.core.subscription import DataQueue -from labone.core.value import AnnotatedValue -from labone.mock import AutomaticSessionFunctionality, spawn_hpk_mock -from labone.mock.entry_point import MockSession -from labone.mock.hpk_schema import get_schema -from labone.server.server import start_local_server -from labone.server.session import SessionInterface - -from .resources import session_protocol_capnp, testfile_capnp, value_capnp - - -class SessionBootstrap(session_protocol_capnp.Session.Server): - """A bootstrap of `labone.core.resource.session_protocol.Session` Server""" - - def __init__(self, mock): - self._mock = mock - - async def listNodes(self, _context, **_): # noqa: N802 - return self._mock.listNodes(_context.params, _context.results) - - async def listNodesJson(self, _context, **_): # noqa: N802 - return self._mock.listNodesJson(_context.params, _context.results) - - async def setValue(self, _context, **_): # noqa: N802 - return self._mock.setValue(_context.params, _context.results) - - async def getValue(self, _context, **_): # noqa: N802 - return self._mock.getValue(_context.params, _context.results) - - async def subscribe(self, _context, **_): - return self._mock.subscribe(_context.params, _context.results) - - -class CapnpServer: - """A capnp server.""" - - def __init__(self, connection: capnp.AsyncIoStream): - self._connection = connection - - @property - def connection(self) -> capnp.AsyncIoStream: - """Connection to the server.""" - return self._connection - - @classmethod - async def create( - cls, - obj: capnp.lib.capnp._DynamicCapabilityServer, - ) -> CapnpServer: - """Create a server for the given object.""" - read, write = socket.socketpair() - write = await capnp.AsyncIoStream.create_connection(sock=write) - _ = asyncio.create_task(cls._new_connection(write, obj)) - return cls(await capnp.AsyncIoStream.create_connection(sock=read)) - - @staticmethod - async def _new_connection( - stream: capnp.AsyncIoStream, - obj: capnp.lib.capnp._DynamicCapabilityServer, - ): - """Establish a new connection.""" - await capnp.TwoPartyServer(stream, bootstrap=obj).on_disconnect() - - -class DummyServer(typing.NamedTuple): - session: KernelSession - server: MagicMock - - -@pytest_asyncio.fixture() -async def mock_connection(reflection_server) -> tuple[KernelSession, MagicMock]: - """Fixture for `labone.core.Session` and the server it is connected to. - - Returns: - Session and a server mock, which is passed into `SessionBootstrap`. - """ - mock_server = MagicMock() - server = await CapnpServer.create(SessionBootstrap(mock_server)) - reflection = reflection_server - client = capnp.TwoPartyClient(server.connection) - reflection.session = client.bootstrap().cast_as(reflection.Session) - - session = KernelSession( - reflection_server=reflection, - kernel_info=ZIKernelInfo(), - server_info=ServerInfo(host="localhost", port=8004), - ) - return DummyServer(session=session, server=mock_server) - - -def test_session_with_unwrapping_reflection(reflection_server): - reflection_server.session = MagicMock() - - session = Session( - reflection_server.session, - reflection_server=reflection_server, - ) - - assert session._session == reflection_server.session.capnp_capability - - -class TestSessionListNodes: - @staticmethod - def mock_return_value(val: list) -> Callable: - def mock_method(_, results): - results.paths = val - - return mock_method - - @pytest.mark.asyncio() - @pytest.mark.parametrize( - ("from_server", "from_api"), - [ - (["foo", "bar"], ["foo", "bar"]), - ([], []), - ([""], [""]), - ], - ) - async def test_return_value(self, mock_connection, from_server, from_api): - mock_connection.server.listNodes.side_effect = self.mock_return_value( - from_server, - ) - r = await mock_connection.session.list_nodes("path") - assert r == from_api - - @pytest.mark.asyncio() - @pytest.mark.parametrize("path", [1, 1.1, ["a"], {"a": "b"}]) - async def test_invalid_path_type(self, mock_connection, path): - with pytest.raises(TypeError): - await mock_connection.session.list_nodes(path) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", list(ListNodesFlags)) - async def test_with_flags_enum(self, mock_connection, flags): - mock_connection.server.listNodes.side_effect = self.mock_return_value([]) - r = await mock_connection.session.list_nodes("path", flags=flags) - assert r == [] - - @pytest.mark.asyncio() - @pytest.mark.parametrize( - "flags", - [0, 46378, 983, 354, 44, 10000], - ) - async def test_with_flags_int(self, mock_connection, flags): - mock_connection.server.listNodes.side_effect = self.mock_return_value([]) - r = await mock_connection.session.list_nodes("path", flags=flags) - assert r == [] - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", ["foo", [3], None]) - async def test_with_flags_type_error(self, mock_connection, flags): - with pytest.raises(TypeError): - await mock_connection.session.list_nodes("path", flags=flags) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", [-2, -100]) - async def test_with_flags_value_error(self, mock_connection, flags): - with pytest.raises(ValueError): - await mock_connection.session.list_nodes("path", flags=flags) - - -class TestSessionListNodesJson: - @staticmethod - def mock_return_value(val: dict) -> Callable: - def mock_method(_, results): - results.nodeProps = json.dumps(val) - - return mock_method - - @pytest.mark.asyncio() - @pytest.mark.parametrize( - ("from_server", "from_api"), - [ - ({"foo": "bar", "bar": "foo"}, {"foo": "bar", "bar": "foo"}), - ({}, {}), - ], - ) - async def test_return_value(self, mock_connection, from_server, from_api): - mock_connection.server.listNodesJson.side_effect = self.mock_return_value( - from_server, - ) - r = await mock_connection.session.list_nodes_info("path") - assert r == from_api - - @pytest.mark.asyncio() - @pytest.mark.parametrize("path", [1, 1.1, ["a"], {"a": "b"}]) - async def test_invalid_path_type(self, mock_connection, path): - with pytest.raises(TypeError): - await mock_connection.session.list_nodes_info(path) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", list(ListNodesInfoFlags)) - async def test_with_flags_enum(self, mock_connection, flags): - mock_connection.server.listNodesJson.side_effect = self.mock_return_value({}) - r = await mock_connection.session.list_nodes_info("path", flags=flags) - assert r == {} - - @pytest.mark.asyncio() - @pytest.mark.parametrize( - "flags", - [0, 46378, 983, 354, 44, 10000], - ) - async def test_with_flags_int(self, mock_connection, flags): - mock_connection.server.listNodesJson.side_effect = self.mock_return_value({}) - r = await mock_connection.session.list_nodes_info("path", flags=flags) - assert r == {} - mock_connection.server.listNodesJson.assert_called_once() - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", ["foo", [3], None]) - async def test_with_flags_type_error(self, mock_connection, flags): - with pytest.raises(TypeError): - await mock_connection.session.list_nodes_info("path", flags=flags) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("flags", [-2, -100]) - async def test_with_flags_value_error(self, mock_connection, flags): - with pytest.raises(ValueError): - await mock_connection.session.list_nodes_info("path", flags=flags) - - -async def mock_remote_response(response): - """Simple function that returns a promise that resolves to `response`.""" - return response - - -async def mock_remote_error(error): - """Simple function that returns a promise that rejects with `error`.""" - raise error - - -class MockRequest: - """Mock of `capnp.lib.capnp._Request`""" - - def __init__( - self, - send_response: Any = None, - send_raise_for: Any = None, - ): - self._send_response = send_response - self._send_raise_for = send_raise_for - - def send(self): - if self._send_raise_for: - raise self._send_raise_for - return self._send_response - - -class TestSendAndWaitRequest: - @pytest.mark.asyncio() - async def test_success(self): - promise = mock_remote_response("foobar") - response = await _send_and_wait_request(MockRequest(promise)) - assert response == "foobar" - - @pytest.mark.asyncio() - async def test_send_kj_error(self): - with pytest.raises(errors.LabOneCoreError, match="error"): - await _send_and_wait_request( - MockRequest(send_raise_for=capnp.lib.capnp.KjException("error")), - ) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("error", [RuntimeError, AttributeError, ValueError]) - async def test_send_misc_error(self, error): - with pytest.raises(errors.LabOneCoreError, match="error"): - await _send_and_wait_request( - MockRequest(send_raise_for=error("error")), - ) - - @pytest.mark.asyncio() - async def test_suppress_unwanted_traceback(self): - # Flaky test.. - try: - await _send_and_wait_request( - MockRequest(send_raise_for=capnp.lib.capnp.KjException("error")), - ) - except errors.LabOneCoreError: - assert "KjException" not in traceback.format_exc() - - promise = mock_remote_error(RuntimeError("error")) - try: - await _send_and_wait_request(MockRequest(promise)) - except errors.LabOneCoreError: - assert "RuntimeError" not in traceback.format_exc() - - @pytest.mark.asyncio() - async def test_a_wait_kj_error(self): - promise = mock_remote_error(capnp.lib.capnp.KjException("error")) - with pytest.raises(errors.LabOneCoreError, match="error"): - await _send_and_wait_request(MockRequest(promise)) - - @pytest.mark.asyncio() - @pytest.mark.parametrize("error", [RuntimeError, AttributeError, ValueError]) - async def test_a_wait_misc_error(self, error): - promise = mock_remote_error(error("error")) - with pytest.raises(errors.LabOneCoreError, match="error"): - await _send_and_wait_request(MockRequest(promise)) - - -@pytest.mark.asyncio() -async def test_capnprequest_field_type_description(): - class TestInterface(testfile_capnp.TestInterface.Server): - pass - - client = testfile_capnp.TestInterface._new_client(TestInterface()) - request = client.testMethod_request() - assert request_field_type_description(request, "testUint32Field") == "uint32" - assert request_field_type_description(request, "testTextField") == "text" - - -def session_proto_value_to_python(builder): - """`labone.core.resources.session_protocol_capnp:Value` to a Python value.""" - return getattr(builder, builder.which()) - - -@dataclass -class ServerRecords: - params: list[Any] = field(default_factory=list) - - -class TestSetValue: - """Integration tests for Session node set values functionality.""" - - @pytest.mark.asyncio() - async def test_server_receives_correct_value(self, mock_connection): - recorder = ServerRecords() - - def mock_method(params, _): - param_builder = params.as_builder() - recorder.params.append(param_builder) - - mock_connection.server.setValue.side_effect = mock_method - - value = AnnotatedValue(value=12, path="/foo/bar") - with pytest.raises(errors.LabOneCoreError): - await mock_connection.session.set(value) - assert len(recorder.params) == 1 - assert recorder.params[0].pathExpression == "/foo/bar" - assert session_proto_value_to_python(recorder.params[0].value) == 12 - - @pytest.mark.asyncio() - async def test_server_response_ok(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].ok = value.to_capnp(reflection=reflection_server) - - mock_connection.server.setValue.side_effect = mock_method - response = await mock_connection.session.set(value) - assert response == value - - @pytest.mark.asyncio() - async def test_server_response_err_single(self, mock_connection): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - - mock_connection.server.setValue.side_effect = mock_method - - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.set(value) - - @pytest.mark.asyncio() - async def test_server_response_err_multiple(self, mock_connection): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - builder[1].from_dict({"err": {"code": 1, "message": "test2 error"}}) - - mock_connection.server.setValue.side_effect = mock_method - - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.set(value) - - @pytest.mark.asyncio() - async def test_illegal_input_list_int(self, mock_connection): - value = AnnotatedValue(value=123, path=["/bar/foobar", "/foo/bar"], timestamp=0) - with pytest.raises(TypeError): - await mock_connection.session.set(value) - - -class TestSetValueWithPathExpression: - """Integration tests for Session node set values functionality.""" - - @pytest.mark.asyncio() - async def test_server_receives_correct_value(self, mock_connection): - recorder = ServerRecords() - - def mock_method(params, _): - param_builder = params.as_builder() - recorder.params.append(param_builder) - - mock_connection.server.setValue.side_effect = mock_method - - value = AnnotatedValue(value=12, path="/foo/bar") - result = await mock_connection.session.set_with_expression(value) - assert len(recorder.params) == 1 - assert recorder.params[0].pathExpression == "/foo/bar" - assert session_proto_value_to_python(recorder.params[0].value) == 12 - assert result == [] - - @pytest.mark.asyncio() - async def test_server_response_ok_single(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].ok = value.to_capnp(reflection=reflection_server) - - mock_connection.server.setValue.side_effect = mock_method - response = await mock_connection.session.set_with_expression(value) - assert response[0] == value - - @pytest.mark.asyncio() - async def test_server_response_ok_multiple( - self, - mock_connection, - reflection_server, - ): - value0 = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - value1 = AnnotatedValue(value=124, path="/bar/foo", timestamp=0) - value2 = AnnotatedValue(value=125, path="/bar/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 3) - builder[0].ok = value0.to_capnp(reflection=reflection_server) - builder[1].ok = value1.to_capnp(reflection=reflection_server) - builder[2].ok = value2.to_capnp(reflection=reflection_server) - - mock_connection.server.setValue.side_effect = mock_method - response = await mock_connection.session.set_with_expression(value0) - assert response[0] == value0 - assert response[1] == value1 - assert response[2] == value2 - - @pytest.mark.asyncio() - async def test_server_response_err_single(self, mock_connection): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - - mock_connection.server.setValue.side_effect = mock_method - - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.set_with_expression(value) - - @pytest.mark.asyncio() - async def test_server_response_err_multiple(self, mock_connection): - value = AnnotatedValue(value=123, path="/bar/foobar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - builder[1].from_dict({"err": {"code": 1, "message": "test2 error"}}) - - mock_connection.server.setValue.side_effect = mock_method - - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.set_with_expression(value) - - @pytest.mark.asyncio() - async def test_server_response_err_mix(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].ok = value.to_capnp(reflection=reflection_server) - builder[1].from_dict({"err": {"code": 2, "message": "test2 error"}}) - - mock_connection.server.setValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test2 error"): - await mock_connection.session.set_with_expression(value) - - @pytest.mark.asyncio() - async def test_illegal_input_list_int(self, mock_connection): - value = AnnotatedValue(value=123, path=["/bar/foobar", "/foo/bar"], timestamp=0) - with pytest.raises(TypeError): - await mock_connection.session.set(value) - - -class TestGetValueWithExpression: - """Integration tests for Session node get values functionality.""" - - @pytest.mark.asyncio() - async def test_server_receives_correct_value(self, mock_connection): - recorder = ServerRecords() - - def mock_method(params, _): - param_builder = params.as_builder() - recorder.params.append(param_builder) - - mock_connection.server.getValue.side_effect = mock_method - result = await mock_connection.session.get_with_expression("/foo/*") - assert len(recorder.params) == 1 - assert recorder.params[0].pathExpression == "/foo/*" - assert result == [] - - @pytest.mark.asyncio() - async def test_server_response_ok_single(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].ok = value.to_capnp(reflection=reflection_server) - - mock_connection.server.getValue.side_effect = mock_method - response = await mock_connection.session.get_with_expression("/foo/bar") - assert response[0] == value - - @pytest.mark.asyncio() - async def test_server_response_ok_multiple( - self, - mock_connection, - reflection_server, - ): - value0 = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - value1 = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].ok = value0.to_capnp(reflection=reflection_server) - builder[1].ok = value1.to_capnp(reflection=reflection_server) - - mock_connection.server.getValue.side_effect = mock_method - response = await mock_connection.session.get_with_expression("/foo/bar") - assert response[0] == value0 - assert response[1] == value1 - - @pytest.mark.asyncio() - async def test_server_response_err_single(self, mock_connection): - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.get_with_expression("/foo/bar") - - @pytest.mark.asyncio() - async def test_server_response_err_multiple(self, mock_connection): - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - builder[1].from_dict({"err": {"code": 2, "message": "test2 error"}}) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.get_with_expression("/foo/bar") - - @pytest.mark.asyncio() - async def test_server_response_err_mix(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].ok = value.to_capnp(reflection=reflection_server) - builder[1].from_dict({"err": {"code": 2, "message": "test2 error"}}) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test2 error"): - await mock_connection.session.get_with_expression("/foo/bar") - - @pytest.mark.asyncio() - async def test_illegal_input_list_string(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.get_with_expression(["/foo/bar"]) - - @pytest.mark.asyncio() - async def test_illegal_input_list_int(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.get_with_expression([1, 2, 3]) - - -class TestGetValue: - """Integration tests for Session node get values functionality.""" - - @pytest.mark.asyncio() - async def test_server_receives_correct_values_single(self, mock_connection): - recorder = ServerRecords() - - def mock_method(params, _): - param_builder = params.as_builder() - recorder.params.append(param_builder) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError): - await mock_connection.session.get("/foo/bar") - assert len(recorder.params) == 1 - assert recorder.params[0].pathExpression == "/foo/bar" - - @pytest.mark.asyncio() - async def test_server_response_ok_single(self, mock_connection, reflection_server): - value = AnnotatedValue(value=123, path="/foo/bar", timestamp=0) - - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].ok = value.to_capnp(reflection=reflection_server) - - mock_connection.server.getValue.side_effect = mock_method - response = await mock_connection.session.get("/foo/bar") - assert response == value - - @pytest.mark.asyncio() - async def test_server_response_err_single(self, mock_connection): - def mock_method(_, results): - builder = results.init("result", 1) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.get("/foo/bar") - - @pytest.mark.asyncio() - async def test_server_response_err_multiple(self, mock_connection): - def mock_method(_, results): - builder = results.init("result", 2) - builder[0].from_dict({"err": {"code": 1, "message": "test error"}}) - builder[1].from_dict({"err": {"code": 2, "message": "test2 error"}}) - - mock_connection.server.getValue.side_effect = mock_method - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.get("/foo/bar") - - @pytest.mark.asyncio() - async def test_illegal_input_list_string(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.get(["/foo/bar"]) - - @pytest.mark.asyncio() - async def test_illegal_input_list_int(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.get([1, 2, 3]) - - -class TestSessionSubscribe: - class SubscriptionServer: - def __init__(self, error=None): - self.server_handle = None - self.path = None - self.client_id = None - self.error = error - - def subscribe(self, params, results): - self.path = params.subscription.path - self.client_id = params.subscription.subscriberId - self.server_handle = params.subscription.streamingHandle - if self.error: - results.result.from_dict({"err": {"code": 1, "message": self.error}}) - else: - results.result.from_dict({"ok": {}}) - - @pytest.mark.asyncio() - async def test_subscribe_meta_data(self, mock_connection): - path = "/dev1234/demods/0/sample" - subscription_server = self.SubscriptionServer() - mock_connection.server.subscribe.side_effect = subscription_server.subscribe - queue = await mock_connection.session.subscribe(path) - assert subscription_server.path == path - assert subscription_server.client_id == mock_connection.session._client_id.bytes - assert queue.qsize() == 0 - assert queue.path == path - - @pytest.mark.asyncio() - async def test_subscribe_error(self, mock_connection): - path = "/dev1234/demods/0/sample" - subscription_server = self.SubscriptionServer(error="test error") - mock_connection.server.subscribe.side_effect = subscription_server.subscribe - - with pytest.raises(errors.LabOneCoreError, match="test error"): - await mock_connection.session.subscribe(path) - - @pytest.mark.asyncio() - async def test_subscribe_invalid_argument_dict(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.subscribe({"I": "am", "not": "a", "path": 1}) - - @pytest.mark.asyncio() - async def test_subscribe_invalid_argument_int(self, mock_connection): - with pytest.raises(TypeError): - await mock_connection.session.subscribe(2) - - @pytest.mark.parametrize("num_values", range(0, 20, 4)) - @pytest.mark.asyncio() - async def test_subscribe_send_value_ok(self, mock_connection, num_values): - path = "/dev1234/demods/0/sample" - subscription_server = self.SubscriptionServer() - mock_connection.server.subscribe.side_effect = subscription_server.subscribe - queue = await mock_connection.session.subscribe(path) - - values = [] - for i in range(num_values): - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = path - value.value.int64 = i - values.append(value) - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = "dummy" - value.value.int64 = 1 - await subscription_server.server_handle.sendValues(values) - assert queue.qsize() == num_values - for i in range(num_values): - assert queue.get_nowait() == AnnotatedValue( - value=i, - path=path, - timestamp=0, - extra_header=None, - ) - - @pytest.mark.asyncio() - async def test_subscribe_get_initial(self, mock_connection): - path = "/dev1234/demods/0/sample" - subscription_server = self.SubscriptionServer() - mock_connection.server.subscribe.side_effect = subscription_server.subscribe - mock_connection.session.get = AsyncMock( - return_value=AnnotatedValue(value=1, path=path), - ) - queue = await mock_connection.session.subscribe(path, get_initial_value=True) - - assert queue.qsize() == 1 - assert queue.get_nowait() == AnnotatedValue(value=1, path=path) - - -class TestSessionWaitForStateChange: - def create_queue(self, value, path): - queue = MagicMock(spec=DataQueue) - value = iter(cycle([value])) if isinstance(value, int) else iter(value) - - async def mock_queue_get(): - await asyncio.sleep(0.01) - return AnnotatedValue(value=next(value), path=path) - - queue.get.side_effect = mock_queue_get - return queue - - @pytest.mark.asyncio() - async def test_wait_for_state_change_already_correct(self, mock_connection): - path = "/foo/bar" - - mock_connection.session.subscribe = AsyncMock( - return_value=self.create_queue(1, path), - ) - - await asyncio.wait_for( - mock_connection.session.wait_for_state_change("/foo/bar", 1), - 0.1, - ) - - mock_connection.session.subscribe.assert_called_once_with( - path, - get_initial_value=True, - ) - - @pytest.mark.asyncio() - async def test_wait_for_state_change_timeout(self, mock_connection): - path = "/foo/bar" - - mock_connection.session.subscribe = AsyncMock( - return_value=self.create_queue(999, path), - ) - - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for( - mock_connection.session.wait_for_state_change("/foo/bar", 1), - 0.1, - ) - - mock_connection.session.subscribe.assert_called_once_with( - path, - get_initial_value=True, - ) - - @pytest.mark.asyncio() - async def test_wait_for_state_change_ok(self, mock_connection): - path = "/foo/bar" - - mock_connection.session.subscribe = AsyncMock( - return_value=self.create_queue([0, 0, 1], path), - ) - - await asyncio.wait_for( - mock_connection.session.wait_for_state_change("/foo/bar", 1), - 0.1, - ) - - mock_connection.session.subscribe.assert_called_once_with( - path, - get_initial_value=True, - ) - - -class BrokenSessionBootstrap(session_protocol_capnp.Session.Server): - """A bootstrap of `labone.core.resource.session_protocol.Session` Server""" - - def __init__(self, mock): - self._mock = mock - - -class TestKJErrors: - @pytest.mark.asyncio() - async def test_session_function_not_implemented(self, reflection_server): - mock_mock_connection = MagicMock() - broken_server = await CapnpServer.create( - BrokenSessionBootstrap(mock_mock_connection), - ) - reflection = reflection_server - client = capnp.TwoPartyClient(broken_server.connection) - reflection.session = client.bootstrap().cast_as(reflection.Session) - client = KernelSession( - reflection_server=reflection, - kernel_info=ZIKernelInfo(), - server_info=ServerInfo(host="localhost", port=8004), - ) - with pytest.raises(errors.UnavailableError): - await client.list_nodes("test") - - -@pytest.mark.asyncio() -async def test_set_transaction_no_wrapper(): - session = await spawn_hpk_mock( - AutomaticSessionFunctionality({"a": {"Type": "Integer"}}), - ) - target_value = 4656 - async with session.set_transaction() as transaction: - transaction.append(session.set(AnnotatedValue(path="a", value=target_value))) - assert (await session.get("a")).value == target_value - assert await session._supports_transaction() is False - - -@pytest.mark.asyncio() -async def test_set_transaction_additional_futures(): - session = await spawn_hpk_mock( - AutomaticSessionFunctionality({"a": {"Type": "Integer"}}), - ) - target_value = 4656 - async with session.set_transaction() as transaction: - transaction.append(session.set(AnnotatedValue(path="a", value=target_value))) - transaction.append(session.get("a")) - transaction.append(asyncio.sleep(0.001)) - assert (await session.get("a")).value == target_value - - -@pytest.mark.asyncio() -async def test_set_transaction_wrapper(): - session = await spawn_hpk_mock( - AutomaticSessionFunctionality( - { - "a": {"Type": "Integer"}, - "/ctrl/transaction/state": {"Type": "Integer"}, - }, - ), - ) - subscription_state = await session.subscribe("/ctrl/transaction/state") - target_value = 4656 - - # Use function so that we can store the acknowledged value which required to - # compare the timestamps - result = None - - async def set_value(): - nonlocal result - result = await session.set(AnnotatedValue(path="a", value=target_value)) - - async with session.set_transaction() as transaction: - transaction.append(set_value()) - assert (await session.get("a")).value == target_value - - assert subscription_state.qsize() == 2 - start = subscription_state.get_nowait() - assert start.value == 1 - end = subscription_state.get_nowait() - assert end.value == 0 - assert start.timestamp < result.timestamp < end.timestamp - assert await session._supports_transaction() is True - - -@pytest.mark.asyncio() -async def test_set_transaction_multiple_devices(): - session_a = await spawn_hpk_mock( - AutomaticSessionFunctionality({"a": {"Type": "Integer"}}), - ) - session_b = await spawn_hpk_mock( - AutomaticSessionFunctionality( - { - "a": {"Type": "Integer"}, - "/ctrl/transaction/state": {"Type": "Integer"}, - }, - ), - ) - async with AsyncExitStack() as stack: - transaction_a = await stack.enter_async_context(session_a.set_transaction()) - transaction_b = await stack.enter_async_context(session_b.set_transaction()) - transaction_a.append(session_a.set(AnnotatedValue(path="a", value=1))) - transaction_b.append(session_b.set(AnnotatedValue(path="a", value=2))) - - assert (await session_a.get("a")).value == 1 - assert (await session_b.get("a")).value == 2 - - -@pytest.mark.asyncio() -async def test_set_transaction_mix_multiple_devices(): - session_a = await spawn_hpk_mock( - AutomaticSessionFunctionality({"a": {"Type": "Integer"}}), - ) - session_b = await spawn_hpk_mock( - AutomaticSessionFunctionality( - { - "a": {"Type": "Integer"}, - "/ctrl/transaction/state": {"Type": "Integer"}, - }, - ), - ) - async with session_b.set_transaction() as transaction: - transaction.append(session_a.set(AnnotatedValue(path="a", value=1))) - transaction.append(session_b.set(AnnotatedValue(path="a", value=2))) - - assert (await session_a.get("a")).value == 1 - assert (await session_b.get("a")).value == 2 - - -class DummyServerVersionTest(SessionInterface): - def __init__(self, version: str): - super().__init__(None) - self._version = version - - async def getSessionVersion(self, _context): # noqa: N802 - return str(self._version) - - -@pytest.mark.parametrize( - ("version", "should_fail"), - [ - (Session.MIN_CAPABILITY_VERSION, False), - (Session.TESTED_CAPABILITY_VERSION, False), - ("1.0.0", True), - ("0.0.0", True), - (f"{Session.TESTED_CAPABILITY_VERSION.major +1}.0.0", True), - ], -) -@pytest.mark.asyncio() -async def test_ensure_compatibility_mismatch(version, should_fail): - mock_server, client_connection = await start_local_server( - schema=get_schema(), - server=DummyServerVersionTest(version), - ) - reflection = await ReflectionServer.create_from_connection(client_connection) - session = MockSession( - mock_server, - reflection.session, # type: ignore[attr-defined] - reflection=reflection, - ) - if should_fail: - with pytest.raises(errors.UnavailableError): - await session.ensure_compatibility() - else: - await session.ensure_compatibility() diff --git a/tests/core/test_shf_vector_data.py b/tests/core/test_shf_vector_data.py index c1c3200..0c4fbc6 100644 --- a/tests/core/test_shf_vector_data.py +++ b/tests/core/test_shf_vector_data.py @@ -3,6 +3,7 @@ import numpy as np import pytest +from labone.core.helper import VectorElementType from labone.core.shf_vector_data import ( SHFDemodSample, ShfDemodulatorVectorExtraHeader, @@ -46,6 +47,20 @@ def test_get_header_length(header_bytes, expected_length): ) +class VectorData: + def __init__( + self, + valueType, # noqa: N803 + extraHeaderInfo=0, # noqa: N803 + data=None, + vectorElementType=VectorElementType.UINT8, # noqa: N803 + ): + self.valueType = valueType + self.extraHeaderInfo = extraHeaderInfo + self.data = [] if data is None else data + self.vectorElementType = vectorElementType + + @pytest.mark.parametrize( "value_type", [ @@ -54,10 +69,8 @@ def test_get_header_length(header_bytes, expected_length): VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA, ], ) -def test_missing_extra_header(value_type, reflection_server): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = value_type.value - input_vector.extraHeaderInfo = 0 +def test_missing_extra_header(value_type): + input_vector = VectorData(valueType=value_type.value, extraHeaderInfo=0) result = parse_shf_vector_data_struct(input_vector) assert result[1] is None @@ -66,9 +79,8 @@ def _construct_extra_header_value(header_length, major_version, minor_version): return int(header_length / 4) | major_version << 21 | minor_version << 16 -def test_unsupported_vector_type(reflection_server): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = 80 +def test_unsupported_vector_type(): + input_vector = VectorData(valueType=80) with pytest.raises(ValueError): parse_shf_vector_data_struct(input_vector) @@ -81,15 +93,18 @@ def test_unsupported_vector_type(reflection_server): VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA, ], ) -def test_invalid_header_version(value_type, reflection_server): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = value_type.value - input_vector.extraHeaderInfo = _construct_extra_header_value( - header_length=8, - major_version=0, - minor_version=0, +def test_invalid_header_version( + value_type, +): + input_vector = VectorData( + valueType=value_type.value, + extraHeaderInfo=_construct_extra_header_value( + header_length=8, + major_version=0, + minor_version=0, + ), + data=b"\x00" * 16, ) - input_vector.data = b"\x00" * 16 with pytest.raises(Exception): # noqa: B017 parse_shf_vector_data_struct(input_vector) @@ -98,21 +113,21 @@ def test_invalid_header_version(value_type, reflection_server): @pytest.mark.parametrize("scaling", [x * 0.25 for x in range(5)]) @pytest.mark.parametrize("header_version", [1, 2]) @pytest.mark.parametrize(("x", "y"), [(0, 0), (1, 1), (32, 743)]) -def test_shf_scope_vector( # noqa: PLR0913 +def test_shf_scope_vector( vector_length, scaling, header_version, x, y, - reflection_server, ): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = VectorValueType.SHF_SCOPE_VECTOR_DATA.value header_length = 64 - input_vector.extraHeaderInfo = _construct_extra_header_value( - header_length=header_length, - major_version=0, - minor_version=header_version, + input_vector = VectorData( + valueType=VectorValueType.SHF_SCOPE_VECTOR_DATA.value, + extraHeaderInfo=_construct_extra_header_value( + header_length=header_length, + major_version=0, + minor_version=header_version, + ), ) # Manually set the scaling Factor header = b"\x00" * 16 + struct.pack("d", scaling) + b"\x00" @@ -164,15 +179,15 @@ def test_shf_demodulator_vector( # noqa: PLR0913 header_version, x, y, - reflection_server, ): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = VectorValueType.SHF_DEMODULATOR_VECTOR_DATA.value header_length = 64 - input_vector.extraHeaderInfo = _construct_extra_header_value( - header_length=header_length, - major_version=1, - minor_version=header_version, + input_vector = VectorData( + valueType=VectorValueType.SHF_DEMODULATOR_VECTOR_DATA.value, + extraHeaderInfo=_construct_extra_header_value( + header_length=header_length, + major_version=1, + minor_version=header_version, + ), ) # Manually set the scaling Factor header = ( @@ -216,15 +231,16 @@ def test_shf_demodulator_vector( # noqa: PLR0913 @pytest.mark.parametrize("vector_length", range(0, 200, 32)) @pytest.mark.parametrize("header_version", [1]) @pytest.mark.parametrize("x", range(0, 30, 7)) -def test_shf_result_logger_vector(vector_length, header_version, x, reflection_server): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA.value - input_vector.vectorElementType = 2 # uint32 +def test_shf_result_logger_vector(vector_length, header_version, x): header_length = 64 - input_vector.extraHeaderInfo = _construct_extra_header_value( - header_length=header_length, - major_version=0, - minor_version=header_version, + input_vector = VectorData( + valueType=VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA.value, + vectorElementType=2, + extraHeaderInfo=_construct_extra_header_value( + header_length=header_length, + major_version=0, + minor_version=header_version, + ), ) header = b"\x00" * header_length data = struct.pack("I", x) * vector_length @@ -253,12 +269,13 @@ def test_shf_result_logger_vector(vector_length, header_version, x, reflection_s @pytest.mark.parametrize("vector_length", range(0, 200, 32)) @pytest.mark.parametrize(("x", "y"), [(0, 0), (1, 1), (32, 743), (3785687, 1285732)]) -def test_shf_waveform_logger_vector(vector_length, x, y, reflection_server): - input_vector = reflection_server.VectorData.new_message() - input_vector.valueType = VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA.value - input_vector.vectorElementType = 2 # uint32 - input_vector.extraHeaderInfo = 0 - input_vector.data = (struct.pack("I", x) + struct.pack("I", y)) * vector_length +def test_shf_waveform_logger_vector(vector_length, x, y): + input_vector = VectorData( + valueType=VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA.value, + vectorElementType=2, # uint32 + extraHeaderInfo=0, + data=(struct.pack("I", x) + struct.pack("I", y)) * vector_length, + ) output_vector, extra_header = parse_shf_vector_data_struct(input_vector) const_scaling = 1 / 131071.0 # constant scaling factor based on the encoding bits assert np.array_equal( diff --git a/tests/core/test_subscription.py b/tests/core/test_subscription.py index 1b6882f..a47a0ee 100644 --- a/tests/core/test_subscription.py +++ b/tests/core/test_subscription.py @@ -2,7 +2,6 @@ import asyncio -import capnp import pytest from labone.core import errors @@ -10,12 +9,10 @@ CircularDataQueue, DataQueue, DistinctConsecutiveDataQueue, - streaming_handle_factory, + StreamingHandle, ) from labone.core.value import AnnotatedValue -from .resources import value_capnp - class FakeSubscription: def __init__(self): @@ -27,19 +24,19 @@ def register_data_queue(self, data_queue) -> None: def test_data_queue_path(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert queue.path == "dummy" def test_data_queue_maxsize(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert queue.maxsize == 0 def test_data_queue_maxsize_to_low(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.put_nowait("test") queue.put_nowait("test") queue.maxsize = 2 @@ -49,7 +46,7 @@ def test_data_queue_maxsize_to_low(): def test_data_queue_maxsize_disconnected(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.disconnect() with pytest.raises(errors.StreamingError): queue.maxsize = 42 @@ -57,13 +54,13 @@ def test_data_queue_maxsize_disconnected(): def test_data_queue_repr_idle(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert repr(queue) == "DataQueue(path='dummy', maxsize=0, qsize=0, connected=True)" def test_data_queue_repr(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.maxsize = 42 queue.put_nowait("test") queue.put_nowait("test") @@ -75,7 +72,7 @@ def test_data_queue_repr(): def test_data_queue_disconnect(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert queue.connected queue.disconnect() assert not queue.connected @@ -83,7 +80,7 @@ def test_data_queue_disconnect(): def test_data_queue_fork(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert len(subscription.data_queues) == 1 forked_queue = queue.fork() assert len(subscription.data_queues) == 2 @@ -93,7 +90,7 @@ def test_data_queue_fork(): def test_data_queue_fork_disconnected(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.disconnect() with pytest.raises(errors.StreamingError): queue.fork() @@ -101,7 +98,7 @@ def test_data_queue_fork_disconnected(): def test_data_queue_put_nowait(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) assert queue.qsize() == 0 queue.put_nowait("test") assert queue.qsize() == 1 @@ -111,7 +108,7 @@ def test_data_queue_put_nowait(): def test_data_queue_put_nowait_disconnected(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.disconnect() with pytest.raises(errors.StreamingError): queue.put_nowait("test") @@ -120,7 +117,7 @@ def test_data_queue_put_nowait_disconnected(): @pytest.mark.asyncio() async def test_data_queue_get(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.put_nowait("test") assert await queue.get() == "test" @@ -128,7 +125,7 @@ async def test_data_queue_get(): @pytest.mark.asyncio() async def test_data_queue_get_timeout(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(queue.get(), 0.01) @@ -136,7 +133,7 @@ async def test_data_queue_get_timeout(): @pytest.mark.asyncio() async def test_data_queue_get_disconnected_ok(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.put_nowait("test") queue.disconnect() assert await queue.get() == "test" @@ -145,7 +142,7 @@ async def test_data_queue_get_disconnected_ok(): @pytest.mark.asyncio() async def test_data_queue_get_disconnected_empty(): subscription = FakeSubscription() - queue = DataQueue(path="dummy", register_function=subscription.register_data_queue) + queue = DataQueue(path="dummy", handle=subscription) queue.disconnect() with pytest.raises(errors.EmptyDisconnectedDataQueueError): await queue.get() @@ -156,7 +153,7 @@ async def test_circular_data_queue_put_enough_space(): subscription = FakeSubscription() queue = CircularDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) queue.maxsize = 2 await asyncio.wait_for(queue.put("test"), timeout=0.01) @@ -169,7 +166,7 @@ async def test_circular_data_queue_put_full(): subscription = FakeSubscription() queue = CircularDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) queue.maxsize = 2 await asyncio.wait_for(queue.put("test1"), timeout=0.01) @@ -185,7 +182,7 @@ async def test_circular_data_queue_put_no_wait_enough_space(): subscription = FakeSubscription() queue = CircularDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) queue.maxsize = 2 queue.put_nowait("test") @@ -198,7 +195,7 @@ async def test_circular_data_queue_put_no_wait_full(): subscription = FakeSubscription() queue = CircularDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) queue.maxsize = 2 queue.put_nowait("test1") @@ -213,7 +210,7 @@ def test_circular_data_queue_fork(): subscription = FakeSubscription() queue = CircularDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) assert len(subscription.data_queues) == 1 forked_queue = queue.fork() @@ -223,33 +220,27 @@ def test_circular_data_queue_fork(): assert forked_queue.connected -def test_streaming_handle_register(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - DataQueue(path="dummy", register_function=streaming_handle.register_data_queue) +def test_streaming_handle_register(): + streaming_handle = StreamingHandle() + DataQueue(path="dummy", handle=streaming_handle) assert len(streaming_handle._data_queues) == 1 @pytest.mark.parametrize("num_values", range(0, 20, 4)) @pytest.mark.parametrize("num_queues", [1, 2, 6]) @pytest.mark.asyncio() -async def test_streaming_handle_update_event(num_values, num_queues, reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() +async def test_streaming_handle_update_event(num_values, num_queues): + streaming_handle = StreamingHandle() queues = [] for _ in range(num_queues): queue = DataQueue( path="dummy", - register_function=streaming_handle.register_data_queue, + handle=streaming_handle, ) queues.append(queue) - values = [] for i in range(num_values): - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = "dummy" - value.value.int64 = i - values.append(value) - await streaming_handle.sendValues(values) + value = AnnotatedValue(value=i, path="dummy", timestamp=0, extra_header=None) + streaming_handle.distribute_to_data_queues(value) for queue in queues: assert queue.qsize() == num_values for i in range(num_values): @@ -261,62 +252,18 @@ async def test_streaming_handle_update_event(num_values, num_queues, reflection_ ) -def test_streaming_handle_with_parser_callback(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle_class( +def test_streaming_handle_with_parser_callback(): + StreamingHandle( parser_callback=lambda a: AnnotatedValue(path=a.path, value=a.value * 2), ) -@pytest.mark.asyncio() -async def test_streaming_handle_update_empty(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - values = [] - value = value_capnp.AnnotatedValue.new_message() - values.append(value) - with pytest.raises(capnp.KjException): - await streaming_handle.sendValues(values) - - -@pytest.mark.asyncio() -async def test_streaming_handle_update_deleted(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - queue = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - del queue - values = [] - value = value_capnp.AnnotatedValue.new_message() - values.append(value) - with pytest.raises(capnp.KjException): - await streaming_handle.sendValues(values) - - -@pytest.mark.asyncio() -async def test_streaming_handle_update_disconnect(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - queue = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue.disconnect() - values = [] - value = value_capnp.AnnotatedValue.new_message() - values.append(value) - with pytest.raises(capnp.KjException): - await streaming_handle.sendValues(values) - - @pytest.mark.asyncio() async def test_distinct_data_queue_put_no_wait_new_value(): subscription = FakeSubscription() queue = DistinctConsecutiveDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) value1 = AnnotatedValue(value=1, path="dummy") value2 = AnnotatedValue(value=2, path="dummy") @@ -332,7 +279,7 @@ async def test_distinct_data_queue_put_no_wait_same_value(): subscription = FakeSubscription() queue = DistinctConsecutiveDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) value = AnnotatedValue(value=1, path="dummy") queue.put_nowait(value) @@ -345,7 +292,7 @@ def test_distinct_data_queue_fork(): subscription = FakeSubscription() queue = DistinctConsecutiveDataQueue( path="dummy", - register_function=subscription.register_data_queue, + handle=subscription, ) assert len(subscription.data_queues) == 1 forked_queue = queue.fork() @@ -353,91 +300,3 @@ def test_distinct_data_queue_fork(): assert len(subscription.data_queues) == 2 assert forked_queue.path == queue.path assert forked_queue.connected - - -@pytest.mark.asyncio() -async def test_streaming_handle_update_partially_disconnected(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - queue_0 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_1 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_0.disconnect() - values = [] - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = "dummy" - value.value.int64 = 1 - values.append(value) - await streaming_handle.sendValues(values) - assert queue_0.qsize() == 0 - assert queue_1.qsize() == 1 - assert queue_1.get_nowait() == AnnotatedValue( - value=1, - path="dummy", - timestamp=0, - extra_header=None, - ) - queue_1.disconnect() - with pytest.raises(capnp.KjException): - await streaming_handle.sendValues(values) - - -@pytest.mark.asyncio() -async def test_streaming_handle_update_queue_full_single(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - queue_0 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_1 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_0.maxsize = 1 - queue_0.put_nowait("dummy") - assert queue_0.qsize() == 1 - values = [] - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = "dummy" - value.value.int64 = 1 - values.append(value) - await streaming_handle.sendValues(values) - assert queue_0.qsize() == 1 - assert queue_1.qsize() == 1 - assert queue_1.get_nowait() == AnnotatedValue( - value=1, - path="dummy", - timestamp=0, - extra_header=None, - ) - - -@pytest.mark.asyncio() -async def test_streaming_handle_update_queue_full_multiple(reflection_server): - streaming_handle_class = streaming_handle_factory(reflection_server) - streaming_handle = streaming_handle_class() - queue_0 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_1 = DataQueue( - path="dummy", - register_function=streaming_handle.register_data_queue, - ) - queue_0.maxsize = 1 - queue_0.put_nowait("dummy") - queue_1.maxsize = 1 - queue_1.put_nowait("dummy") - values = [] - value = value_capnp.AnnotatedValue.new_message() - value.metadata.path = "dummy" - value.value.int64 = 1 - values.append(value) - with pytest.raises(capnp.KjException): - await streaming_handle.sendValues(values) diff --git a/tests/mock/ab_hpk_automatic_functionality_test.py b/tests/mock/ab_hpk_automatic_functionality_test.py index 5d4891f..7dc0fe4 100644 --- a/tests/mock/ab_hpk_automatic_functionality_test.py +++ b/tests/mock/ab_hpk_automatic_functionality_test.py @@ -22,16 +22,15 @@ import pytest -from labone.core import AnnotatedValue, KernelSession, ServerInfo, ZIKernelInfo +from labone.core import AnnotatedValue, KernelInfo, KernelSession, ServerInfo from labone.core.session import ListNodesFlags, Session -from labone.mock import spawn_hpk_mock -from labone.mock.automatic_session_functionality import AutomaticSessionFunctionality -from labone.mock.entry_point import MockSession +from labone.mock import AutomaticLabOneServer +from labone.server.session import MockSession async def get_session(): return await KernelSession.create( - kernel_info=ZIKernelInfo(), + kernel_info=KernelInfo.zi_connection(), server_info=ServerInfo(host="localhost", port=8004), ) @@ -41,8 +40,7 @@ async def get_mock_session() -> MockSession: session = await get_session() paths_to_info = await session.list_nodes_info("*") - functionality = AutomaticSessionFunctionality(paths_to_info) - return await spawn_hpk_mock(functionality) + return await AutomaticLabOneServer(paths_to_info).start_pipe() def same_prints_and_exceptions_for_real_and_mock(test_function): diff --git a/tests/mock/module_test.py b/tests/mock/module_test.py index 7e9c716..1836146 100644 --- a/tests/mock/module_test.py +++ b/tests/mock/module_test.py @@ -4,7 +4,7 @@ Subscription behavior can only be tested with a client, holding a queue and a mock server on the other side of capnp. Aiming to test the -AutomaticSessionFunctionality, +AutomaticLabOneServer, we still need to use a larger scope in order to test meaningful behavior. """ @@ -19,8 +19,7 @@ ShfResultLoggerVectorExtraHeader, ShfScopeVectorExtraHeader, ) -from labone.mock import spawn_hpk_mock -from labone.mock.automatic_session_functionality import AutomaticSessionFunctionality +from labone.mock import AutomaticLabOneServer @pytest.mark.asyncio() @@ -29,16 +28,13 @@ async def test_useable_via_entry_point(): Tests that a session can be established and used. Only looks that no error is raised. """ - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) - + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() await session.set(AnnotatedValue(path="/a/b", value=7)) @pytest.mark.asyncio() async def test_subscription(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") await session.set(AnnotatedValue(path="/a/b", value=7)) @@ -48,8 +44,7 @@ async def test_subscription(): @pytest.mark.asyncio() async def test_subscription_multiple_changes(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") await session.set(AnnotatedValue(path="/a/b", value=7)) @@ -63,8 +58,7 @@ async def test_subscription_multiple_changes(): @pytest.mark.asyncio() async def test_subscription_seperate_for_each_path(): - functionality = AutomaticSessionFunctionality({"/a/b": {}, "/a/c": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}, "/a/c": {}}).start_pipe() queue = await session.subscribe("/a/b") queue2 = await session.subscribe("/a/c") @@ -78,8 +72,7 @@ async def test_subscription_seperate_for_each_path(): @pytest.mark.asyncio() async def test_subscription_updated_by_set_with_expression(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") await session.set_with_expression(AnnotatedValue(path="/a", value=7)) @@ -105,11 +98,10 @@ async def test_shf_scope_vector_handled_correctly_through_set_and_subscription() 0, ) - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") - await functionality.set( + await session.mock_server.set( AnnotatedValue( path="/a/b", value=value.copy(), @@ -139,11 +131,10 @@ async def test_shf_result_logger_vector_handled_correctly_in_set_and_subscribe() 12, ) - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") - await functionality.set( + await session.mock_server.set( AnnotatedValue( path="/a/b", value=value.copy(), @@ -177,11 +168,10 @@ async def test_shf_demodulator_vector_handled_correctly_through_set_and_subscrip scaling=4.0000000467443897e-07, ) - functionality = AutomaticSessionFunctionality({"/a/b": {}}) - session = await spawn_hpk_mock(functionality) + session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() queue = await session.subscribe("/a/b") - await functionality.set( + await session.mock_server.set( AnnotatedValue( path="/a/b", value=SHFDemodSample(x=value.x.copy(), y=value.y.copy()), @@ -196,7 +186,5 @@ async def test_shf_demodulator_vector_handled_correctly_through_set_and_subscrip @pytest.mark.asyncio() async def test_ensure_compatibility(): - session = await spawn_hpk_mock( - AutomaticSessionFunctionality({}), - ) + session = await AutomaticLabOneServer({}).start_pipe() await session.ensure_compatibility() diff --git a/tests/mock/test_automatic_mock_functionality.py b/tests/mock/test_automatic_mock_functionality.py index 81cc686..4303847 100644 --- a/tests/mock/test_automatic_mock_functionality.py +++ b/tests/mock/test_automatic_mock_functionality.py @@ -1,4 +1,4 @@ -"""Unit Tests for the AutomaticSessionFunctionality.""" +"""Unit Tests for the AutomaticLabOneServer.""" from __future__ import annotations @@ -12,11 +12,11 @@ if TYPE_CHECKING: from labone.core.helper import LabOneNodePath from labone.core.value import AnnotatedValue, Value -from labone.mock import AutomaticSessionFunctionality +from labone.mock import AutomaticLabOneServer async def get_functionality_with_state(state: dict[LabOneNodePath, Value]): - functionality = AutomaticSessionFunctionality({path: {} for path in state}) + functionality = AutomaticLabOneServer({path: {} for path in state}) for path, value in state.items(): await functionality.set(AnnotatedValue(value=value, path=path)) return functionality @@ -24,18 +24,18 @@ async def get_functionality_with_state(state: dict[LabOneNodePath, Value]): @pytest.mark.asyncio() async def test_node_info_default_readable(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.get("/a/b") @pytest.mark.asyncio() async def test_node_info_default_writable(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.set(AnnotatedValue(path="/a/b", value=1)) async def check_state_agrees_with( - functionality: AutomaticSessionFunctionality, + functionality: AutomaticLabOneServer, state: dict[LabOneNodePath, Value], ) -> bool: for path, value in state.items(): @@ -46,21 +46,21 @@ async def check_state_agrees_with( @pytest.mark.asyncio() async def test_remembers_state(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.set(AnnotatedValue(value=123, path="/a/b")) assert (await functionality.get("/a/b")).value == 123 @pytest.mark.asyncio() async def test_relavtive_path(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.set(AnnotatedValue(value=123, path="b")) assert (await functionality.get("b")).value == 123 @pytest.mark.asyncio() async def test_state_overwritable(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.set(AnnotatedValue(value=123, path="/a/b")) await functionality.set(AnnotatedValue(value=456, path="/a/b")) assert (await functionality.get("/a/b")).value == 456 @@ -68,7 +68,7 @@ async def test_state_overwritable(): @pytest.mark.asyncio() async def test_seperate_state_per_path(): - functionality = AutomaticSessionFunctionality({"/a/b": {}, "/a/c": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}, "/a/c": {}}) await functionality.set(AnnotatedValue(value=123, path="/a/b")) await functionality.set(AnnotatedValue(value=456, path="/a/c")) assert (await functionality.get("/a/b")).value == 123 @@ -77,21 +77,21 @@ async def test_seperate_state_per_path(): @pytest.mark.asyncio() async def test_cannot_get_outside_of_tree_structure(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) with pytest.raises(Exception): # noqa: B017 await functionality.get("/a/c") @pytest.mark.asyncio() async def test_cannot_set_outside_of_tree_structure(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) with pytest.raises(Exception): # noqa: B017 await functionality.set(AnnotatedValue(value=123, path="/a/c")) @pytest.mark.asyncio() async def test_list_nodes_answered_by_tree_structure(): - functionality = AutomaticSessionFunctionality( + functionality = AutomaticLabOneServer( {"/x": {}, "/x/y": {}, "/v/w/q/a": {}}, ) assert set(await functionality.list_nodes("*")) == {"/x", "/x/y", "/v/w/q/a"} @@ -124,7 +124,7 @@ async def test_list_nodes_answered_by_tree_structure(): ) @pytest.mark.asyncio() async def test_list_nodes_info(path_to_info, path, expected): - functionality = AutomaticSessionFunctionality(path_to_info) + functionality = AutomaticLabOneServer(path_to_info) assert (await functionality.list_nodes_info(path)).keys() == expected.keys() @@ -150,7 +150,7 @@ async def test_list_nodes_info(path_to_info, path, expected): ) @pytest.mark.asyncio() async def test_consistency_list_nodes_vs_list_nodes_info(path_to_info, path): - functionality = AutomaticSessionFunctionality(path_to_info) + functionality = AutomaticLabOneServer(path_to_info) assert set((await functionality.list_nodes_info(path)).keys()) == set( await functionality.list_nodes(path), @@ -234,14 +234,14 @@ async def test_set_with_expression(expression, value, expected_new_state): ) @pytest.mark.asyncio() async def test_handling_of_multiple_data_types(value: Value): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) await functionality.set(AnnotatedValue(value=value, path="/a/b")) assert (await functionality.get("/a/b")).value == value @pytest.mark.asyncio() async def test_handling_of_numpy_array(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) value = np.array([1, 2, 3]) await functionality.set(AnnotatedValue(value=value, path="/a/b")) assert np.all((await functionality.get("/a/b")).value == value) @@ -249,7 +249,7 @@ async def test_handling_of_numpy_array(): @pytest.mark.asyncio() async def test_timestamps_are_increasing(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) # calling set 10 times to ensure higher probability for wrong order, # if timestamps are not increasing @@ -270,13 +270,13 @@ async def test_timestamps_are_increasing(): @pytest.mark.asyncio() async def test_cannot_set_readonly_node(): - functionality = AutomaticSessionFunctionality({"/a/b": {"Properties": "Read"}}) + functionality = AutomaticLabOneServer({"/a/b": {"Properties": "Read"}}) with pytest.raises(LabOneCoreError): await functionality.set(AnnotatedValue(value=1, path="/a/b")) @pytest.mark.asyncio() async def test_error_when_set_with_expression_no_matches(): - functionality = AutomaticSessionFunctionality({"/a/b": {}}) + functionality = AutomaticLabOneServer({"/a/b": {}}) with pytest.raises(LabOneCoreError): await functionality.set_with_expression(AnnotatedValue(value=1, path="/b/*")) diff --git a/tests/mock/test_session_mock_template.py b/tests/mock/test_session_mock_template.py index e5cf44e..bf8f737 100644 --- a/tests/mock/test_session_mock_template.py +++ b/tests/mock/test_session_mock_template.py @@ -9,26 +9,51 @@ server mock are compatible and work as a functional unit. """ -from unittest.mock import ANY, Mock +from __future__ import annotations + +from unittest.mock import ANY, AsyncMock import pytest from labone.core.session import ListNodesFlags from labone.core.value import AnnotatedValue -from labone.mock.entry_point import spawn_hpk_mock -from labone.server.session import SessionFunctionality +from labone.server.session import LabOneServerBase + + +class MockLabOneServer(LabOneServerBase): + async def set(self, value: AnnotatedValue) -> AnnotatedValue: ... + + async def get(self, path: str) -> AnnotatedValue: ... + + async def get_with_expression(self, path: str) -> list[AnnotatedValue]: ... + + async def set_with_expression( + self, + value: AnnotatedValue, + ) -> list[AnnotatedValue]: ... + + async def subscribe(self) -> None: ... + + async def list_nodes(self, path: str, flags: ListNodesFlags) -> list[str]: ... + + async def list_nodes_info( + self, + path: str, + flags: ListNodesFlags, + ) -> dict[str, dict]: ... @pytest.mark.asyncio() async def test_set_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.set = AsyncMock() functionality.set.return_value = AnnotatedValue( path="/mock/path", value=1, timestamp=2, ) - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.set(AnnotatedValue(path="/a/b", value=7)) @@ -41,14 +66,15 @@ async def test_set_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_get_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.get = AsyncMock() functionality.get.return_value = AnnotatedValue( path="/mock/path", value=1, timestamp=2, ) - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.get("/a/b") @@ -61,13 +87,14 @@ async def test_get_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_get_with_expression_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.get_with_expression = AsyncMock() functionality.get_with_expression.return_value = [ AnnotatedValue(path="/mock/path", value=1, timestamp=2), AnnotatedValue(path="/mock/path/2", value=3, timestamp=4), ] - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.get_with_expression("/a/b") @@ -83,13 +110,14 @@ async def test_get_with_expression_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_set_with_expression_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.set_with_expression = AsyncMock() functionality.set_with_expression.return_value = [ AnnotatedValue(path="/mock/path", value=1, timestamp=2), AnnotatedValue(path="/mock/path/2", value=1, timestamp=4), ] - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.set_with_expression(AnnotatedValue(path="/a/b", value=7)) @@ -107,22 +135,25 @@ async def test_set_with_expression_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_subscribe_propagates_to_functionality(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.subscribe = AsyncMock() + functionality.subscribe.return_value = None - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() await session.subscribe("/a/b") # propogates to functionality - functionality.subscribe_logic.assert_called_once() + functionality.subscribe.assert_called_once() @pytest.mark.asyncio() async def test_list_nodes_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.list_nodes = AsyncMock() functionality.list_nodes.return_value = ["/a/b", "/a/c"] - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.list_nodes("/a/b", flags=ListNodesFlags.ABSOLUTE) @@ -138,21 +169,19 @@ async def test_list_nodes_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_list_nodes_info_propagates_to_functionality_and_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.list_nodes_info = AsyncMock() functionality.list_nodes_info.return_value = { "/a/b": {"value": 1}, "/a/c": {"value": 2}, } - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() response = await session.list_nodes_info(path="/a/b", flags=ListNodesFlags.ABSOLUTE) # propogates to functionality - functionality.list_nodes_info.assert_called_once_with( - path="/a/b", - flags=ListNodesFlags.ABSOLUTE, - ) + functionality.list_nodes_info.assert_called_once() # returns functionality response assert response == { @@ -163,10 +192,11 @@ async def test_list_nodes_info_propagates_to_functionality_and_back(): @pytest.mark.asyncio() async def test_errors_in_set_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.set = AsyncMock() functionality.set.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.set(AnnotatedValue(path="/a/b", value=7)) @@ -174,10 +204,11 @@ async def test_errors_in_set_functionality_are_transmitted_back(): @pytest.mark.asyncio() async def test_errors_in_get_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.get = AsyncMock() functionality.get.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.get("/a/b") @@ -185,10 +216,11 @@ async def test_errors_in_get_functionality_are_transmitted_back(): @pytest.mark.asyncio() async def test_errors_in_get_with_expression_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.get_with_expression = AsyncMock() functionality.get_with_expression.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.get_with_expression("/a/b") @@ -196,10 +228,11 @@ async def test_errors_in_get_with_expression_functionality_are_transmitted_back( @pytest.mark.asyncio() async def test_errors_in_set_with_expression_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.set_with_expression = AsyncMock() functionality.set_with_expression.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.set_with_expression(AnnotatedValue(path="/a/b", value=7)) @@ -207,10 +240,11 @@ async def test_errors_in_set_with_expression_functionality_are_transmitted_back( @pytest.mark.asyncio() async def test_errors_in_subscribe_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) - functionality.subscribe_logic.side_effect = Exception("Some error") + functionality = MockLabOneServer() + functionality.subscribe = AsyncMock() + functionality.subscribe.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.subscribe("/a/b") @@ -218,10 +252,11 @@ async def test_errors_in_subscribe_functionality_are_transmitted_back(): @pytest.mark.asyncio() async def test_errors_in_list_nodes_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.list_nodes = AsyncMock() functionality.list_nodes.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.list_nodes("/a/b") @@ -229,10 +264,11 @@ async def test_errors_in_list_nodes_functionality_are_transmitted_back(): @pytest.mark.asyncio() async def test_errors_in_list_nodes_info_functionality_are_transmitted_back(): - functionality = Mock(spec=SessionFunctionality) + functionality = MockLabOneServer() + functionality.list_nodes_info = AsyncMock() functionality.list_nodes_info.side_effect = Exception("Some error") - session = await spawn_hpk_mock(functionality) + session = await functionality.start_pipe() with pytest.raises(Exception): # noqa: B017 await session.list_nodes_info(path="/a/b") diff --git a/tests/mock_server_for_testing.py b/tests/mock_server_for_testing.py index dfcb2f9..1e26b46 100644 --- a/tests/mock_server_for_testing.py +++ b/tests/mock_server_for_testing.py @@ -3,11 +3,10 @@ from __future__ import annotations import typing as t -from unittest.mock import Mock +from unittest.mock import AsyncMock, Mock from labone.core.session import NodeInfo, Session -from labone.mock.automatic_session_functionality import AutomaticSessionFunctionality -from labone.mock.entry_point import spawn_hpk_mock +from labone.mock import AutomaticLabOneServer from labone.nodetree.entry_point import construct_nodetree if t.TYPE_CHECKING: @@ -25,8 +24,7 @@ async def get_mocked_node( Use when testing calls to the server. """ - functionality = AutomaticSessionFunctionality(nodes_to_info) - session_mock = await spawn_hpk_mock(functionality) + session_mock = await AutomaticLabOneServer(nodes_to_info).start_pipe() return ( await construct_nodetree( session_mock, @@ -48,9 +46,9 @@ async def get_unittest_mocked_node( mock server. """ session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = ( - nodes_to_info # required for construction - ) + session_mock.list_nodes_info = AsyncMock(return_value=nodes_to_info) + session_mock.get_with_expression = AsyncMock() + return ( await construct_nodetree(session_mock, hide_kernel_prefix=hide_kernel_prefix) ).root diff --git a/tests/nodetree/test_node_to_session_interface.py b/tests/nodetree/test_node_to_session_interface.py index 8ec9805..9b291cf 100644 --- a/tests/nodetree/test_node_to_session_interface.py +++ b/tests/nodetree/test_node_to_session_interface.py @@ -5,7 +5,7 @@ from __future__ import annotations -from unittest.mock import ANY, Mock +from unittest.mock import ANY, AsyncMock, Mock import pytest @@ -19,8 +19,8 @@ async def test_get_translates_to_session(): value = AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4) session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.get.return_value = value + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.get = AsyncMock(return_value=value) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root assert value == await node.a.b.c.d() @@ -32,8 +32,8 @@ async def test_set_translates_to_session(): value = AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4) session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.set.return_value = value + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.set = AsyncMock(return_value=value) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root assert await node.a.b.c.d(42) == value @@ -47,8 +47,8 @@ async def test_partial_get_translates_to_session(): value = (AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4),) session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.get_with_expression.return_value = value + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.get_with_expression = AsyncMock(return_value=value) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node.a.b.c() @@ -60,8 +60,8 @@ async def test_partial_set_translates_to_session(): value = AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4) session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.set_with_expression.return_value = [value] + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.set_with_expression = AsyncMock(return_value=[value]) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node.a.b.c(32) @@ -75,8 +75,8 @@ async def test_wildcard_get_translates_to_session(): value = (AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4),) session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.get_with_expression.return_value = value + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.get_with_expression = AsyncMock(return_value=value) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node.a["*"].c.d() @@ -86,10 +86,12 @@ async def test_wildcard_get_translates_to_session(): @pytest.mark.asyncio() async def test_wildcard_set_translates_to_session(): session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.set_with_expression.return_value = [ - AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4), - ] + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.set_with_expression = AsyncMock( + return_value=[ + AnnotatedValue(path="/a/b/c/d", value=42, extra_header=3, timestamp=4), + ], + ) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node.a["*"].c.d(35) @@ -101,8 +103,8 @@ async def test_wildcard_set_translates_to_session(): @pytest.mark.asyncio() async def test_partial_subscribe_translates_to_session(): session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b/c/d": {}} - session_mock.subscribe.return_value = Mock() + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) + session_mock.subscribe = AsyncMock(return_value=Mock()) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node.a.b.c.d.subscribe() @@ -117,22 +119,23 @@ async def test_partial_subscribe_translates_to_session(): @pytest.mark.asyncio() async def test_wait_for_state_change_translates_to_session(): session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b": {}} + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - await node.a.b.wait_for_state_change(value=5, invert=True) + await node.a.b.c.d.wait_for_state_change(value=5, invert=True) - session_mock.wait_for_state_change.assert_called_once_with("/a/b", 5, invert=True) + session_mock.wait_for_state_change.assert_called_once_with( + "/a/b/c/d", + 5, + invert=True, + ) @pytest.mark.asyncio() async def test_wait_for_state_change_wildcard_translates_to_session(): session_mock = Mock(spec=Session) - session_mock.list_nodes_info.return_value = {"/a/b": {}, "/a/c": {}} - session_mock.list_nodes.return_value = [ - "/a/b", - "/a/c", - ] # required for wildcard resolution + session_mock.list_nodes_info = AsyncMock(return_value={"/a/b": {}, "/a/c": {}}) + session_mock.list_nodes = AsyncMock(return_value=["/a/b", "/a/c"]) node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root await node["*"].wait_for_state_change(value=5, invert=True) diff --git a/tests/test_dataserver.py b/tests/test_dataserver.py index 4d8e97b..5b0cc3d 100644 --- a/tests/test_dataserver.py +++ b/tests/test_dataserver.py @@ -7,13 +7,12 @@ ) from labone.dataserver import DataServer from labone.errors import LabOneError -from labone.mock import AutomaticSessionFunctionality, spawn_hpk_mock +from labone.mock import AutomaticLabOneServer @pytest.mark.asyncio() async def test_create_ok(): - mock_server = AutomaticSessionFunctionality({}) - session = await spawn_hpk_mock(mock_server) + session = await AutomaticLabOneServer({}).start_pipe() dataserver = await DataServer.create_from_session( session=session, host="host", @@ -24,8 +23,7 @@ async def test_create_ok(): @pytest.mark.asyncio() async def test_create_ok_new_session(): - mock_server = AutomaticSessionFunctionality({}) - session = await spawn_hpk_mock(mock_server) + session = await AutomaticLabOneServer({}).start_pipe() with patch.object( KernelSession, "create", @@ -52,8 +50,7 @@ async def test_create_raises(): ) @pytest.mark.asyncio() async def test_check_firmware_compatibility(status_nr): - mock_server = AutomaticSessionFunctionality({"/zi/devices": {}}) - session = await spawn_hpk_mock(mock_server) + session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() dataserver = await DataServer.create_from_session( session=session, host="host", @@ -66,8 +63,7 @@ async def test_check_firmware_compatibility(status_nr): @pytest.mark.asyncio() async def test_check_firmware_compatibility_single_instrument(): - mock_server = AutomaticSessionFunctionality({"/zi/devices": {}}) - session = await spawn_hpk_mock(mock_server) + session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() dataserver = await DataServer.create_from_session( session=session, host="host", @@ -103,8 +99,7 @@ async def test_check_firmware_compatibility_raises(id_and_codes, contained_in_er for id_, code in id_and_codes: val += '"' + id_ + '":{"STATUSFLAGS":' + str(code) + "}," val = val[:-1] + "}" - mock_server = AutomaticSessionFunctionality({"/zi/devices": {}}) - session = await spawn_hpk_mock(mock_server) + session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() dataserver = await DataServer.create_from_session( session=session, host="host", diff --git a/tests/test_instrument.py b/tests/test_instrument.py index b6e75ac..d53b1e8 100644 --- a/tests/test_instrument.py +++ b/tests/test_instrument.py @@ -4,21 +4,19 @@ from labone.errors import LabOneError from labone.instrument import Instrument -from labone.mock import AutomaticSessionFunctionality, spawn_hpk_mock +from labone.mock import AutomaticLabOneServer from tests.mock_server_for_testing import get_mocked_node @pytest.mark.asyncio() @patch("labone.instrument.KernelSession", autospec=True) async def test_connect_device(kernel_session): - session = await spawn_hpk_mock(AutomaticSessionFunctionality({})) + session = await AutomaticLabOneServer({}).start_pipe() kernel_session.create.return_value = session instrument = await Instrument.create("dev1234", host="testee") assert instrument.kernel_session == session assert instrument.serial == "dev1234" kernel_session.create.assert_awaited_once() - assert kernel_session.create.call_args.kwargs["kernel_info"].device_id == "dev1234" - assert kernel_session.create.call_args.kwargs["kernel_info"].interface == "" assert kernel_session.create.call_args.kwargs["server_info"].host == "testee" assert kernel_session.create.call_args.kwargs["server_info"].port == 8004 @@ -26,7 +24,7 @@ async def test_connect_device(kernel_session): @pytest.mark.asyncio() @patch("labone.instrument.KernelSession", autospec=True) async def test_connect_device_custom_default_args(kernel_session): - session = await spawn_hpk_mock(AutomaticSessionFunctionality({})) + session = await AutomaticLabOneServer({}).start_pipe() kernel_session.create.return_value = session instrument = await Instrument.create( "dev1234", @@ -37,8 +35,6 @@ async def test_connect_device_custom_default_args(kernel_session): assert instrument.kernel_session == session assert instrument.serial == "dev1234" kernel_session.create.assert_awaited_once() - assert kernel_session.create.call_args.kwargs["kernel_info"].device_id == "dev1234" - assert kernel_session.create.call_args.kwargs["kernel_info"].interface == "wifi" assert kernel_session.create.call_args.kwargs["server_info"].host == "testee" assert kernel_session.create.call_args.kwargs["server_info"].port == 8888