From 8ab59e053f0c7a075bb48a1cc6b9aba0941f4b6b Mon Sep 17 00:00:00 2001 From: Kai Jan Kriegel Date: Wed, 16 Mar 2022 05:59:18 +0100 Subject: [PATCH] updated for latest api --- Dockerfile | 7 ++++ config.toml => config/config.toml | 4 +- fabapi/connect.py | 59 ++++++++++++--------------- host.py | 10 +++++ main.py | 54 +++---------------------- reader.py | 66 +++++++++++++++++++++++++++++++ timer.py | 18 +++++++++ 7 files changed, 133 insertions(+), 85 deletions(-) create mode 100644 Dockerfile rename config.toml => config/config.toml (89%) create mode 100644 host.py create mode 100644 reader.py create mode 100644 timer.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..813ada5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.8-slim-buster +WORKDIR /app +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +COPY . . +VOLUME /app/config +CMD [ "python3", "main.py"] \ No newline at end of file diff --git a/config.toml b/config/config.toml similarity index 89% rename from config.toml rename to config/config.toml index d7a5e8f..20b2f4c 100644 --- a/config.toml +++ b/config/config.toml @@ -4,11 +4,11 @@ port = 1883 [bffhd] hostname = "192.168.178.31" -port = 59661 +port = 59662 [readers] [readers.abc] - id = "111" + id = "001" machine = "urn:fabaccess:resource:Testmachine" [readers.def] diff --git a/fabapi/connect.py b/fabapi/connect.py index 9df7233..af0a642 100644 --- a/fabapi/connect.py +++ b/fabapi/connect.py @@ -41,26 +41,23 @@ async def connect(host, port, user, pw): # Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode) client = capnp.TwoPartyClient() - cap = client.bootstrap().cast_as(connection_capnp.Bootstrap) # Assemble reader and writer tasks, run in the background coroutines = [myreader(client, reader), mywriter(client, writer)] asyncio.gather(*coroutines, return_exceptions=True) - auth = cap.authenticationSystem().authenticationSystem - req = auth.start_request() - req.request.mechanism = "PLAIN" - req.request.initialResponse.initial = "\0" + user + "\0" + pw - rep_prom = req.send() - response = await rep_prom.a_wait() - if response.response.outcome.result == "successful": - logging.info("Auth completed successfully") - return cap + boot = client.bootstrap().cast_as(connection_capnp.Bootstrap) + auth = await boot.createSession("PLAIN").a_wait() + p = "\0" + user + "\0" + pw + response = await auth.authentication.step(p).a_wait() + if response.which() == 'successful': + return response.successful.session else: - logging.info("Auth failed") + print("Authentication failed!") return None + async def connect_with_fabfire_initial(host, port, uid): # Setup SSL context ctx = ssl.create_default_context( @@ -81,38 +78,32 @@ async def connect_with_fabfire_initial(host, port, uid): # Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode) client = capnp.TwoPartyClient() - cap = client.bootstrap().cast_as(connection_capnp.Bootstrap) + # Assemble reader and writer tasks, run in the background coroutines = [myreader(client, reader), mywriter(client, writer)] asyncio.gather(*coroutines, return_exceptions=True) - auth = cap.authenticationSystem().authenticationSystem - req = auth.start_request() - req.request.mechanism = "X-FABFIRE" - req.request.initialResponse.initial = uid - rep_prom = req.send() - response = await rep_prom.a_wait() - logging.debug(f"got response type: {response.response.which()}") - if response.response.which() == "challence": - logging.debug(f"challenge: {response.response.challence}") - return auth, response.response.challence, cap + boot = client.bootstrap().cast_as(connection_capnp.Bootstrap) + auth = await boot.createSession("X-FABFIRE").a_wait() + response = await auth.authentication.step(uid).a_wait() + logging.debug(f"got response type: {response.which()}") + if response.which() == "challenge": + logging.debug(f"challenge: {response.challenge}") + return auth, response.challenge else: - logging.error(f"Auth failed: {response.response.outcome.result}, additional info: {response.response.outcome.helpText}") + logging.error(f"Auth failed: {response.failed.code}, additional info: {response.failed.additionalData}") return None async def connect_with_fabfire_step(auth, msg): - req = auth.step_request() - req.response = msg - rep_prom = req.send() - response = await rep_prom.a_wait() - if response.response.which() == "challence": - logging.debug(f"challenge: {response.response.challence}") - return response.response.challence, False # auth cap, challenge, not done - elif response.response.outcome.result == "successful": - logging.info(f"Auth completed successfully! Got additional Data: {response.response.outcome.additionalData.additional}") - return response.response.outcome.additionalData.additional, True # dont care, message, we are done + response = await auth.authentication.step(msg).a_wait() + if response.which() == "challenge": + logging.debug(f"challenge: {response.challenge}") + return response.challenge, None # auth cap, challenge, not done + elif response.which() == "successful": + logging.info(f"Auth completed successfully! Got additional Data: {response.successful.additionalData}") + return response.successful.additionalData, response.successful.session # dont care, message, we are done else: - logging.error(f"Auth failed: {response.response.outcome.result}, additional info: {response.response.outcome.helpText}") + logging.error(f"Auth failed: {response.failed.code}, additional info: {response.failed.additionalData}") return None diff --git a/host.py b/host.py new file mode 100644 index 0000000..85b42ba --- /dev/null +++ b/host.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=True) +class Host: + hostname: str + port: int + username: Optional[str] = None + password: Optional[str] = None \ No newline at end of file diff --git a/main.py b/main.py index 0f7c7cf..4b7edd3 100644 --- a/main.py +++ b/main.py @@ -4,57 +4,11 @@ import asyncio import logging import os from contextlib import AsyncExitStack, asynccontextmanager -from dataclasses import dataclass -from typing import Optional -import asyncio_mqtt from asyncio_mqtt import Client, MqttError -import fabapi import toml - -@dataclass(frozen=True) -class Host: - hostname: str - port: int - username: Optional[str] = None - password: Optional[str] = None - - -class Reader: - def __init__(self, reader_id: str, machine_urn: str, bffhd_address: Host): - self.reader_id = reader_id - self.machine_urn = machine_urn - self.bffhd_address = bffhd_address - self.is_authenticated = False - self.auth_cap = None - self.bootstrap_cap = None - - async def handle_messages(self, messages, client: asyncio_mqtt.Client): - async for message in messages: - response_for_reader = None - if not self.auth_cap: - self.auth_cap, response_for_reader, self.bootstrap_cap = await fabapi.connect_with_fabfire_initial( - self.bffhd_address.hostname, self.bffhd_address.port, message.payload) - elif not self.is_authenticated: - response_for_reader, self.is_authenticated = await fabapi.connect_with_fabfire_step(self.auth_cap, - message.payload) - if self.is_authenticated: - await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2, - retain=False) - ms = await self.bootstrap_cap.machineSystem().a_wait() - info = await ms.machineSystem.info().a_wait() - ma = await info.info.getMachineURN(f"{self.machine_urn}").a_wait() - if ma.machine.state == "inUse": - await ma.machine.inuse.giveBack().a_wait() - else: - await ma.machine.use.use().a_wait() - self.is_authenticated = False - self.bootstrap_cap = None - self.auth_cap = None - response_for_reader = None - - if response_for_reader: - await client.publish(f"/cmnd/reader/{self.reader_id}", payload=response_for_reader, qos=2, retain=False) +from host import Host +from reader import Reader async def run_adapter(mqtt_host: Host, readers: list[Reader]): @@ -77,6 +31,8 @@ async def run_adapter(mqtt_host: Host, readers: list[Reader]): messages = await stack.enter_async_context(manager) task = asyncio.create_task(reader.handle_messages(messages, client)) tasks.add(task) + await client.publish(f"/cmnd/reader/{reader.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2, + retain=False) # reset readers logging.info(f"Registered handler for reader {reader.reader_id}") # Messages that doesn't match a filter will get logged here @@ -117,7 +73,7 @@ async def main(): # if the connection is lost. reconnect_interval = 3 # [seconds] - config = toml.load("config.toml") + config = toml.load("config/config.toml") mqtt_host = Host(config["mqtt"]["hostname"], config["mqtt"]["port"], config["mqtt"].setdefault("username", None), config["mqtt"].setdefault("password", None)) diff --git a/reader.py b/reader.py new file mode 100644 index 0000000..08c72ff --- /dev/null +++ b/reader.py @@ -0,0 +1,66 @@ +import asyncio +import logging + +import asyncio_mqtt + +import fabapi +from host import Host +from timer import Timer + + +class Reader: + def __init__(self, reader_id: str, machine_urn: str, bffhd_address: Host): + self.reader_id = reader_id + self.machine_urn = machine_urn + self.bffhd_address = bffhd_address + self.auth_cap = None + self.session = None + self.timeout_timer = None + + async def handle_messages(self, messages, client: asyncio_mqtt.Client): + try: + async for message in messages: + response_for_reader = None + if not self.auth_cap: + self.timeout_timer = Timer(10, lambda: self.handle_timeout(client)) + self.auth_cap, response_for_reader = await fabapi.connect_with_fabfire_initial( + self.bffhd_address.hostname, self.bffhd_address.port, message.payload) + elif not self.session: + response_for_reader, self.session = await fabapi.connect_with_fabfire_step(self.auth_cap, + message.payload) + if self.session: + self.timeout_timer.cancel() + await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2, + retain=False) + info = await self.session.machineSystem.info().a_wait() + ma = await info.info.getMachineURN(f"{self.machine_urn}").a_wait() + if ma.state == "inUse": + await ma.inuse.giveBack().a_wait() + await asyncio.sleep(2) + await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"message","MssgID":0,"AddnTxt":""}', qos=2, + retain=False) + else: + await ma.use.use().a_wait() + await asyncio.sleep(2) + await client.publish(f"/cmnd/reader/{self.reader_id}", + payload='{"Cmd":"message","MssgID":3,"AddnTxt":""}', qos=2, + retain=False) + self.session = None + self.auth_cap = None + response_for_reader = None + + if response_for_reader: + await client.publish(f"/cmnd/reader/{self.reader_id}", payload=response_for_reader, qos=2, retain=False) + except Exception as e: + logging.error(f"Caught exception {e}") + await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2, + retain=False) + self.session = None + self.auth_cap = None + + async def handle_timeout(self, client): + await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2, + retain=False) + logging.critical(f"authentication timed out on reader {self.reader_id}") + self.auth_cap = None + self.session = None \ No newline at end of file diff --git a/timer.py b/timer.py new file mode 100644 index 0000000..1e523dc --- /dev/null +++ b/timer.py @@ -0,0 +1,18 @@ +import asyncio +from typing import Callable + +import asyncio_mqtt + + +class Timer: + def __init__(self, timeout: int, callback: Callable): + self._timeout = timeout + self._callback = callback + self._task = asyncio.ensure_future(self._job()) + + async def _job(self): + await asyncio.sleep(self._timeout) + await self._callback() + + def cancel(self): + self._task.cancel()