From 937bb12727f42dbef7dd7e3702492c12ca1d1009 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 27 Sep 2021 16:18:53 +0300 Subject: [PATCH 01/78] Create EnvironmentService class. Move exceptions file from auth to environments module --- auth/auth.py | 14 ++------------ environments/environmental_services.py | 15 +++++++++++++++ environments/exceptions.py | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 environments/environmental_services.py create mode 100644 environments/exceptions.py diff --git a/auth/auth.py b/auth/auth.py index 93576f02..09426157 100644 --- a/auth/auth.py +++ b/auth/auth.py @@ -3,27 +3,17 @@ from auth.exceptions import InvalidEnvironmentSetup from auth.parameters import AuthUrlParameter from auth.response import AuthResponse +from environments.environmental_services import EnvironmentalService from environments.environments import ProductionEnvironment, QAEnvironment -class Authorization: +class Authorization(EnvironmentalService): SIGNATURE_KEY = "signature" STATE_KEY = "state" TOKEN_KEY = "token" ERROR_KEY = "error" - def __init__(self, env): - self._set_env(env) - - def _set_env(self, env) -> None: - if env == "QA": - self._environment = QAEnvironment() - elif env == "Production": - self._environment = ProductionEnvironment() - else: - raise InvalidEnvironmentSetup(env=env) - def get_auth_request_url(self, parameters: AuthUrlParameter) -> str: auth_parameters = parameters.get_parameters() return self._environment.get_secured_onboarding_authorization_url(**auth_parameters) diff --git a/environments/environmental_services.py b/environments/environmental_services.py new file mode 100644 index 00000000..5d9da90f --- /dev/null +++ b/environments/environmental_services.py @@ -0,0 +1,15 @@ +from environments.exceptions import InvalidEnvironmentSetup +from environments.environments import ProductionEnvironment, QAEnvironment + + +class EnvironmentalService: + def __init__(self, env): + self._set_env(env) + + def _set_env(self, env) -> None: + if env == "QA": + self._environment = QAEnvironment() + elif env == "Production": + self._environment = ProductionEnvironment() + else: + raise InvalidEnvironmentSetup(env=env) \ No newline at end of file diff --git a/environments/exceptions.py b/environments/exceptions.py new file mode 100644 index 00000000..2ebb7c2a --- /dev/null +++ b/environments/exceptions.py @@ -0,0 +1,16 @@ + + +class InvalidEnvironmentSetup(Exception): + + def __init__(self, message=None, env=None): + if not message: + message = "Invalid value of env parameter. [QA] or [Production] values are allowed" + self.message = message + self.env = env + + +class BadAuthResponse(Exception): + def __init__(self, message=None): + if not message: + message = "Bad Response. Response could is not verified." + self.message = message From 95d608324531389184e5ef473a5b644a6a94f226 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 27 Sep 2021 16:31:04 +0300 Subject: [PATCH 02/78] Fix exceptions in auth --- auth/auth.py | 2 -- auth/exceptions.py | 10 ---------- 2 files changed, 12 deletions(-) diff --git a/auth/auth.py b/auth/auth.py index 09426157..6d23e662 100644 --- a/auth/auth.py +++ b/auth/auth.py @@ -1,10 +1,8 @@ from urllib.parse import urlparse, parse_qs -from auth.exceptions import InvalidEnvironmentSetup from auth.parameters import AuthUrlParameter from auth.response import AuthResponse from environments.environmental_services import EnvironmentalService -from environments.environments import ProductionEnvironment, QAEnvironment class Authorization(EnvironmentalService): diff --git a/auth/exceptions.py b/auth/exceptions.py index 2ebb7c2a..106f9c20 100644 --- a/auth/exceptions.py +++ b/auth/exceptions.py @@ -1,14 +1,4 @@ - -class InvalidEnvironmentSetup(Exception): - - def __init__(self, message=None, env=None): - if not message: - message = "Invalid value of env parameter. [QA] or [Production] values are allowed" - self.message = message - self.env = env - - class BadAuthResponse(Exception): def __init__(self, message=None): if not message: From cb6191b67760cead432a65313a2977d7fa6d1210 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 27 Sep 2021 16:31:36 +0300 Subject: [PATCH 03/78] Change message of InvalidEnvironmentSetup --- environments/exceptions.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/environments/exceptions.py b/environments/exceptions.py index 2ebb7c2a..6430b632 100644 --- a/environments/exceptions.py +++ b/environments/exceptions.py @@ -4,13 +4,8 @@ class InvalidEnvironmentSetup(Exception): def __init__(self, message=None, env=None): if not message: - message = "Invalid value of env parameter. [QA] or [Production] values are allowed" - self.message = message - self.env = env - + message = "Invalid value of env parameter. [QA] or [Production] values are allowed. " \ + "Please use environments.enums.Enviroments enum for configure environment properly" -class BadAuthResponse(Exception): - def __init__(self, message=None): - if not message: - message = "Bad Response. Response could is not verified." self.message = message + self.env = env From c0569f043384c694715c38cf8364c69ea907e850 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 11:01:08 +0300 Subject: [PATCH 04/78] Implement Onboarding --- constants/__init__.py | 0 constants/keys.py | 0 constants/media_types.py | 5 ++ environments/enums.py | 0 environments/environmental_services.py | 2 +- onboarding/__init__.py | 0 onboarding/enums.py | 11 +++ onboarding/exceptions.py | 15 ++++ onboarding/headers.py | 48 ++++++++++++ onboarding/onboarding.py | 70 +++++++++++++++++ onboarding/parameters.py | 95 +++++++++++++++++++++++ onboarding/request.py | 53 +++++++++++++ onboarding/request_body.py | 101 +++++++++++++++++++++++++ onboarding/response.py | 29 +++++++ onboarding/signature.py | 29 +++++++ revoking/__init__.py | 0 revoking/headers.py | 23 ++++++ revoking/parameters.py | 34 +++++++++ revoking/request.py | 36 +++++++++ revoking/request_body.py | 30 ++++++++ revoking/response.py | 7 ++ revoking/revoking.py | 30 ++++++++ 22 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 constants/__init__.py create mode 100644 constants/keys.py create mode 100644 constants/media_types.py create mode 100644 environments/enums.py create mode 100644 onboarding/__init__.py create mode 100644 onboarding/enums.py create mode 100644 onboarding/exceptions.py create mode 100644 onboarding/headers.py create mode 100644 onboarding/onboarding.py create mode 100644 onboarding/parameters.py create mode 100644 onboarding/request.py create mode 100644 onboarding/request_body.py create mode 100644 onboarding/response.py create mode 100644 onboarding/signature.py create mode 100644 revoking/__init__.py create mode 100644 revoking/headers.py create mode 100644 revoking/parameters.py create mode 100644 revoking/request.py create mode 100644 revoking/request_body.py create mode 100644 revoking/response.py create mode 100644 revoking/revoking.py diff --git a/constants/__init__.py b/constants/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/constants/keys.py b/constants/keys.py new file mode 100644 index 00000000..e69de29b diff --git a/constants/media_types.py b/constants/media_types.py new file mode 100644 index 00000000..a77638f6 --- /dev/null +++ b/constants/media_types.py @@ -0,0 +1,5 @@ +from auth.enums import BaseEnum + + +class ContentTypes(BaseEnum): + APPLICATION_JSON = "application/json" diff --git a/environments/enums.py b/environments/enums.py new file mode 100644 index 00000000..e69de29b diff --git a/environments/environmental_services.py b/environments/environmental_services.py index 5d9da90f..f980cd2f 100644 --- a/environments/environmental_services.py +++ b/environments/environmental_services.py @@ -12,4 +12,4 @@ def _set_env(self, env) -> None: elif env == "Production": self._environment = ProductionEnvironment() else: - raise InvalidEnvironmentSetup(env=env) \ No newline at end of file + raise InvalidEnvironmentSetup(env=env) diff --git a/onboarding/__init__.py b/onboarding/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/onboarding/enums.py b/onboarding/enums.py new file mode 100644 index 00000000..cc7dbd31 --- /dev/null +++ b/onboarding/enums.py @@ -0,0 +1,11 @@ +from auth.enums import BaseEnum + + +class CertificateTypes(BaseEnum): + PEM = "PEM" + P12 = "P12" + + +class GateWays(BaseEnum): + MQTT = "2" + REST = "3" diff --git a/onboarding/exceptions.py b/onboarding/exceptions.py new file mode 100644 index 00000000..96686763 --- /dev/null +++ b/onboarding/exceptions.py @@ -0,0 +1,15 @@ +class AgriRouuterBaseException(Exception): + _message = ... + + def __init__(self, message=None): + if not message: + message = self._message + self.message = message + + +class WrongCertificationType(AgriRouuterBaseException): + _message = "Wrong Certification type. Use onboarding.enums.CertificationTypes values instead." + + +class WrongGateWay(AgriRouuterBaseException): + _message = "Wrong Gate Way Id. Use onboarding.enums.GateWays values instead." diff --git a/onboarding/headers.py b/onboarding/headers.py new file mode 100644 index 00000000..32b17a56 --- /dev/null +++ b/onboarding/headers.py @@ -0,0 +1,48 @@ +from abc import ABC, abstractmethod + +from constants.media_types import ContentTypes + + +class BaseOnboardingHeader(ABC): + @abstractmethod + def __init__(self, *args, **kwargs): + self._set_params(*args, **kwargs) + + @abstractmethod + def get_header(self) -> dict: + ... + + @abstractmethod + def _set_params(self, *args, **kwargs): + ... + + +class SoftwareOnboardingHeader(BaseOnboardingHeader): + def __init__(self, + reg_code, + application_id, + signature=None, + content_type=ContentTypes.APPLICATION_JSON.value + ): + + self._set_params(reg_code, application_id, signature, content_type) + + def get_header(self) -> dict: + return self.params + + def sign(self, signature): + self.params["X-Agrirouter-Signature"] = signature + + def _set_params(self, reg_code: str, application_id: str, signature: str, content_type: str): + header = dict() + header["Authorization"] = f"Bearer {reg_code}" + header["Content-Type"] = content_type + header["X-Agrirouter-ApplicationId"] = application_id + if signature: + header["X-Agrirouter-Signature"] = signature + + self.params = header + + +class CUOnboardingHeader(BaseOnboardingHeader): + pass diff --git a/onboarding/onboarding.py b/onboarding/onboarding.py new file mode 100644 index 00000000..28071c54 --- /dev/null +++ b/onboarding/onboarding.py @@ -0,0 +1,70 @@ +import requests + +from environments.environmental_services import EnvironmentalService +from onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader +from onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, RevokingParameter, \ + CUOnboardingParameter +from onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest +from onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody +from onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, RevokingResponse, \ + CUOnboardingResponse + + +class SoftwareOnboarding(EnvironmentalService): + + def _create_request(self, params: BaseOnboardingParameter, url: str) -> SoftwareOnboardingRequest: + body_params = params.get_body_params() + request_body = SoftwareOnboardingBody(**body_params) + + header_params = params.get_header_params() + header_params["request_body"] = request_body.json(new_lines=False) + request_header = SoftwareOnboardingHeader(**header_params) + + return SoftwareOnboardingRequest(header=request_header, body=request_body, url=url) + + def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: + request = self._create_request(params, url) + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + + def verify(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResponse: + url = self._environment.get_verify_onboard_request_url() + http_response = self._perform_request(params=params, url=url) + + return SoftwareOnboardingResponse(http_response) + + def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResponse: + url = self._environment.get_secured_onboard_url() + http_response = self._perform_request(params=params, url=url) + + return SoftwareOnboardingResponse(http_response) + + +class CUOnboarding(EnvironmentalService): + + def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardingRequest: + body_params = params.get_body_params() + request_body = CUOnboardingBody(**body_params) + + header_params = params.get_header_params() + header_params["request_body"] = request_body.json(new_lines=False) + request_header = CUOnboardingHeader(**header_params) + + return CUOnboardingRequest(header=request_header, body=request_body, url=url) + + def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: + request = self._create_request(params, url) + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + + def onboard(self, params: CUOnboardingParameter) -> CUOnboardingResponse: + url = self._environment.get_onboard_url() + http_response = self._perform_request(params=params, url=url) + + return CUOnboardingResponse(http_response) diff --git a/onboarding/parameters.py b/onboarding/parameters.py new file mode 100644 index 00000000..b8a4f879 --- /dev/null +++ b/onboarding/parameters.py @@ -0,0 +1,95 @@ +from abc import ABC, abstractmethod + +from constants.media_types import ContentTypes +from onboarding.enums import CertificateTypes + + +class BaseOnboardingParameter(ABC): + @abstractmethod + def __init__(self, *args, **kwargs): + ... + + @abstractmethod + def get_header_params(self, *args, **kwargs): + ... + + @abstractmethod + def get_body_params(self, *args, **kwargs): + ... + + +class SoftwareOnboardingParameter(BaseOnboardingParameter): + def __init__(self, + id_, + application_id, + certification_version_id, + gateway_id, + utc_timestamp, + time_zone, + reg_code, + content_type=ContentTypes.APPLICATION_JSON.value, + certificate_type=CertificateTypes.P12.value, + ): + + self.id_ = id_ + self.application_id = application_id + self.content_type = content_type + self.certification_version_id = certification_version_id + self.gateway_id = gateway_id + self.certificate_type = certificate_type + self.utc_timestamp = utc_timestamp + self.time_zone = time_zone + self.reg_code = reg_code + + def get_header_params(self): + return { + "content_type": self.content_type, + "reg_code": self.reg_code, + "application_id": self.application_id, + } + + def get_body_params(self): + return { + "id": self.id_, + "application_id": self.application_id, + "certification_version_id": self.certification_version_id, + "gateway_id": self.gateway_id, + "certificate_type": self.certificate_type, + "utc_timestamp": self.utc_timestamp, + "time_zone": self.time_zone, + } + + +class CUOnboardingParameter(BaseOnboardingParameter): + def __init__(self, + id_, + application_id, + certification_version_id, + gateway_id, + reg_code, + content_type=ContentTypes.APPLICATION_JSON.value, + certificate_type=CertificateTypes.P12.value, + ): + + self.id_ = id_ + self.application_id = application_id + self.content_type = content_type + self.certification_version_id = certification_version_id + self.gateway_id = gateway_id + self.certificate_type = certificate_type + self.reg_code = reg_code + + def get_header_params(self): + return { + "content_type": self.content_type, + "reg_code": self.reg_code, + } + + def get_body_params(self): + return { + "id": self.id_, + "application_id": self.application_id, + "certification_version_id": self.certification_version_id, + "gateway_id": self.gateway_id, + "certificate_type": self.certificate_type, + } diff --git a/onboarding/request.py b/onboarding/request.py new file mode 100644 index 00000000..5dda15b5 --- /dev/null +++ b/onboarding/request.py @@ -0,0 +1,53 @@ +from onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader +from onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody + + +class BaseOnboardingRequest: + def __init__(self, header: BaseOnboardingHeader, body: BaseOnboardingBody, url: str): + self.header = header + self.body = body + self.url = url + + def get_url(self): + return self.url + + def get_data(self): + return self.body.get_parameters() + + def get_header(self): + return self.header.get_header() + + def sign(self): + """ + TODO: add here create_signature + :return: + """ + signature = ... # create signature + self.header.sign(signature) + pass + + @property + def is_signed(self): + header_has_signature = self.get_header().get("X-Agrirouter-Signature", None) + if header_has_signature: + return True + return False + + @property + def is_valid(self): + if not self.is_signed: + return False + + +class SoftwareOnboardingRequest(BaseOnboardingRequest): + """ + Request must be used to onboard Farming Software or Telemetry Platform + """ + pass + + +class CUOnboardingRequest(BaseOnboardingRequest): + """ + Request must be used to onboard CUs + """ + pass diff --git a/onboarding/request_body.py b/onboarding/request_body.py new file mode 100644 index 00000000..b4f922b6 --- /dev/null +++ b/onboarding/request_body.py @@ -0,0 +1,101 @@ +import json +from abc import ABC, abstractmethod + +from onboarding.enums import CertificateTypes, GateWays +from onboarding.exceptions import WrongCertificationType, WrongGateWay + + +class BaseOnboardingBody(ABC): + @abstractmethod + def __init__(self, *args, **kwargs): + ... + + @abstractmethod + def get_parameters(self, *args, **kwargs) -> dict: + ... + + @abstractmethod + def _set_params(self, *args, **kwargs): + ... + + +class SoftwareOnboardingBody(BaseOnboardingBody): + def __init__(self, + id_, + application_id, + certification_version_id, + gateway_id, + certificate_type, + utc_timestamp, + time_zone + ): + + self._validate_certificate_type(certificate_type) + self._validate_gateway_id(gateway_id) + + self._set_params( + id_, + application_id, + certification_version_id, + gateway_id, + certificate_type, + utc_timestamp, + time_zone + ) + + def get_parameters(self) -> dict: + return self.params + + def _set_params(self, + id_, + application_id, + certification_version_id, + gateway_id, + certificate_type, + utc_timestamp, + time_zone + ): + + self.params = { + "id": id_, + "applicationId": application_id, + "certificationVersionId": certification_version_id, + "gatewayId": gateway_id, + "certificateType": certificate_type, + "UTCTimestamp": utc_timestamp, + "timeZone": time_zone, + } + + def json(self, new_lines: bool = True) -> str: + result = json.dumps(self.get_parameters(), indent="") + if not new_lines: + return result.replace("\n", "") + return result + + @staticmethod + def _validate_certificate_type(certificate_type: str) -> None: + if certificate_type not in CertificateTypes.values_list(): + raise WrongCertificationType + + @staticmethod + def _validate_gateway_id(gateway_id: str) -> None: + if gateway_id not in GateWays.values_list(): + raise WrongGateWay + + +class CUOnboardingBody(BaseOnboardingBody): + + def __init__(self, *args, **kwargs): + ... + + def get_parameters(self, *args, **kwargs) -> dict: + ... + + def _set_params(self, *args, **kwargs): + ... + + def json(self, new_lines: bool = True) -> str: + result = json.dumps(self.get_parameters(), indent="") + if not new_lines: + return result.replace("\n", "") + return result diff --git a/onboarding/response.py b/onboarding/response.py new file mode 100644 index 00000000..e1d1d420 --- /dev/null +++ b/onboarding/response.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod + + +class BaseOnboardingResonse(ABC): + @abstractmethod + def __init__(self, http_response): + self.response = http_response + + +class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): + """ + Response from verify request used for Farming Software or Telemetry Platform before onboarding + """ + pass + + +class SoftwareOnboardingResponse(BaseOnboardingResonse): + """ + Response from onboarding request used for Farming Software or Telemetry Platform + """ + pass + + +class CUOnboardingResponse(BaseOnboardingResonse): + """ + Response from onboarding request used for CUs + """ + pass + diff --git a/onboarding/signature.py b/onboarding/signature.py new file mode 100644 index 00000000..fa68befc --- /dev/null +++ b/onboarding/signature.py @@ -0,0 +1,29 @@ +from cryptography.hazmat.primitives.serialization import load_pem_public_key, load_pem_private_key +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding + +from pprint import pprint + +SIGNATURE_ALGORITHM = "SHA256withRSA" + + +def create_signature(request_body: str, private_key: str) -> bytes: + private_key_bytes = bytearray(private_key.encode('utf-8')) + private_key_data = load_pem_private_key(private_key_bytes, None) + signature = private_key_data.sign( + request_body.encode('utf-8'), + padding.PKCS1v15(), + hashes.SHA256() + ) + return signature + + +def verify_signature(request_body: str, signature: bytearray, public_key: str) -> None: + public_key_bytes = bytearray(public_key.encode('utf-8')) + public_key_data = load_pem_public_key(public_key_bytes) + public_key_data.verify( + signature, + request_body.encode('utf-8'), + padding.PKCS1v15(), + hashes.SHA256() + ) diff --git a/revoking/__init__.py b/revoking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/revoking/headers.py b/revoking/headers.py new file mode 100644 index 00000000..c4d1b7a8 --- /dev/null +++ b/revoking/headers.py @@ -0,0 +1,23 @@ +from constants.media_types import ContentTypes + + +class RevokingHeader: + def __init__(self, + application_id, + signature=None, + content_type=ContentTypes.APPLICATION_JSON.value + ): + + self._set_params(application_id, signature, content_type) + + def get_header(self) -> dict: + return self.params + + def _set_params(self, application_id: str, signature: str, content_type: str): + header = dict() + header["Content-Type"] = content_type + header["X-Agrirouter-ApplicationId"] = application_id + if signature: + header["X-Agrirouter-Signature"] = signature + + self.params = header \ No newline at end of file diff --git a/revoking/parameters.py b/revoking/parameters.py new file mode 100644 index 00000000..ef65d556 --- /dev/null +++ b/revoking/parameters.py @@ -0,0 +1,34 @@ +from constants.media_types import ContentTypes + + +class RevokingParameter: + + def __init__(self, + application_id, + account_id, + endpoint_ids, + utc_timestamp, + timestamp, + content_type=ContentTypes.APPLICATION_JSON.value + ): + + self.application_id = application_id + self.content_type = content_type + self.account_id = account_id + self.endpoint_ids = endpoint_ids + self.utc_timestamp = utc_timestamp + self.timestamp = timestamp + + def get_header_params(self): + return { + "application_id": self.application_id, + "content_type": self.content_type, + } + + def get_body_params(self): + return { + "account_id": self.account_id, + "endpoint_ids": self.endpoint_ids, + "utc_timestamp": self.utc_timestamp, + "timestamp": self.timestamp, + } diff --git a/revoking/request.py b/revoking/request.py new file mode 100644 index 00000000..aa7620cc --- /dev/null +++ b/revoking/request.py @@ -0,0 +1,36 @@ +class BaseOnboardingRequest: + def __init__(self, header: RevokingHeader, body: RevokingBody, url: str): + self.header = header + self.body = body + self.url = url + + def get_url(self): + return self.url + + def get_data(self): + return self.body.get_parameters() + + def get_header(self): + return self.header.get_header() + + def sign(self): + """ + TODO: add here create_signature + :return: + """ + signature = ... # create signature + self.header.sign(signature) + + @property + def is_signed(self) -> bool: + header_has_signature = self.get_header().get("X-Agrirouter-Signature", None) + if header_has_signature: + return True + return False + + @property + def is_valid(self) -> bool: + if not self.is_signed: + return False + signature = self.get_header().get("X-Agrirouter-Signature") + # return validate_signature(signature) diff --git a/revoking/request_body.py b/revoking/request_body.py new file mode 100644 index 00000000..e23c2750 --- /dev/null +++ b/revoking/request_body.py @@ -0,0 +1,30 @@ +class RevokingBody: + def __init__(self, + account_id, + endpoint_ids, + utc_timestamp, + time_zone): + + self._set_params( + account_id, + endpoint_ids, + utc_timestamp, + time_zone + ) + + def get_parameters(self) -> dict: + return self.params + + def _set_params(self, + account_id, + endpoint_ids, + utc_timestamp, + time_zone + ) -> None: + + self.params = { + "account_id": account_id, + "endpoint_ids": endpoint_ids, + "UTCTimestamp": utc_timestamp, + "timeZone": time_zone, + } diff --git a/revoking/response.py b/revoking/response.py new file mode 100644 index 00000000..60d0c4bf --- /dev/null +++ b/revoking/response.py @@ -0,0 +1,7 @@ +class RevokingResonse: + """ + Response from revoking request + """ + + def __init__(self, http_response): + self.response = http_response diff --git a/revoking/revoking.py b/revoking/revoking.py new file mode 100644 index 00000000..9d1dc96b --- /dev/null +++ b/revoking/revoking.py @@ -0,0 +1,30 @@ +import requests + +from environments.environmental_services import EnvironmentalService + + +class Revoking(EnvironmentalService): + + def _create_request(self, params: RevokingParameter, url: str) -> RevokingRequest: + body_params = params.get_body_params() + request_body = RevokingBody(**body_params) + + header_params = params.get_header_params() + header_params["request_body"] = request_body.json(new_lines=False) + request_header = RevokingHeader(**header_params) + + return RevokingRequest(header=request_header, body=request_body, url=url) + + def _perform_request(self, params: RevokingParameter, url: str) -> requests.Response: + request = self._create_request(params, url) + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + + def revoke(self, params: RevokingParameter) -> RevokingResponse: + url = self._environment.get_revoke_url() + http_response = self._perform_request(params=params, url=url) + + return RevokingResponse(http_response) \ No newline at end of file From e039b7598930561e79371f993e0fc37a44c172a5 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 12:53:43 +0300 Subject: [PATCH 05/78] Fix import bug in onboarding/onboarding.py --- onboarding/onboarding.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/onboarding/onboarding.py b/onboarding/onboarding.py index 28071c54..24e93d49 100644 --- a/onboarding/onboarding.py +++ b/onboarding/onboarding.py @@ -2,12 +2,10 @@ from environments.environmental_services import EnvironmentalService from onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader -from onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, RevokingParameter, \ - CUOnboardingParameter +from onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter from onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest from onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, RevokingResponse, \ - CUOnboardingResponse +from onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, CUOnboardingResponse class SoftwareOnboarding(EnvironmentalService): From eaaacc4d559486366dbab05976e2cfaa3b6c1505 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 13:14:17 +0300 Subject: [PATCH 06/78] Fix bug in OnboardingParameter.get_body_params() method --- onboarding/parameters.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/onboarding/parameters.py b/onboarding/parameters.py index b8a4f879..12f3373a 100644 --- a/onboarding/parameters.py +++ b/onboarding/parameters.py @@ -35,10 +35,10 @@ def __init__(self, self.application_id = application_id self.content_type = content_type self.certification_version_id = certification_version_id - self.gateway_id = gateway_id + self.gateway_id = str(gateway_id) self.certificate_type = certificate_type - self.utc_timestamp = utc_timestamp - self.time_zone = time_zone + self.utc_timestamp = str(utc_timestamp) + self.time_zone = str(time_zone) self.reg_code = reg_code def get_header_params(self): @@ -50,7 +50,7 @@ def get_header_params(self): def get_body_params(self): return { - "id": self.id_, + "id_": self.id_, "application_id": self.application_id, "certification_version_id": self.certification_version_id, "gateway_id": self.gateway_id, @@ -87,7 +87,7 @@ def get_header_params(self): def get_body_params(self): return { - "id": self.id_, + "id_": self.id_, "application_id": self.application_id, "certification_version_id": self.certification_version_id, "gateway_id": self.gateway_id, From db3f2e0e2dc2b82134f43d7751d56312c730dd97 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 13:15:26 +0300 Subject: [PATCH 07/78] Fix OnboardingParameter.create_request(), OnboardingParameter.create_request() --- onboarding/onboarding.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onboarding/onboarding.py b/onboarding/onboarding.py index 24e93d49..20365d07 100644 --- a/onboarding/onboarding.py +++ b/onboarding/onboarding.py @@ -15,7 +15,6 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software request_body = SoftwareOnboardingBody(**body_params) header_params = params.get_header_params() - header_params["request_body"] = request_body.json(new_lines=False) request_header = SoftwareOnboardingHeader(**header_params) return SoftwareOnboardingRequest(header=request_header, body=request_body, url=url) @@ -48,7 +47,6 @@ def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardi request_body = CUOnboardingBody(**body_params) header_params = params.get_header_params() - header_params["request_body"] = request_body.json(new_lines=False) request_header = CUOnboardingHeader(**header_params) return CUOnboardingRequest(header=request_header, body=request_body, url=url) From ad16fc5bd972c77546d7783a3c401eab14438c61 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 13:40:14 +0300 Subject: [PATCH 08/78] Fix revoking --- revoking/headers.py | 3 +++ revoking/request.py | 6 +++++- revoking/response.py | 2 +- revoking/revoking.py | 6 +++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/revoking/headers.py b/revoking/headers.py index c4d1b7a8..08dc878e 100644 --- a/revoking/headers.py +++ b/revoking/headers.py @@ -13,6 +13,9 @@ def __init__(self, def get_header(self) -> dict: return self.params + def sign(self, signatute): + self.params["X-Agrirouter-Signature"] = signatute + def _set_params(self, application_id: str, signature: str, content_type: str): header = dict() header["Content-Type"] = content_type diff --git a/revoking/request.py b/revoking/request.py index aa7620cc..a08898c4 100644 --- a/revoking/request.py +++ b/revoking/request.py @@ -1,4 +1,8 @@ -class BaseOnboardingRequest: +from revoking.headers import RevokingHeader +from revoking.request_body import RevokingBody + + +class RevokingRequest: def __init__(self, header: RevokingHeader, body: RevokingBody, url: str): self.header = header self.body = body diff --git a/revoking/response.py b/revoking/response.py index 60d0c4bf..249cad11 100644 --- a/revoking/response.py +++ b/revoking/response.py @@ -1,4 +1,4 @@ -class RevokingResonse: +class RevokingResponse: """ Response from revoking request """ diff --git a/revoking/revoking.py b/revoking/revoking.py index 9d1dc96b..8a8a4f7b 100644 --- a/revoking/revoking.py +++ b/revoking/revoking.py @@ -1,6 +1,11 @@ import requests from environments.environmental_services import EnvironmentalService +from revoking.headers import RevokingHeader +from revoking.parameters import RevokingParameter +from revoking.request import RevokingRequest +from revoking.request_body import RevokingBody +from revoking.response import RevokingResponse class Revoking(EnvironmentalService): @@ -10,7 +15,6 @@ def _create_request(self, params: RevokingParameter, url: str) -> RevokingReques request_body = RevokingBody(**body_params) header_params = params.get_header_params() - header_params["request_body"] = request_body.json(new_lines=False) request_header = RevokingHeader(**header_params) return RevokingRequest(header=request_header, body=request_body, url=url) From 9372b544c1dded1926f6ccf923107840ff7c7b0e Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 19:34:37 +0300 Subject: [PATCH 09/78] Implement request signing in onboarding --- onboarding/exceptions.py | 6 ++++++ onboarding/headers.py | 4 ++++ onboarding/onboarding.py | 37 +++++++++++++++++++++++++++---------- onboarding/request.py | 15 +++------------ onboarding/request_body.py | 4 ++++ onboarding/response.py | 22 +++++++++++++++++----- onboarding/signature.py | 2 +- 7 files changed, 62 insertions(+), 28 deletions(-) diff --git a/onboarding/exceptions.py b/onboarding/exceptions.py index 96686763..34dc3777 100644 --- a/onboarding/exceptions.py +++ b/onboarding/exceptions.py @@ -13,3 +13,9 @@ class WrongCertificationType(AgriRouuterBaseException): class WrongGateWay(AgriRouuterBaseException): _message = "Wrong Gate Way Id. Use onboarding.enums.GateWays values instead." + + +class RequestNotSigned(AgriRouuterBaseException): + _message = "Request does not contain signature header. Please sign the request with request.sign() method.\n" \ + "Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/" \ + "integration/onboarding.html#signing-requests" diff --git a/onboarding/headers.py b/onboarding/headers.py index 32b17a56..8ebf313e 100644 --- a/onboarding/headers.py +++ b/onboarding/headers.py @@ -16,6 +16,10 @@ def get_header(self) -> dict: def _set_params(self, *args, **kwargs): ... + @abstractmethod + def sign(self, *args, **kwargs): + ... + class SoftwareOnboardingHeader(BaseOnboardingHeader): def __init__(self, diff --git a/onboarding/onboarding.py b/onboarding/onboarding.py index 20365d07..ee0d3dfe 100644 --- a/onboarding/onboarding.py +++ b/onboarding/onboarding.py @@ -1,6 +1,7 @@ import requests from environments.environmental_services import EnvironmentalService +from onboarding.exceptions import RequestNotSigned from onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader from onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter from onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest @@ -10,6 +11,11 @@ class SoftwareOnboarding(EnvironmentalService): + def __init__(self, *args, **kwargs): + self._public_key = kwargs.pop("public_key") + self._private_key = kwargs.pop("private_key") + super(SoftwareOnboarding, self).__init__(*args, **kwargs) + def _create_request(self, params: BaseOnboardingParameter, url: str) -> SoftwareOnboardingRequest: body_params = params.get_body_params() request_body = SoftwareOnboardingBody(**body_params) @@ -21,11 +27,14 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) - return requests.post( - url=request.get_url(), - data=request.get_data(), - headers=request.get_header() - ) + request.sign(self._private_key) + if request.is_signed: + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + raise RequestNotSigned def verify(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResponse: url = self._environment.get_verify_onboard_request_url() @@ -42,6 +51,11 @@ def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResp class CUOnboarding(EnvironmentalService): + def __init__(self, *args, **kwargs): + self._public_key = kwargs.pop("public_key") + self._private_key = kwargs.pop("private_key") + super(CUOnboarding, self).__init__(*args, **kwargs) + def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardingRequest: body_params = params.get_body_params() request_body = CUOnboardingBody(**body_params) @@ -53,11 +67,14 @@ def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardi def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) - return requests.post( - url=request.get_url(), - data=request.get_data(), - headers=request.get_header() - ) + request.sign(self._private_key) + if request.is_signed: + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + raise RequestNotSigned def onboard(self, params: CUOnboardingParameter) -> CUOnboardingResponse: url = self._environment.get_onboard_url() diff --git a/onboarding/request.py b/onboarding/request.py index 5dda15b5..8ec4258f 100644 --- a/onboarding/request.py +++ b/onboarding/request.py @@ -1,5 +1,6 @@ from onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader from onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody +from onboarding.signature import create_signature class BaseOnboardingRequest: @@ -17,14 +18,9 @@ def get_data(self): def get_header(self): return self.header.get_header() - def sign(self): - """ - TODO: add here create_signature - :return: - """ - signature = ... # create signature + def sign(self, private_key): + signature = create_signature(self.body.json(new_lines=False), private_key) self.header.sign(signature) - pass @property def is_signed(self): @@ -33,11 +29,6 @@ def is_signed(self): return True return False - @property - def is_valid(self): - if not self.is_signed: - return False - class SoftwareOnboardingRequest(BaseOnboardingRequest): """ diff --git a/onboarding/request_body.py b/onboarding/request_body.py index b4f922b6..1d6c2ff2 100644 --- a/onboarding/request_body.py +++ b/onboarding/request_body.py @@ -18,6 +18,10 @@ def get_parameters(self, *args, **kwargs) -> dict: def _set_params(self, *args, **kwargs): ... + @abstractmethod + def json(self, *args, **kwargs): + ... + class SoftwareOnboardingBody(BaseOnboardingBody): def __init__(self, diff --git a/onboarding/response.py b/onboarding/response.py index e1d1d420..4718b641 100644 --- a/onboarding/response.py +++ b/onboarding/response.py @@ -1,10 +1,22 @@ -from abc import ABC, abstractmethod +from requests import Response -class BaseOnboardingResonse(ABC): - @abstractmethod - def __init__(self, http_response): - self.response = http_response +class BaseOnboardingResonse: + + def __init__(self, http_response: Response): + self.response: Response = http_response + + @property + def data(self): + return self.response.json() + + @property + def status_code(self): + return self.response.status_code + + @property + def text(self): + return self.response.text class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): diff --git a/onboarding/signature.py b/onboarding/signature.py index fa68befc..6bedc3bd 100644 --- a/onboarding/signature.py +++ b/onboarding/signature.py @@ -18,7 +18,7 @@ def create_signature(request_body: str, private_key: str) -> bytes: return signature -def verify_signature(request_body: str, signature: bytearray, public_key: str) -> None: +def verify_signature(request_body: str, signature: bytes, public_key: str) -> None: public_key_bytes = bytearray(public_key.encode('utf-8')) public_key_data = load_pem_public_key(public_key_bytes) public_key_data.verify( From 810cb1895384be91db57e85b452abab9f0d9dcf2 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 19:34:52 +0300 Subject: [PATCH 10/78] Implement request signing in revoking --- revoking/request.py | 9 +++------ revoking/request_body.py | 9 +++++++++ revoking/response.py | 19 +++++++++++++++++-- revoking/revoking.py | 19 ++++++++++++++----- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/revoking/request.py b/revoking/request.py index a08898c4..df80233e 100644 --- a/revoking/request.py +++ b/revoking/request.py @@ -1,3 +1,4 @@ +from onboarding.signature import create_signature from revoking.headers import RevokingHeader from revoking.request_body import RevokingBody @@ -17,12 +18,8 @@ def get_data(self): def get_header(self): return self.header.get_header() - def sign(self): - """ - TODO: add here create_signature - :return: - """ - signature = ... # create signature + def sign(self, private_key): + signature = create_signature(self.body.json(new_lines=False), private_key) self.header.sign(signature) @property diff --git a/revoking/request_body.py b/revoking/request_body.py index e23c2750..6ec70b2d 100644 --- a/revoking/request_body.py +++ b/revoking/request_body.py @@ -1,3 +1,6 @@ +import json + + class RevokingBody: def __init__(self, account_id, @@ -28,3 +31,9 @@ def _set_params(self, "UTCTimestamp": utc_timestamp, "timeZone": time_zone, } + + def json(self, new_lines: bool = True) -> str: + result = json.dumps(self.get_parameters(), indent="") + if not new_lines: + return result.replace("\n", "") + return result diff --git a/revoking/response.py b/revoking/response.py index 249cad11..cec8bb00 100644 --- a/revoking/response.py +++ b/revoking/response.py @@ -1,7 +1,22 @@ +from requests import Response + + class RevokingResponse: """ Response from revoking request """ - def __init__(self, http_response): - self.response = http_response + def __init__(self, http_response: Response): + self.response: Response = http_response + + @property + def data(self): + return self.response.json() + + @property + def status_code(self): + return self.response.status_code + + @property + def text(self): + return self.response.text diff --git a/revoking/revoking.py b/revoking/revoking.py index 8a8a4f7b..a1adb219 100644 --- a/revoking/revoking.py +++ b/revoking/revoking.py @@ -1,6 +1,7 @@ import requests from environments.environmental_services import EnvironmentalService +from onboarding.exceptions import RequestNotSigned from revoking.headers import RevokingHeader from revoking.parameters import RevokingParameter from revoking.request import RevokingRequest @@ -10,6 +11,11 @@ class Revoking(EnvironmentalService): + def __init__(self, *args, **kwargs): + self._public_key = kwargs.pop("public_key") + self._private_key = kwargs.pop("private_key") + super(Revoking, self).__init__(*args, **kwargs) + def _create_request(self, params: RevokingParameter, url: str) -> RevokingRequest: body_params = params.get_body_params() request_body = RevokingBody(**body_params) @@ -21,11 +27,14 @@ def _create_request(self, params: RevokingParameter, url: str) -> RevokingReques def _perform_request(self, params: RevokingParameter, url: str) -> requests.Response: request = self._create_request(params, url) - return requests.post( - url=request.get_url(), - data=request.get_data(), - headers=request.get_header() - ) + request.sign(self._private_key) + if request.is_signed: + return requests.post( + url=request.get_url(), + data=request.get_data(), + headers=request.get_header() + ) + raise RequestNotSigned def revoke(self, params: RevokingParameter) -> RevokingResponse: url = self._environment.get_revoke_url() From 0fd281e1c95bc094fba1327df67420991c1893cd Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 28 Sep 2021 19:35:04 +0300 Subject: [PATCH 11/78] Implement request signing in auth --- auth/auth.py | 10 ++++++++-- auth/response.py | 21 ++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/auth/auth.py b/auth/auth.py index 6d23e662..d0525e82 100644 --- a/auth/auth.py +++ b/auth/auth.py @@ -12,6 +12,11 @@ class Authorization(EnvironmentalService): TOKEN_KEY = "token" ERROR_KEY = "error" + def __init__(self, *args, **kwargs): + self._public_key = kwargs.pop("public_key") + self._private_key = kwargs.pop("private_key") + super(Authorization, self).__init__(*args, **kwargs) + def get_auth_request_url(self, parameters: AuthUrlParameter) -> str: auth_parameters = parameters.get_parameters() return self._environment.get_secured_onboarding_authorization_url(**auth_parameters) @@ -21,8 +26,9 @@ def extract_auth_response(self, url: str) -> AuthResponse: query_params = self._extract_query_params(parsed_url.query) return AuthResponse(query_params) - def verify_auth_response(self): - pass + @staticmethod + def verify_auth_response(response, public_key): + response.verify(public_key) @staticmethod def _extract_query_params(query_params: str) -> dict: diff --git a/auth/response.py b/auth/response.py index e67f2b8e..40a6b9c3 100644 --- a/auth/response.py +++ b/auth/response.py @@ -1,6 +1,11 @@ import base64 import json from typing import Union +from urllib.parse import unquote + +from cryptography.exceptions import InvalidSignature + +from onboarding.signature import verify_signature class AuthResponse: @@ -19,7 +24,7 @@ def __init__(self, query_params): self.is_successful = not bool(self._error) self.is_valid = False - def verify(self) -> None: + def verify(self, public_key) -> None: """ Validates signature according to docs: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#analyse-result @@ -29,17 +34,23 @@ def verify(self) -> None: :return: """ - # TODO: implement validation of response according to docs: - # https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#analyse-result + encoded_data = self._state + self._token + unquoted_signature = unquote(self._signature) + encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) + try: + verify_signature(encoded_data, encoded_signature, public_key) + except InvalidSignature: + print("Response is invalid: invalid signature.") + self.is_valid = False self.is_valid = True @staticmethod def decode_token(token: Union[str, bytes]) -> dict: if type(token) == str: - token = token.encode("ASCII") + token = token.encode("utf-8") base_64_decoded_token = base64.b64decode(token) - decoded_token = base_64_decoded_token.decode("ASCII") + decoded_token = base_64_decoded_token.decode("utf-8") return json.loads(decoded_token) def get_auth_result(self) -> dict: From 65f5662dad3564bc4f487b5fd53bcf854571f07c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 29 Sep 2021 13:40:28 +0300 Subject: [PATCH 12/78] Refactor RequestNotSigned exception --- onboarding/exceptions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/onboarding/exceptions.py b/onboarding/exceptions.py index 34dc3777..490e51e3 100644 --- a/onboarding/exceptions.py +++ b/onboarding/exceptions.py @@ -16,6 +16,8 @@ class WrongGateWay(AgriRouuterBaseException): class RequestNotSigned(AgriRouuterBaseException): - _message = "Request does not contain signature header. Please sign the request with request.sign() method.\n" \ - "Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/" \ - "integration/onboarding.html#signing-requests" + _message = """ + Request does not contain signature header. Please sign the request with request.sign() method.\n + Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/ + integration/onboarding.html#signing-requests + """ From d96a4f3fec53a47f22caf7a198d6ff4543074a34 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 29 Sep 2021 13:53:38 +0300 Subject: [PATCH 13/78] Create requeirements.txt --- requirements.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e2da2ee2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +certifi==2021.5.30 +cffi==1.14.6 +charset-normalizer==2.0.6 +cryptography==3.4.8 +idna==3.2 +pycparser==2.20 +requests==2.26.0 +urllib3==1.26.7 From bc5cc972df4a2c6622d97267d5fa7975cd33d556 Mon Sep 17 00:00:00 2001 From: kskachkov Date: Mon, 4 Oct 2021 19:08:54 +0300 Subject: [PATCH 14/78] Move files to common agrirouter directory --- {auth => agrirouter/auth}/__init__.py | 0 {auth => agrirouter/auth}/auth.py | 6 +++--- {auth => agrirouter/auth}/enums.py | 0 {auth => agrirouter/auth}/exceptions.py | 0 {auth => agrirouter/auth}/parameters.py | 2 +- {auth => agrirouter/auth}/response.py | 2 +- {clients => agrirouter/clients}/__init__.py | 0 {clients => agrirouter/clients}/clients.py | 2 +- {constants => agrirouter/constants}/__init__.py | 0 {constants => agrirouter/constants}/keys.py | 0 {constants => agrirouter/constants}/media_types.py | 2 +- .../environments}/__init__.py | 0 {environments => agrirouter/environments}/enums.py | 0 .../environments}/environmental_services.py | 4 ++-- .../environments}/environments.py | 0 .../environments}/exceptions.py | 0 {onboarding => agrirouter/onboarding}/__init__.py | 0 {onboarding => agrirouter/onboarding}/enums.py | 2 +- .../onboarding}/exceptions.py | 0 {onboarding => agrirouter/onboarding}/headers.py | 2 +- .../onboarding}/onboarding.py | 14 +++++++------- .../onboarding}/parameters.py | 4 ++-- {onboarding => agrirouter/onboarding}/request.py | 6 +++--- .../onboarding}/request_body.py | 4 ++-- {onboarding => agrirouter/onboarding}/response.py | 0 {onboarding => agrirouter/onboarding}/signature.py | 0 {revoking => agrirouter/revoking}/__init__.py | 0 {revoking => agrirouter/revoking}/headers.py | 2 +- {revoking => agrirouter/revoking}/parameters.py | 2 +- {revoking => agrirouter/revoking}/request.py | 6 +++--- {revoking => agrirouter/revoking}/request_body.py | 0 {revoking => agrirouter/revoking}/response.py | 0 {revoking => agrirouter/revoking}/revoking.py | 14 +++++++------- 33 files changed, 37 insertions(+), 37 deletions(-) rename {auth => agrirouter/auth}/__init__.py (100%) rename {auth => agrirouter/auth}/auth.py (85%) rename {auth => agrirouter/auth}/enums.py (100%) rename {auth => agrirouter/auth}/exceptions.py (100%) rename {auth => agrirouter/auth}/parameters.py (96%) rename {auth => agrirouter/auth}/response.py (97%) rename {clients => agrirouter/clients}/__init__.py (100%) rename {clients => agrirouter/clients}/clients.py (89%) rename {constants => agrirouter/constants}/__init__.py (100%) rename {constants => agrirouter/constants}/keys.py (100%) rename {constants => agrirouter/constants}/media_types.py (63%) rename {environments => agrirouter/environments}/__init__.py (100%) rename {environments => agrirouter/environments}/enums.py (100%) rename {environments => agrirouter/environments}/environmental_services.py (69%) rename {environments => agrirouter/environments}/environments.py (100%) rename {environments => agrirouter/environments}/exceptions.py (100%) rename {onboarding => agrirouter/onboarding}/__init__.py (100%) rename {onboarding => agrirouter/onboarding}/enums.py (74%) rename {onboarding => agrirouter/onboarding}/exceptions.py (100%) rename {onboarding => agrirouter/onboarding}/headers.py (95%) rename {onboarding => agrirouter/onboarding}/onboarding.py (81%) rename {onboarding => agrirouter/onboarding}/parameters.py (96%) rename {onboarding => agrirouter/onboarding}/request.py (80%) rename {onboarding => agrirouter/onboarding}/request_body.py (94%) rename {onboarding => agrirouter/onboarding}/response.py (100%) rename {onboarding => agrirouter/onboarding}/signature.py (100%) rename {revoking => agrirouter/revoking}/__init__.py (100%) rename {revoking => agrirouter/revoking}/headers.py (92%) rename {revoking => agrirouter/revoking}/parameters.py (94%) rename {revoking => agrirouter/revoking}/request.py (84%) rename {revoking => agrirouter/revoking}/request_body.py (100%) rename {revoking => agrirouter/revoking}/response.py (100%) rename {revoking => agrirouter/revoking}/revoking.py (75%) diff --git a/auth/__init__.py b/agrirouter/auth/__init__.py similarity index 100% rename from auth/__init__.py rename to agrirouter/auth/__init__.py diff --git a/auth/auth.py b/agrirouter/auth/auth.py similarity index 85% rename from auth/auth.py rename to agrirouter/auth/auth.py index d0525e82..fa9e2e9a 100644 --- a/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -1,8 +1,8 @@ from urllib.parse import urlparse, parse_qs -from auth.parameters import AuthUrlParameter -from auth.response import AuthResponse -from environments.environmental_services import EnvironmentalService +from agrirouter.auth.parameters import AuthUrlParameter +from agrirouter.auth.response import AuthResponse +from agrirouter.environments.environmental_services import EnvironmentalService class Authorization(EnvironmentalService): diff --git a/auth/enums.py b/agrirouter/auth/enums.py similarity index 100% rename from auth/enums.py rename to agrirouter/auth/enums.py diff --git a/auth/exceptions.py b/agrirouter/auth/exceptions.py similarity index 100% rename from auth/exceptions.py rename to agrirouter/auth/exceptions.py diff --git a/auth/parameters.py b/agrirouter/auth/parameters.py similarity index 96% rename from auth/parameters.py rename to agrirouter/auth/parameters.py index 48ee6e7b..a7d9902d 100644 --- a/auth/parameters.py +++ b/agrirouter/auth/parameters.py @@ -1,6 +1,6 @@ from uuid import uuid4 -from auth.enums import ResponseType +from agrirouter.auth.enums import ResponseType class AuthUrlParameter: diff --git a/auth/response.py b/agrirouter/auth/response.py similarity index 97% rename from auth/response.py rename to agrirouter/auth/response.py index 40a6b9c3..3e4f1067 100644 --- a/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,7 +5,7 @@ from cryptography.exceptions import InvalidSignature -from onboarding.signature import verify_signature +from agrirouter.onboarding.signature import verify_signature class AuthResponse: diff --git a/clients/__init__.py b/agrirouter/clients/__init__.py similarity index 100% rename from clients/__init__.py rename to agrirouter/clients/__init__.py diff --git a/clients/clients.py b/agrirouter/clients/clients.py similarity index 89% rename from clients/clients.py rename to agrirouter/clients/clients.py index f3102e35..f42483ae 100644 --- a/clients/clients.py +++ b/agrirouter/clients/clients.py @@ -1,4 +1,4 @@ -from environments.environments import ProductionEnvironment, QAEnvironment +from agrirouter.environments.environments import ProductionEnvironment, QAEnvironment class InvalidEnvironmentSetup(Exception): diff --git a/constants/__init__.py b/agrirouter/constants/__init__.py similarity index 100% rename from constants/__init__.py rename to agrirouter/constants/__init__.py diff --git a/constants/keys.py b/agrirouter/constants/keys.py similarity index 100% rename from constants/keys.py rename to agrirouter/constants/keys.py diff --git a/constants/media_types.py b/agrirouter/constants/media_types.py similarity index 63% rename from constants/media_types.py rename to agrirouter/constants/media_types.py index a77638f6..56d454f0 100644 --- a/constants/media_types.py +++ b/agrirouter/constants/media_types.py @@ -1,4 +1,4 @@ -from auth.enums import BaseEnum +from agrirouter.auth.enums import BaseEnum class ContentTypes(BaseEnum): diff --git a/environments/__init__.py b/agrirouter/environments/__init__.py similarity index 100% rename from environments/__init__.py rename to agrirouter/environments/__init__.py diff --git a/environments/enums.py b/agrirouter/environments/enums.py similarity index 100% rename from environments/enums.py rename to agrirouter/environments/enums.py diff --git a/environments/environmental_services.py b/agrirouter/environments/environmental_services.py similarity index 69% rename from environments/environmental_services.py rename to agrirouter/environments/environmental_services.py index f980cd2f..c502becd 100644 --- a/environments/environmental_services.py +++ b/agrirouter/environments/environmental_services.py @@ -1,5 +1,5 @@ -from environments.exceptions import InvalidEnvironmentSetup -from environments.environments import ProductionEnvironment, QAEnvironment +from agrirouter.environments.exceptions import InvalidEnvironmentSetup +from agrirouter.environments.environments import ProductionEnvironment, QAEnvironment class EnvironmentalService: diff --git a/environments/environments.py b/agrirouter/environments/environments.py similarity index 100% rename from environments/environments.py rename to agrirouter/environments/environments.py diff --git a/environments/exceptions.py b/agrirouter/environments/exceptions.py similarity index 100% rename from environments/exceptions.py rename to agrirouter/environments/exceptions.py diff --git a/onboarding/__init__.py b/agrirouter/onboarding/__init__.py similarity index 100% rename from onboarding/__init__.py rename to agrirouter/onboarding/__init__.py diff --git a/onboarding/enums.py b/agrirouter/onboarding/enums.py similarity index 74% rename from onboarding/enums.py rename to agrirouter/onboarding/enums.py index cc7dbd31..f25a34e9 100644 --- a/onboarding/enums.py +++ b/agrirouter/onboarding/enums.py @@ -1,4 +1,4 @@ -from auth.enums import BaseEnum +from agrirouter.auth.enums import BaseEnum class CertificateTypes(BaseEnum): diff --git a/onboarding/exceptions.py b/agrirouter/onboarding/exceptions.py similarity index 100% rename from onboarding/exceptions.py rename to agrirouter/onboarding/exceptions.py diff --git a/onboarding/headers.py b/agrirouter/onboarding/headers.py similarity index 95% rename from onboarding/headers.py rename to agrirouter/onboarding/headers.py index 8ebf313e..20fcf6fa 100644 --- a/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from constants.media_types import ContentTypes +from agrirouter.constants.media_types import ContentTypes class BaseOnboardingHeader(ABC): diff --git a/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py similarity index 81% rename from onboarding/onboarding.py rename to agrirouter/onboarding/onboarding.py index ee0d3dfe..0d0094e6 100644 --- a/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -1,12 +1,12 @@ import requests -from environments.environmental_services import EnvironmentalService -from onboarding.exceptions import RequestNotSigned -from onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader -from onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter -from onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest -from onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, CUOnboardingResponse +from agrirouter.environments.environmental_services import EnvironmentalService +from agrirouter.onboarding.exceptions import RequestNotSigned +from agrirouter.onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter +from agrirouter.onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest +from agrirouter.onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody +from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, CUOnboardingResponse class SoftwareOnboarding(EnvironmentalService): diff --git a/onboarding/parameters.py b/agrirouter/onboarding/parameters.py similarity index 96% rename from onboarding/parameters.py rename to agrirouter/onboarding/parameters.py index 12f3373a..f88be38c 100644 --- a/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from constants.media_types import ContentTypes -from onboarding.enums import CertificateTypes +from agrirouter.constants.media_types import ContentTypes +from agrirouter.onboarding.enums import CertificateTypes class BaseOnboardingParameter(ABC): diff --git a/onboarding/request.py b/agrirouter/onboarding/request.py similarity index 80% rename from onboarding/request.py rename to agrirouter/onboarding/request.py index 8ec4258f..aae1e653 100644 --- a/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -1,6 +1,6 @@ -from onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader -from onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody -from onboarding.signature import create_signature +from agrirouter.onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader +from agrirouter.onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody +from agrirouter.onboarding.signature import create_signature class BaseOnboardingRequest: diff --git a/onboarding/request_body.py b/agrirouter/onboarding/request_body.py similarity index 94% rename from onboarding/request_body.py rename to agrirouter/onboarding/request_body.py index 1d6c2ff2..d1c108eb 100644 --- a/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -1,8 +1,8 @@ import json from abc import ABC, abstractmethod -from onboarding.enums import CertificateTypes, GateWays -from onboarding.exceptions import WrongCertificationType, WrongGateWay +from agrirouter.onboarding.enums import CertificateTypes, GateWays +from agrirouter.onboarding.exceptions import WrongCertificationType, WrongGateWay class BaseOnboardingBody(ABC): diff --git a/onboarding/response.py b/agrirouter/onboarding/response.py similarity index 100% rename from onboarding/response.py rename to agrirouter/onboarding/response.py diff --git a/onboarding/signature.py b/agrirouter/onboarding/signature.py similarity index 100% rename from onboarding/signature.py rename to agrirouter/onboarding/signature.py diff --git a/revoking/__init__.py b/agrirouter/revoking/__init__.py similarity index 100% rename from revoking/__init__.py rename to agrirouter/revoking/__init__.py diff --git a/revoking/headers.py b/agrirouter/revoking/headers.py similarity index 92% rename from revoking/headers.py rename to agrirouter/revoking/headers.py index 08dc878e..0c1770f2 100644 --- a/revoking/headers.py +++ b/agrirouter/revoking/headers.py @@ -1,4 +1,4 @@ -from constants.media_types import ContentTypes +from agrirouter.constants.media_types import ContentTypes class RevokingHeader: diff --git a/revoking/parameters.py b/agrirouter/revoking/parameters.py similarity index 94% rename from revoking/parameters.py rename to agrirouter/revoking/parameters.py index ef65d556..95a73332 100644 --- a/revoking/parameters.py +++ b/agrirouter/revoking/parameters.py @@ -1,4 +1,4 @@ -from constants.media_types import ContentTypes +from agrirouter.constants.media_types import ContentTypes class RevokingParameter: diff --git a/revoking/request.py b/agrirouter/revoking/request.py similarity index 84% rename from revoking/request.py rename to agrirouter/revoking/request.py index df80233e..d45b7787 100644 --- a/revoking/request.py +++ b/agrirouter/revoking/request.py @@ -1,6 +1,6 @@ -from onboarding.signature import create_signature -from revoking.headers import RevokingHeader -from revoking.request_body import RevokingBody +from agrirouter.onboarding.signature import create_signature +from agrirouter.revoking.headers import RevokingHeader +from agrirouter.revoking.request_body import RevokingBody class RevokingRequest: diff --git a/revoking/request_body.py b/agrirouter/revoking/request_body.py similarity index 100% rename from revoking/request_body.py rename to agrirouter/revoking/request_body.py diff --git a/revoking/response.py b/agrirouter/revoking/response.py similarity index 100% rename from revoking/response.py rename to agrirouter/revoking/response.py diff --git a/revoking/revoking.py b/agrirouter/revoking/revoking.py similarity index 75% rename from revoking/revoking.py rename to agrirouter/revoking/revoking.py index a1adb219..aaa8f871 100644 --- a/revoking/revoking.py +++ b/agrirouter/revoking/revoking.py @@ -1,12 +1,12 @@ import requests -from environments.environmental_services import EnvironmentalService -from onboarding.exceptions import RequestNotSigned -from revoking.headers import RevokingHeader -from revoking.parameters import RevokingParameter -from revoking.request import RevokingRequest -from revoking.request_body import RevokingBody -from revoking.response import RevokingResponse +from agrirouter.environments.environmental_services import EnvironmentalService +from agrirouter.onboarding.exceptions import RequestNotSigned +from agrirouter.revoking.headers import RevokingHeader +from agrirouter.revoking.parameters import RevokingParameter +from agrirouter.revoking.request import RevokingRequest +from agrirouter.revoking.request_body import RevokingBody +from agrirouter.revoking.response import RevokingResponse class Revoking(EnvironmentalService): From aa3ba7d761694bd463556f1caa48a2cea66c7b61 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 5 Oct 2021 13:57:44 +0300 Subject: [PATCH 15/78] Add package config files (#3) --- .gitignore | 4 +++- agrirouter/__init__.py | 0 pyproject.toml | 3 +++ setup.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 agrirouter/__init__.py create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 72ab21b1..25dda8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ +dist venv .idea **/__pycache__ +*.egg-info *.log *.log.* -.env \ No newline at end of file +.env diff --git a/agrirouter/__init__.py b/agrirouter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..9787c3bd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..8ca649fe --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup + +setup( + name='agrirouter-sdk-python', + version='1.0.0', + packages=['agrirouter'], + python_requires=">= 3.6", + url='https://github.com/DKE-Data/agrirouter-sdk-python', + license='Apache-2.0', + author='agrirouter', + author_email='info@dke-data.com', + description="""This project contains the API for the communication with the agrirouter. Everything you need for the + onboarding process, secure communication and much more.""", + classifiers=[ + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + ], + project_urls={ + 'Documentation': 'https://github.com/DKE-Data/agrirouter-sdk-python', + 'Source': 'https://github.com/DKE-Data/agrirouter-sdk-python', + }, +) From 8d35ed2b2e0526ba83f666a3776bb3764155295e Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 5 Oct 2021 21:02:04 +0300 Subject: [PATCH 16/78] Add initial documentation (#4) * Create LICENSE * Add dependencies to setup * Add assets * Add README * Add tests directory * Ignore build dir --- .gitignore | 1 + LICENSE | 201 +++++++++++++++++++++++++++++++++++ README.adoc | 67 ++++++++++++ assets/images/agrirouter.svg | 69 ++++++++++++ assets/images/lmis.svg | 79 ++++++++++++++ setup.py | 10 ++ tests/__init__.py | 0 7 files changed, 427 insertions(+) create mode 100644 LICENSE create mode 100644 README.adoc create mode 100755 assets/images/agrirouter.svg create mode 100644 assets/images/lmis.svg create mode 100644 tests/__init__.py diff --git a/.gitignore b/.gitignore index 25dda8a9..b8c6d657 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build dist venv .idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.adoc b/README.adoc new file mode 100644 index 00000000..59227151 --- /dev/null +++ b/README.adoc @@ -0,0 +1,67 @@ += agrirouter-sdk-python +:imagesdir: assets/images +:toc: +:toc-title: +:toclevels: 4 + +[abstract] +== Abstract +image::agrirouter.svg[agrirouter] + +The agrirouter is a universal data exchange platform for farmers and agricultural contractors that makes it possible to connect machinery and agricultural software, regardless of vendor or manufacturer. Agrirouter does not save data; it transfers data. +As a universal data exchange platform, agrirouter fills a gap on the way to Farming 4.0. Its underlying concept unites cross-vendor and discrimination-free data transfer. You retain full control over your data. Even data exchange with service providers (e.g. agricultural contractors) and other partners is uncomplicated: Data are very rapidly transferred via the online connection, and if you wish, is intelligently connected to other datasets. + +== Supporters & Maintainers +image::lmis.svg[agrirouter] + +The LMIS AG is a recognised german IT service company, certified according to DIN EN ISO 9001:2015 and based in +Osnabrück, Berlin, Friedland and Wuppertal. Our core competence is the individual development, optimisation and support +of IT solutions. We also provide professional IT consulting services and training courses. We have been supporting +the whole project during the development in the field of test management and are currently responsible for the development +support of the platform. + +We are active maintainers of the SDK and are using the SDK for internal testing purposes as well. Therefore, we have a +high interest in a stable and usable interface to connect to the agrirouter. + +Feel free to get in touch by visiting our https://www.lmis.de[website] or contacting us via GitHub. + +== The current project you're looking at + +This project contains the SDK for the communication with the agrirouter. Everything you need for the onboard process, secure communication and much more. + +== Installation + +The necessary dependencies are installed via `composer`. Just run the following command to add the SDK to your project. + + +`git clone https://github.com/DKE-Data/agrirouter-sdk-python.git` + +To install the SDK run the 'setup.py' script in the main directory (for a global install you will need to run this command with root privileges): + +`cd agrirouter-sdk-python` + +`virtualenv venv` + +`...` + +`$ . venv/bin/activate` + +`python setup.py install` + +For more install options type: + +`python setup.py --help` + +== Requirements + +Python 3.6 or above is required. + + +== External resources + +Here are some external resources for the development: + +* https://my-agrirouter.com[My Agrirouter Website] +* https://github.com/DKE-Data/agrirouter-interface-documentation[Integration Guide] +* https://www.aef-online.org[EFDI Protobuf Definition] +* https://www.lmis.de[LMIS - Maintenance & Support] diff --git a/assets/images/agrirouter.svg b/assets/images/agrirouter.svg new file mode 100755 index 00000000..c3d214b6 --- /dev/null +++ b/assets/images/agrirouter.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/lmis.svg b/assets/images/lmis.svg new file mode 100644 index 00000000..95e5da4a --- /dev/null +++ b/assets/images/lmis.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py index 8ca649fe..1d3753e5 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,16 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', ], + install_requires=[ + 'certifi~=2021.5.30', + 'cffi~=1.14.6', + 'charset-normalizer~=2.0.6', + 'cryptography~=3.4.8', + 'idna~=3.2', + 'pycparser~=2.20', + 'requests~=2.26.0', + 'urllib3~=1.26.7' + ], project_urls={ 'Documentation': 'https://github.com/DKE-Data/agrirouter-sdk-python', 'Source': 'https://github.com/DKE-Data/agrirouter-sdk-python', diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b From 686eff30841f2f88f38ba5dd589f16237ca634f0 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 6 Oct 2021 10:51:54 +0300 Subject: [PATCH 17/78] Clean up the README (#5) --- README.adoc | 24 +++---------- assets/images/lmis.svg | 79 ------------------------------------------ 2 files changed, 5 insertions(+), 98 deletions(-) delete mode 100644 assets/images/lmis.svg diff --git a/README.adoc b/README.adoc index 59227151..37137e1d 100644 --- a/README.adoc +++ b/README.adoc @@ -11,19 +11,6 @@ image::agrirouter.svg[agrirouter] The agrirouter is a universal data exchange platform for farmers and agricultural contractors that makes it possible to connect machinery and agricultural software, regardless of vendor or manufacturer. Agrirouter does not save data; it transfers data. As a universal data exchange platform, agrirouter fills a gap on the way to Farming 4.0. Its underlying concept unites cross-vendor and discrimination-free data transfer. You retain full control over your data. Even data exchange with service providers (e.g. agricultural contractors) and other partners is uncomplicated: Data are very rapidly transferred via the online connection, and if you wish, is intelligently connected to other datasets. -== Supporters & Maintainers -image::lmis.svg[agrirouter] - -The LMIS AG is a recognised german IT service company, certified according to DIN EN ISO 9001:2015 and based in -Osnabrück, Berlin, Friedland and Wuppertal. Our core competence is the individual development, optimisation and support -of IT solutions. We also provide professional IT consulting services and training courses. We have been supporting -the whole project during the development in the field of test management and are currently responsible for the development -support of the platform. - -We are active maintainers of the SDK and are using the SDK for internal testing purposes as well. Therefore, we have a -high interest in a stable and usable interface to connect to the agrirouter. - -Feel free to get in touch by visiting our https://www.lmis.de[website] or contacting us via GitHub. == The current project you're looking at @@ -34,23 +21,23 @@ This project contains the SDK for the communication with the agrirouter. Everyth The necessary dependencies are installed via `composer`. Just run the following command to add the SDK to your project. -`git clone https://github.com/DKE-Data/agrirouter-sdk-python.git` +`$ git clone https://github.com/DKE-Data/agrirouter-sdk-python.git` To install the SDK run the 'setup.py' script in the main directory (for a global install you will need to run this command with root privileges): -`cd agrirouter-sdk-python` +`$ cd agrirouter-sdk-python` -`virtualenv venv` +`$ virtualenv venv` `...` `$ . venv/bin/activate` -`python setup.py install` +`$ python setup.py install` For more install options type: -`python setup.py --help` +`$ python setup.py --help` == Requirements @@ -64,4 +51,3 @@ Here are some external resources for the development: * https://my-agrirouter.com[My Agrirouter Website] * https://github.com/DKE-Data/agrirouter-interface-documentation[Integration Guide] * https://www.aef-online.org[EFDI Protobuf Definition] -* https://www.lmis.de[LMIS - Maintenance & Support] diff --git a/assets/images/lmis.svg b/assets/images/lmis.svg deleted file mode 100644 index 95e5da4a..00000000 --- a/assets/images/lmis.svg +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 45e7654118168c42846b3352044a76b3ff12ce96 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 13 Oct 2021 14:57:37 +0300 Subject: [PATCH 18/78] Implement messaging (#8) * Implement get_connection_criteria, get_sensor_alternate_id, get_authentication methods for BaseOnboardingResonse * Implement messaging/dto parameters * Create protobuf classes for messages * Implement certification for messaging * Create AbstractClient, HttpMessagingClient, MqttMessagingClient * Add prtobuf library to requirements * Create MessageHeaderParameters, MessagePayloadParameters * Add paho-mqtt to requirements * Fix MqttMessagingClient * Create cloud_virtualized_app_registration_pb2.py protobuf file * Create utils * Fix imports in protobuf generated files * Implement encoding messages * Implement decoding messages * Implement mqtt client * Implement MQttMessagingService. Refactor HttpMessagingService * Refactor messaging/parameters * Decoded Message * Create TypeUrlNotFoundError * Create TypeUrl service * Refactor messaging/decode * Fix generated/cload_virtualized_add_registration * Create TechnicalMessageType enum * Rename function encode to encode_messge * Implement MessagingServiceParameters * Implement MEssagingServices classes * Add AR Public Keys to handle signature verification * Refactor auth * Implement QueryMessagesService, QueryHeaderService * Implement OutboxService * Implement CloudOnboardService, CloudOffboardService * Fix SoftwareOnboardingHeader.sign() method * Fix imports * Create examples file * Add unit tests (#6) * Move main classes to agrirouter/__init__.py * Update examples.txt * Fix examples.txt * Fix examples.txt * Fix SoftwareOnboardingHeader * Fix onboarding * Implement messaging certification * Fix onboarding headers signing * Refactor onboarding request body * Refactor onboarding parameters * Refactor onboarding client * Fix requests certification in messaging * Refactor messaging certification --- .gitignore | 1 + README.adoc | 3 + agrirouter/__init__.py | 17 + agrirouter/auth/auth.py | 4 +- agrirouter/auth/response.py | 20 +- agrirouter/clients/clients.py | 29 - agrirouter/environments/environments.py | 11 + agrirouter/environments/keys.py | 19 + .../cloud_virtualized_app_registration_pb2.py | 325 ++++++++ agrirouter/generated/commons/chunk_pb2.py | 91 +++ agrirouter/generated/commons/message_pb2.py | 214 ++++++ .../request/payload/account/endpoints_pb2.py | 110 +++ .../payload/endpoint/capabilities_pb2.py | 205 +++++ .../payload/endpoint/subscription_pb2.py | 125 +++ .../request/payload/feed/feed_requests_pb2.py | 231 ++++++ .../messaging/request/request_pb2.py | 208 +++++ .../response/payload/account/endpoints_pb2.py | 227 ++++++ .../payload/endpoint/capability_pb2.py | 125 +++ .../payload/feed/feed_response_pb2.py | 710 ++++++++++++++++++ .../payload/feed/push_notification_pb2.py | 239 ++++++ .../messaging/response/response_pb2.py | 211 ++++++ .../generated/settings/dh_settings_pb2.py | 77 ++ agrirouter/{clients => messaging}/__init__.py | 0 agrirouter/messaging/certification.py | 23 + .../messaging/clients}/__init__.py | 0 agrirouter/messaging/clients/http.py | 22 + agrirouter/messaging/clients/mqtt.py | 138 ++++ agrirouter/messaging/decode.py | 50 ++ agrirouter/messaging/encode.py | 46 ++ agrirouter/messaging/enums.py | 15 + agrirouter/messaging/exceptions.py | 9 + agrirouter/messaging/messages.py | 115 +++ .../parameters/__init__.py} | 0 agrirouter/messaging/parameters/dto.py | 91 +++ agrirouter/messaging/parameters/service.py | 348 +++++++++ agrirouter/messaging/request.py | 25 + agrirouter/messaging/result.py | 47 ++ .../services/__init__.py} | 0 agrirouter/messaging/services/cloud.py | 71 ++ agrirouter/messaging/services/commons.py | 90 +++ .../messaging/services/http/__init__.py | 0 agrirouter/messaging/services/http/outbox.py | 30 + agrirouter/messaging/services/messaging.py | 254 +++++++ agrirouter/onboarding/headers.py | 10 +- agrirouter/onboarding/onboarding.py | 7 +- agrirouter/onboarding/parameters.py | 9 +- agrirouter/onboarding/request.py | 2 +- agrirouter/onboarding/request_body.py | 15 +- agrirouter/onboarding/response.py | 25 +- agrirouter/utils/__init__.py | 0 agrirouter/utils/type_url.py | 45 ++ agrirouter/utils/utc_time_util.py | 6 + agrirouter/utils/uuid_util.py | 5 + conftest.py | 13 + examples.txt | 72 ++ requirements.txt | 6 + tests/auth_test/test_response.py | 22 + tests/constants.py | 60 ++ .../test_environmental_services.py | 11 + tests/enviroments_test/test_environments.py | 213 ++++++ tests/messaging_test/test_request.py | 15 + tests/onboarding_test/test_headers.py | 23 + tox.ini | 16 + 63 files changed, 5095 insertions(+), 56 deletions(-) delete mode 100644 agrirouter/clients/clients.py create mode 100644 agrirouter/environments/keys.py create mode 100644 agrirouter/generated/cloud_provider_integration/cloud_virtualized_app_registration_pb2.py create mode 100644 agrirouter/generated/commons/chunk_pb2.py create mode 100644 agrirouter/generated/commons/message_pb2.py create mode 100644 agrirouter/generated/messaging/request/payload/account/endpoints_pb2.py create mode 100644 agrirouter/generated/messaging/request/payload/endpoint/capabilities_pb2.py create mode 100644 agrirouter/generated/messaging/request/payload/endpoint/subscription_pb2.py create mode 100644 agrirouter/generated/messaging/request/payload/feed/feed_requests_pb2.py create mode 100644 agrirouter/generated/messaging/request/request_pb2.py create mode 100644 agrirouter/generated/messaging/response/payload/account/endpoints_pb2.py create mode 100644 agrirouter/generated/messaging/response/payload/endpoint/capability_pb2.py create mode 100644 agrirouter/generated/messaging/response/payload/feed/feed_response_pb2.py create mode 100644 agrirouter/generated/messaging/response/payload/feed/push_notification_pb2.py create mode 100644 agrirouter/generated/messaging/response/response_pb2.py create mode 100644 agrirouter/generated/settings/dh_settings_pb2.py rename agrirouter/{clients => messaging}/__init__.py (100%) create mode 100644 agrirouter/messaging/certification.py rename {tests => agrirouter/messaging/clients}/__init__.py (100%) create mode 100644 agrirouter/messaging/clients/http.py create mode 100644 agrirouter/messaging/clients/mqtt.py create mode 100644 agrirouter/messaging/decode.py create mode 100644 agrirouter/messaging/encode.py create mode 100644 agrirouter/messaging/enums.py create mode 100644 agrirouter/messaging/exceptions.py create mode 100644 agrirouter/messaging/messages.py rename agrirouter/{constants/keys.py => messaging/parameters/__init__.py} (100%) create mode 100644 agrirouter/messaging/parameters/dto.py create mode 100644 agrirouter/messaging/parameters/service.py create mode 100644 agrirouter/messaging/request.py create mode 100644 agrirouter/messaging/result.py rename agrirouter/{environments/enums.py => messaging/services/__init__.py} (100%) create mode 100644 agrirouter/messaging/services/cloud.py create mode 100644 agrirouter/messaging/services/commons.py create mode 100644 agrirouter/messaging/services/http/__init__.py create mode 100644 agrirouter/messaging/services/http/outbox.py create mode 100644 agrirouter/messaging/services/messaging.py create mode 100644 agrirouter/utils/__init__.py create mode 100644 agrirouter/utils/type_url.py create mode 100644 agrirouter/utils/utc_time_util.py create mode 100644 agrirouter/utils/uuid_util.py create mode 100644 conftest.py create mode 100644 examples.txt create mode 100644 tests/auth_test/test_response.py create mode 100644 tests/constants.py create mode 100644 tests/enviroments_test/test_environmental_services.py create mode 100644 tests/enviroments_test/test_environments.py create mode 100644 tests/messaging_test/test_request.py create mode 100644 tests/onboarding_test/test_headers.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index b8c6d657..980ae2ce 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ venv *.log *.log.* .env +.pytest_cache \ No newline at end of file diff --git a/README.adoc b/README.adoc index 37137e1d..a8a74271 100644 --- a/README.adoc +++ b/README.adoc @@ -43,6 +43,9 @@ For more install options type: Python 3.6 or above is required. +== Running unit tests + +`$ pytest` == External resources diff --git a/agrirouter/__init__.py b/agrirouter/__init__.py index e69de29b..ad6ecf05 100644 --- a/agrirouter/__init__.py +++ b/agrirouter/__init__.py @@ -0,0 +1,17 @@ +from agrirouter.auth.auth import Authorization +from agrirouter.auth.parameters import AuthUrlParameter + +from agrirouter.onboarding.onboarding import SoftwareOnboarding +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter + +from agrirouter.revoking.revoking import Revoking +from agrirouter.revoking.parameters import RevokingParameter + +from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters, \ + QueryMessageParameters, QueryHeaderParameters, CloudOffboardParameters, CloudOnboardParameters, \ + CapabilityParameters, FeedConfirmParameters, FeedDeleteParameters, ListEndpointsParameters, MessageParameters, \ + SubscriptionParameters +from agrirouter.messaging.services.cloud import CloudOnboardService, CloudOffboardService +from agrirouter.messaging.services.messaging import SubscriptionService, CapabilityService, FeedConfirmService,\ + FeedDeleteService, QueryHeaderService, QueryMessagesService, ListEndpointsService + diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index fa9e2e9a..6cf1902b 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -26,8 +26,8 @@ def extract_auth_response(self, url: str) -> AuthResponse: query_params = self._extract_query_params(parsed_url.query) return AuthResponse(query_params) - @staticmethod - def verify_auth_response(response, public_key): + def verify_auth_response(self, response, public_key=None): + public_key = public_key if public_key else self._environment.get_env_public_key() response.verify(public_key) @staticmethod diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index 3e4f1067..a711c4a6 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -22,7 +22,16 @@ def __init__(self, query_params): self._token = query_params.get(self.TOKEN_KEY, None) self._error = query_params.get(self.ERROR_KEY, None) self.is_successful = not bool(self._error) - self.is_valid = False + self._was_verified = False + self._is_valid = False + + @property + def is_valid(self): + if not self._was_verified: + raise PermissionError("The response was not verified yet. Please verify first. " + "You can access is_valid only after verifying") + + return self._is_valid def verify(self, public_key) -> None: """ @@ -37,13 +46,14 @@ def verify(self, public_key) -> None: encoded_data = self._state + self._token unquoted_signature = unquote(self._signature) encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) + self._was_verified = True try: verify_signature(encoded_data, encoded_signature, public_key) except InvalidSignature: print("Response is invalid: invalid signature.") - self.is_valid = False + self._is_valid = False - self.is_valid = True + self._is_valid = True @staticmethod def decode_token(token: Union[str, bytes]) -> dict: @@ -63,7 +73,3 @@ def get_auth_result(self) -> dict: self.TOKEN_KEY: self._token, self.CRED_KEY: decoded_token } - - -def decorator(func): - pass diff --git a/agrirouter/clients/clients.py b/agrirouter/clients/clients.py deleted file mode 100644 index f42483ae..00000000 --- a/agrirouter/clients/clients.py +++ /dev/null @@ -1,29 +0,0 @@ -from agrirouter.environments.environments import ProductionEnvironment, QAEnvironment - - -class InvalidEnvironmentSetup(Exception): - def __init__(self, message=None, env=None): - if not message: - message = "Invalid value of env parameter. [QA] or [Production] values are allowed" - self.message = message - self.env = env - - -class ARClient: - - def __init__(self, env): - self._set_env(env) - - def authenticate(self): - auth_link = self._create_auth_link() - - def _create_auth_link(self): - return "" - - def _set_env(self, env): - if env == "QA": - self._environment = QAEnvironment - if env == "Production": - return ProductionEnvironment - else: - raise InvalidEnvironmentSetup(env=env) diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 9e6d75b2..503ecd6b 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -1,3 +1,5 @@ +from agrirouter.environments.keys import AR_QA_PUBLIC_KEY, AR_PROD_PUBLIC_KEY + class BaseEnvironment: _AGRIROUTER_LOGIN_URL = "/app" @@ -10,6 +12,8 @@ class BaseEnvironment: _API_PREFIX = "" _REGISTRATION_SERVICE_URL = "" + AR_PUBLIC_KEY = None + def get_base_url(self) -> str: return self._ENV_BASE_URL @@ -45,14 +49,21 @@ def get_secured_onboarding_authorization_url(self, application_id, response_type def get_mqtt_server_url(self, host, port) -> str: return self._MQTT_URL_TEMPLATE.format(host=host, port=port) + def get_env_public_key(self): + return self.AR_PUBLIC_KEY + class ProductionEnvironment(BaseEnvironment): _ENV_BASE_URL = "https://goto.my-agrirouter.com" _API_PREFIX = "/api/v1.0" _REGISTRATION_SERVICE_URL = "https://onboard.my-agrirouter.com" + AR_PUBLIC_KEY = AR_PROD_PUBLIC_KEY + class QAEnvironment(BaseEnvironment): _ENV_BASE_URL = "https://agrirouter-qa.cfapps.eu10.hana.ondemand.com" _API_PREFIX = "/api/v1.0" _REGISTRATION_SERVICE_URL = "https://agrirouter-registration-service-hubqa-eu10.cfapps.eu10.hana.ondemand.com" + + AR_PUBLIC_KEY = AR_QA_PUBLIC_KEY diff --git a/agrirouter/environments/keys.py b/agrirouter/environments/keys.py new file mode 100644 index 00000000..7ed3a292 --- /dev/null +++ b/agrirouter/environments/keys.py @@ -0,0 +1,19 @@ +AR_QA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" \ + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n" \ + "3HvTfUVcismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n" \ + "8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4u2FVJphC1m2BfLxelGLjzx\n" \ + "VAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n" \ + "1/7MLD6IE2+HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\n" \ + "Y8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wNvxXWtkng3yh+7WiZ/Y\n" \ + "qwIDAQAB\n" \ + "-----END PUBLIC KEY-----" + +AR_PROD_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" \ + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwCxD31sYtzH9NTfZ6n8H\n" \ + "+H/QgOaoTL9GAakplAsdwYSLjBpgYMZOHIgkdM9ksRP8WsITChtZtxrCnBjR8bap\n" \ + "ekPT/pM9zPZlNEPxUlylJNwwTWjzTJP03+Yr07Q8v8fTJ5VWzAHlHtGQ/sI7yXA8\n" \ + "pzruTNre1MzxO3lkljt2Q2e7CVXAp1b53BghgysppL9Bl7NK1R+vdWSs0B1Db/Gj\n" \ + "alOkWUnhivTjRMX61RGDCQSVSEaX12EvJX7FooAsW3NFeZCgeZGWEa5ZMALIiBL4\n" \ + "GNASOOHju7ewlYjkyGIRxxAoc3C0w5dg1qlLiAFWToYwgDOcUpLRjU/7bzGiGvp8\n" \ + "RwIDAQAB\n" \ + "-----END PUBLIC KEY-----" diff --git a/agrirouter/generated/cloud_provider_integration/cloud_virtualized_app_registration_pb2.py b/agrirouter/generated/cloud_provider_integration/cloud_virtualized_app_registration_pb2.py new file mode 100644 index 00000000..2ce2e547 --- /dev/null +++ b/agrirouter/generated/cloud_provider_integration/cloud_virtualized_app_registration_pb2.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: cloud-provider-integration/cloud-virtualized-app-registration.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from agrirouter.generated.commons import message_pb2 as commons_dot_message__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='cloud-provider-integration/cloud-virtualized-app-registration.proto', + package='agrirouter.cloud.registration', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\nCcloud-provider-integration/cloud-virtualized-app-registration.proto\x12\x1d\x61grirouter.cloud.registration\x1a\x15\x63ommons/message.proto\"\xb7\x01\n\x11OnboardingRequest\x12i\n\x13onboarding_requests\x18\x01 \x03(\x0b\x32L.agrirouter.cloud.registration.OnboardingRequest.EndpointRegistrationDetails\x1a\x37\n\x1b\x45ndpointRegistrationDetails\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xaf\x03\n\x12OnboardingResponse\x12j\n\x13onboarded_endpoints\x18\x01 \x03(\x0b\x32M.agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails\x12L\n\x08\x66\x61ilures\x18\x02 \x03(\x0b\x32:.agrirouter.cloud.registration.OnboardingResponse.Failures\x1a\x99\x01\n\x1b\x45ndpointRegistrationDetails\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1b\n\x13\x64\x65vice_alternate_id\x18\x02 \x01(\t\x12\x1b\n\x13sensor_alternate_id\x18\x03 \x01(\t\x12\x1f\n\x17\x63\x61pability_alternate_id\x18\x04 \x01(\t\x12\x13\n\x0b\x65ndpoint_id\x18\x05 \x01(\t\x1a\x43\n\x08\x46\x61ilures\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\x06reason\x18\x02 \x01(\x0b\x32\x1b.agrirouter.commons.Message\"\'\n\x12OffboardingRequest\x12\x11\n\tendpoints\x18\x01 \x03(\tb\x06proto3' + , + dependencies=[commons_dot_message__pb2.DESCRIPTOR,]) + + + + +_ONBOARDINGREQUEST_ENDPOINTREGISTRATIONDETAILS = _descriptor.Descriptor( + name='EndpointRegistrationDetails', + full_name='agrirouter.cloud.registration.OnboardingRequest.EndpointRegistrationDetails', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='agrirouter.cloud.registration.OnboardingRequest.EndpointRegistrationDetails.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='name', full_name='agrirouter.cloud.registration.OnboardingRequest.EndpointRegistrationDetails.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=254, + serialized_end=309, +) + +_ONBOARDINGREQUEST = _descriptor.Descriptor( + name='OnboardingRequest', + full_name='agrirouter.cloud.registration.OnboardingRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='onboarding_requests', full_name='agrirouter.cloud.registration.OnboardingRequest.onboarding_requests', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_ONBOARDINGREQUEST_ENDPOINTREGISTRATIONDETAILS, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=126, + serialized_end=309, +) + + +_ONBOARDINGRESPONSE_ENDPOINTREGISTRATIONDETAILS = _descriptor.Descriptor( + name='EndpointRegistrationDetails', + full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='device_alternate_id', full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails.device_alternate_id', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sensor_alternate_id', full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails.sensor_alternate_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='capability_alternate_id', full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails.capability_alternate_id', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='endpoint_id', full_name='agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails.endpoint_id', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=521, + serialized_end=674, +) + +_ONBOARDINGRESPONSE_FAILURES = _descriptor.Descriptor( + name='Failures', + full_name='agrirouter.cloud.registration.OnboardingResponse.Failures', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='agrirouter.cloud.registration.OnboardingResponse.Failures.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reason', full_name='agrirouter.cloud.registration.OnboardingResponse.Failures.reason', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=676, + serialized_end=743, +) + +_ONBOARDINGRESPONSE = _descriptor.Descriptor( + name='OnboardingResponse', + full_name='agrirouter.cloud.registration.OnboardingResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='onboarded_endpoints', full_name='agrirouter.cloud.registration.OnboardingResponse.onboarded_endpoints', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='failures', full_name='agrirouter.cloud.registration.OnboardingResponse.failures', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_ONBOARDINGRESPONSE_ENDPOINTREGISTRATIONDETAILS, _ONBOARDINGRESPONSE_FAILURES, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=312, + serialized_end=743, +) + + +_OFFBOARDINGREQUEST = _descriptor.Descriptor( + name='OffboardingRequest', + full_name='agrirouter.cloud.registration.OffboardingRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='endpoints', full_name='agrirouter.cloud.registration.OffboardingRequest.endpoints', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=745, + serialized_end=784, +) + +_ONBOARDINGREQUEST_ENDPOINTREGISTRATIONDETAILS.containing_type = _ONBOARDINGREQUEST +_ONBOARDINGREQUEST.fields_by_name['onboarding_requests'].message_type = _ONBOARDINGREQUEST_ENDPOINTREGISTRATIONDETAILS +_ONBOARDINGRESPONSE_ENDPOINTREGISTRATIONDETAILS.containing_type = _ONBOARDINGRESPONSE +_ONBOARDINGRESPONSE_FAILURES.fields_by_name['reason'].message_type = commons_dot_message__pb2._MESSAGE +_ONBOARDINGRESPONSE_FAILURES.containing_type = _ONBOARDINGRESPONSE +_ONBOARDINGRESPONSE.fields_by_name['onboarded_endpoints'].message_type = _ONBOARDINGRESPONSE_ENDPOINTREGISTRATIONDETAILS +_ONBOARDINGRESPONSE.fields_by_name['failures'].message_type = _ONBOARDINGRESPONSE_FAILURES +DESCRIPTOR.message_types_by_name['OnboardingRequest'] = _ONBOARDINGREQUEST +DESCRIPTOR.message_types_by_name['OnboardingResponse'] = _ONBOARDINGRESPONSE +DESCRIPTOR.message_types_by_name['OffboardingRequest'] = _OFFBOARDINGREQUEST +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +OnboardingRequest = _reflection.GeneratedProtocolMessageType('OnboardingRequest', (_message.Message,), { + + 'EndpointRegistrationDetails' : _reflection.GeneratedProtocolMessageType('EndpointRegistrationDetails', (_message.Message,), { + 'DESCRIPTOR' : _ONBOARDINGREQUEST_ENDPOINTREGISTRATIONDETAILS, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OnboardingRequest.EndpointRegistrationDetails) + }) + , + 'DESCRIPTOR' : _ONBOARDINGREQUEST, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OnboardingRequest) + }) +_sym_db.RegisterMessage(OnboardingRequest) +_sym_db.RegisterMessage(OnboardingRequest.EndpointRegistrationDetails) + +OnboardingResponse = _reflection.GeneratedProtocolMessageType('OnboardingResponse', (_message.Message,), { + + 'EndpointRegistrationDetails' : _reflection.GeneratedProtocolMessageType('EndpointRegistrationDetails', (_message.Message,), { + 'DESCRIPTOR' : _ONBOARDINGRESPONSE_ENDPOINTREGISTRATIONDETAILS, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OnboardingResponse.EndpointRegistrationDetails) + }) + , + + 'Failures' : _reflection.GeneratedProtocolMessageType('Failures', (_message.Message,), { + 'DESCRIPTOR' : _ONBOARDINGRESPONSE_FAILURES, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OnboardingResponse.Failures) + }) + , + 'DESCRIPTOR' : _ONBOARDINGRESPONSE, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OnboardingResponse) + }) +_sym_db.RegisterMessage(OnboardingResponse) +_sym_db.RegisterMessage(OnboardingResponse.EndpointRegistrationDetails) +_sym_db.RegisterMessage(OnboardingResponse.Failures) + +OffboardingRequest = _reflection.GeneratedProtocolMessageType('OffboardingRequest', (_message.Message,), { + 'DESCRIPTOR' : _OFFBOARDINGREQUEST, + '__module__' : 'cloud_provider_integration.cloud_virtualized_app_registration_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.cloud.registration.OffboardingRequest) + }) +_sym_db.RegisterMessage(OffboardingRequest) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/commons/chunk_pb2.py b/agrirouter/generated/commons/chunk_pb2.py new file mode 100644 index 00000000..005fddf2 --- /dev/null +++ b/agrirouter/generated/commons/chunk_pb2.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: commons/chunk.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='commons/chunk.proto', + package='agrirouter.commons', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x13\x63ommons/chunk.proto\x12\x12\x61grirouter.commons\"X\n\x0e\x43hunkComponent\x12\x12\n\ncontext_id\x18\x01 \x01(\t\x12\x0f\n\x07\x63urrent\x18\x02 \x01(\x03\x12\r\n\x05total\x18\x03 \x01(\x03\x12\x12\n\ntotal_size\x18\x04 \x01(\x03\x62\x06proto3' +) + + + + +_CHUNKCOMPONENT = _descriptor.Descriptor( + name='ChunkComponent', + full_name='agrirouter.commons.ChunkComponent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='context_id', full_name='agrirouter.commons.ChunkComponent.context_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='current', full_name='agrirouter.commons.ChunkComponent.current', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='total', full_name='agrirouter.commons.ChunkComponent.total', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='total_size', full_name='agrirouter.commons.ChunkComponent.total_size', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=43, + serialized_end=131, +) + +DESCRIPTOR.message_types_by_name['ChunkComponent'] = _CHUNKCOMPONENT +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ChunkComponent = _reflection.GeneratedProtocolMessageType('ChunkComponent', (_message.Message,), { + 'DESCRIPTOR' : _CHUNKCOMPONENT, + '__module__' : 'commons.chunk_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.commons.ChunkComponent) + }) +_sym_db.RegisterMessage(ChunkComponent) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/commons/message_pb2.py b/agrirouter/generated/commons/message_pb2.py new file mode 100644 index 00000000..73433729 --- /dev/null +++ b/agrirouter/generated/commons/message_pb2.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: commons/message.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='commons/message.proto', + package='agrirouter.commons', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x15\x63ommons/message.proto\x12\x12\x61grirouter.commons\"\x92\x01\n\x07Message\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x14\n\x0cmessage_code\x18\x02 \x01(\t\x12\x33\n\x04\x61rgs\x18\x03 \x03(\x0b\x32%.agrirouter.commons.Message.ArgsEntry\x1a+\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x1d\n\x08Metadata\x12\x11\n\tfile_name\x18\x01 \x01(\t\"9\n\x08Messages\x12-\n\x08messages\x18\x01 \x03(\x0b\x32\x1b.agrirouter.commons.Messageb\x06proto3' +) + + + + +_MESSAGE_ARGSENTRY = _descriptor.Descriptor( + name='ArgsEntry', + full_name='agrirouter.commons.Message.ArgsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='agrirouter.commons.Message.ArgsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='agrirouter.commons.Message.ArgsEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=b'8\001', + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=149, + serialized_end=192, +) + +_MESSAGE = _descriptor.Descriptor( + name='Message', + full_name='agrirouter.commons.Message', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message', full_name='agrirouter.commons.Message.message', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_code', full_name='agrirouter.commons.Message.message_code', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='args', full_name='agrirouter.commons.Message.args', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_MESSAGE_ARGSENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=46, + serialized_end=192, +) + + +_METADATA = _descriptor.Descriptor( + name='Metadata', + full_name='agrirouter.commons.Metadata', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='file_name', full_name='agrirouter.commons.Metadata.file_name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=194, + serialized_end=223, +) + + +_MESSAGES = _descriptor.Descriptor( + name='Messages', + full_name='agrirouter.commons.Messages', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='messages', full_name='agrirouter.commons.Messages.messages', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=225, + serialized_end=282, +) + +_MESSAGE_ARGSENTRY.containing_type = _MESSAGE +_MESSAGE.fields_by_name['args'].message_type = _MESSAGE_ARGSENTRY +_MESSAGES.fields_by_name['messages'].message_type = _MESSAGE +DESCRIPTOR.message_types_by_name['Message'] = _MESSAGE +DESCRIPTOR.message_types_by_name['Metadata'] = _METADATA +DESCRIPTOR.message_types_by_name['Messages'] = _MESSAGES +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), { + + 'ArgsEntry' : _reflection.GeneratedProtocolMessageType('ArgsEntry', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGE_ARGSENTRY, + '__module__' : 'commons.message_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.commons.Message.ArgsEntry) + }) + , + 'DESCRIPTOR' : _MESSAGE, + '__module__' : 'commons.message_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.commons.Message) + }) +_sym_db.RegisterMessage(Message) +_sym_db.RegisterMessage(Message.ArgsEntry) + +Metadata = _reflection.GeneratedProtocolMessageType('Metadata', (_message.Message,), { + 'DESCRIPTOR' : _METADATA, + '__module__' : 'commons.message_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.commons.Metadata) + }) +_sym_db.RegisterMessage(Metadata) + +Messages = _reflection.GeneratedProtocolMessageType('Messages', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGES, + '__module__' : 'commons.message_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.commons.Messages) + }) +_sym_db.RegisterMessage(Messages) + + +_MESSAGE_ARGSENTRY._options = None +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/request/payload/account/endpoints_pb2.py b/agrirouter/generated/messaging/request/payload/account/endpoints_pb2.py new file mode 100644 index 00000000..15313f5d --- /dev/null +++ b/agrirouter/generated/messaging/request/payload/account/endpoints_pb2.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/request/payload/account/endpoints.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/request/payload/account/endpoints.proto', + package='agrirouter.request.payload.account', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n1messaging/request/payload/account/endpoints.proto\x12\"agrirouter.request.payload.account\"\xbf\x01\n\x12ListEndpointsQuery\x12\x1e\n\x16technical_message_type\x18\x01 \x01(\t\x12S\n\tdirection\x18\x02 \x01(\x0e\x32@.agrirouter.request.payload.account.ListEndpointsQuery.Direction\"4\n\tDirection\x12\x08\n\x04SEND\x10\x00\x12\x0b\n\x07RECEIVE\x10\x01\x12\x10\n\x0cSEND_RECEIVE\x10\x02\x62\x06proto3' +) + + + +_LISTENDPOINTSQUERY_DIRECTION = _descriptor.EnumDescriptor( + name='Direction', + full_name='agrirouter.request.payload.account.ListEndpointsQuery.Direction', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SEND', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RECEIVE', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SEND_RECEIVE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=229, + serialized_end=281, +) +_sym_db.RegisterEnumDescriptor(_LISTENDPOINTSQUERY_DIRECTION) + + +_LISTENDPOINTSQUERY = _descriptor.Descriptor( + name='ListEndpointsQuery', + full_name='agrirouter.request.payload.account.ListEndpointsQuery', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.request.payload.account.ListEndpointsQuery.technical_message_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='direction', full_name='agrirouter.request.payload.account.ListEndpointsQuery.direction', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _LISTENDPOINTSQUERY_DIRECTION, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=90, + serialized_end=281, +) + +_LISTENDPOINTSQUERY.fields_by_name['direction'].enum_type = _LISTENDPOINTSQUERY_DIRECTION +_LISTENDPOINTSQUERY_DIRECTION.containing_type = _LISTENDPOINTSQUERY +DESCRIPTOR.message_types_by_name['ListEndpointsQuery'] = _LISTENDPOINTSQUERY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ListEndpointsQuery = _reflection.GeneratedProtocolMessageType('ListEndpointsQuery', (_message.Message,), { + 'DESCRIPTOR' : _LISTENDPOINTSQUERY, + '__module__' : 'messaging.request.payload.account.endpoints_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.payload.account.ListEndpointsQuery) + }) +_sym_db.RegisterMessage(ListEndpointsQuery) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/request/payload/endpoint/capabilities_pb2.py b/agrirouter/generated/messaging/request/payload/endpoint/capabilities_pb2.py new file mode 100644 index 00000000..a31856ca --- /dev/null +++ b/agrirouter/generated/messaging/request/payload/endpoint/capabilities_pb2.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/request/payload/endpoint/capabilities.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/request/payload/endpoint/capabilities.proto', + package='agrirouter.request.payload.endpoint', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n5messaging/request/payload/endpoint/capabilities.proto\x12#agrirouter.request.payload.endpoint\"\xb9\x04\n\x17\x43\x61pabilitySpecification\x12]\n\x0c\x63\x61pabilities\x18\x01 \x03(\x0b\x32G.agrirouter.request.payload.endpoint.CapabilitySpecification.Capability\x12\x1c\n\x14\x61pp_certification_id\x18\x02 \x01(\t\x12$\n\x1c\x61pp_certification_version_id\x18\x03 \x01(\t\x12p\n\x19\x65nable_push_notifications\x18\x04 \x01(\x0e\x32M.agrirouter.request.payload.endpoint.CapabilitySpecification.PushNotification\x1a\x87\x01\n\nCapability\x12\x1e\n\x16technical_message_type\x18\x01 \x01(\t\x12Y\n\tdirection\x18\x02 \x01(\x0e\x32\x46.agrirouter.request.payload.endpoint.CapabilitySpecification.Direction\"4\n\tDirection\x12\x08\n\x04SEND\x10\x00\x12\x0b\n\x07RECEIVE\x10\x01\x12\x10\n\x0cSEND_RECEIVE\x10\x02\"I\n\x10PushNotification\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x1a\n\x16\x45NABLED_HIGH_FREQUENCY\x10\x02\x62\x06proto3' +) + + + +_CAPABILITYSPECIFICATION_DIRECTION = _descriptor.EnumDescriptor( + name='Direction', + full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.Direction', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SEND', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RECEIVE', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SEND_RECEIVE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=537, + serialized_end=589, +) +_sym_db.RegisterEnumDescriptor(_CAPABILITYSPECIFICATION_DIRECTION) + +_CAPABILITYSPECIFICATION_PUSHNOTIFICATION = _descriptor.EnumDescriptor( + name='PushNotification', + full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.PushNotification', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='DISABLED', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENABLED', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENABLED_HIGH_FREQUENCY', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=591, + serialized_end=664, +) +_sym_db.RegisterEnumDescriptor(_CAPABILITYSPECIFICATION_PUSHNOTIFICATION) + + +_CAPABILITYSPECIFICATION_CAPABILITY = _descriptor.Descriptor( + name='Capability', + full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.Capability', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.Capability.technical_message_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='direction', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.Capability.direction', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=400, + serialized_end=535, +) + +_CAPABILITYSPECIFICATION = _descriptor.Descriptor( + name='CapabilitySpecification', + full_name='agrirouter.request.payload.endpoint.CapabilitySpecification', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='capabilities', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.capabilities', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='app_certification_id', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.app_certification_id', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='app_certification_version_id', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.app_certification_version_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='enable_push_notifications', full_name='agrirouter.request.payload.endpoint.CapabilitySpecification.enable_push_notifications', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_CAPABILITYSPECIFICATION_CAPABILITY, ], + enum_types=[ + _CAPABILITYSPECIFICATION_DIRECTION, + _CAPABILITYSPECIFICATION_PUSHNOTIFICATION, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=95, + serialized_end=664, +) + +_CAPABILITYSPECIFICATION_CAPABILITY.fields_by_name['direction'].enum_type = _CAPABILITYSPECIFICATION_DIRECTION +_CAPABILITYSPECIFICATION_CAPABILITY.containing_type = _CAPABILITYSPECIFICATION +_CAPABILITYSPECIFICATION.fields_by_name['capabilities'].message_type = _CAPABILITYSPECIFICATION_CAPABILITY +_CAPABILITYSPECIFICATION.fields_by_name['enable_push_notifications'].enum_type = _CAPABILITYSPECIFICATION_PUSHNOTIFICATION +_CAPABILITYSPECIFICATION_DIRECTION.containing_type = _CAPABILITYSPECIFICATION +_CAPABILITYSPECIFICATION_PUSHNOTIFICATION.containing_type = _CAPABILITYSPECIFICATION +DESCRIPTOR.message_types_by_name['CapabilitySpecification'] = _CAPABILITYSPECIFICATION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CapabilitySpecification = _reflection.GeneratedProtocolMessageType('CapabilitySpecification', (_message.Message,), { + + 'Capability' : _reflection.GeneratedProtocolMessageType('Capability', (_message.Message,), { + 'DESCRIPTOR' : _CAPABILITYSPECIFICATION_CAPABILITY, + '__module__' : 'messaging.request.payload.endpoint.capabilities_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.payload.endpoint.CapabilitySpecification.Capability) + }) + , + 'DESCRIPTOR' : _CAPABILITYSPECIFICATION, + '__module__' : 'messaging.request.payload.endpoint.capabilities_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.payload.endpoint.CapabilitySpecification) + }) +_sym_db.RegisterMessage(CapabilitySpecification) +_sym_db.RegisterMessage(CapabilitySpecification.Capability) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/request/payload/endpoint/subscription_pb2.py b/agrirouter/generated/messaging/request/payload/endpoint/subscription_pb2.py new file mode 100644 index 00000000..fa456a6f --- /dev/null +++ b/agrirouter/generated/messaging/request/payload/endpoint/subscription_pb2.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/request/payload/endpoint/subscription.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/request/payload/endpoint/subscription.proto', + package='agrirouter.request.payload.endpoint', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n5messaging/request/payload/endpoint/subscription.proto\x12#agrirouter.request.payload.endpoint\"\xdd\x01\n\x0cSubscription\x12n\n\x17technical_message_types\x18\x01 \x03(\x0b\x32M.agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem\x1a]\n\x1bMessageTypeSubscriptionItem\x12\x1e\n\x16technical_message_type\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x64is\x18\x02 \x03(\r\x12\x10\n\x08position\x18\x03 \x01(\x08\x62\x06proto3' +) + + + + +_SUBSCRIPTION_MESSAGETYPESUBSCRIPTIONITEM = _descriptor.Descriptor( + name='MessageTypeSubscriptionItem', + full_name='agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem.technical_message_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ddis', full_name='agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem.ddis', index=1, + number=2, type=13, cpp_type=3, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='position', full_name='agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem.position', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=223, + serialized_end=316, +) + +_SUBSCRIPTION = _descriptor.Descriptor( + name='Subscription', + full_name='agrirouter.request.payload.endpoint.Subscription', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_types', full_name='agrirouter.request.payload.endpoint.Subscription.technical_message_types', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_SUBSCRIPTION_MESSAGETYPESUBSCRIPTIONITEM, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=95, + serialized_end=316, +) + +_SUBSCRIPTION_MESSAGETYPESUBSCRIPTIONITEM.containing_type = _SUBSCRIPTION +_SUBSCRIPTION.fields_by_name['technical_message_types'].message_type = _SUBSCRIPTION_MESSAGETYPESUBSCRIPTIONITEM +DESCRIPTOR.message_types_by_name['Subscription'] = _SUBSCRIPTION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Subscription = _reflection.GeneratedProtocolMessageType('Subscription', (_message.Message,), { + + 'MessageTypeSubscriptionItem' : _reflection.GeneratedProtocolMessageType('MessageTypeSubscriptionItem', (_message.Message,), { + 'DESCRIPTOR' : _SUBSCRIPTION_MESSAGETYPESUBSCRIPTIONITEM, + '__module__' : 'messaging.request.payload.endpoint.subscription_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.payload.endpoint.Subscription.MessageTypeSubscriptionItem) + }) + , + 'DESCRIPTOR' : _SUBSCRIPTION, + '__module__' : 'messaging.request.payload.endpoint.subscription_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.payload.endpoint.Subscription) + }) +_sym_db.RegisterMessage(Subscription) +_sym_db.RegisterMessage(Subscription.MessageTypeSubscriptionItem) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/request/payload/feed/feed_requests_pb2.py b/agrirouter/generated/messaging/request/payload/feed/feed_requests_pb2.py new file mode 100644 index 00000000..54c4b0df --- /dev/null +++ b/agrirouter/generated/messaging/request/payload/feed/feed_requests_pb2.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/request/payload/feed/feed-requests.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/request/payload/feed/feed-requests.proto', + package='agrirouter.feed.request', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n2messaging/request/payload/feed/feed-requests.proto\x12\x17\x61grirouter.feed.request\x1a\x1fgoogle/protobuf/timestamp.proto\"l\n\x0eValidityPeriod\x12-\n\tsent_from\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x07sent_to\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"v\n\x0cMessageQuery\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\x12\x0f\n\x07senders\x18\x02 \x03(\t\x12@\n\x0fvalidity_period\x18\x03 \x01(\x0b\x32\'.agrirouter.feed.request.ValidityPeriod\"%\n\x0eMessageConfirm\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\"w\n\rMessageDelete\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\x12\x0f\n\x07senders\x18\x02 \x03(\t\x12@\n\x0fvalidity_period\x18\x03 \x01(\x0b\x32\'.agrirouter.feed.request.ValidityPeriodb\x06proto3' + , + dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) + + + + +_VALIDITYPERIOD = _descriptor.Descriptor( + name='ValidityPeriod', + full_name='agrirouter.feed.request.ValidityPeriod', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='sent_from', full_name='agrirouter.feed.request.ValidityPeriod.sent_from', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sent_to', full_name='agrirouter.feed.request.ValidityPeriod.sent_to', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=112, + serialized_end=220, +) + + +_MESSAGEQUERY = _descriptor.Descriptor( + name='MessageQuery', + full_name='agrirouter.feed.request.MessageQuery', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message_ids', full_name='agrirouter.feed.request.MessageQuery.message_ids', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='senders', full_name='agrirouter.feed.request.MessageQuery.senders', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='validity_period', full_name='agrirouter.feed.request.MessageQuery.validity_period', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=222, + serialized_end=340, +) + + +_MESSAGECONFIRM = _descriptor.Descriptor( + name='MessageConfirm', + full_name='agrirouter.feed.request.MessageConfirm', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message_ids', full_name='agrirouter.feed.request.MessageConfirm.message_ids', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=342, + serialized_end=379, +) + + +_MESSAGEDELETE = _descriptor.Descriptor( + name='MessageDelete', + full_name='agrirouter.feed.request.MessageDelete', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message_ids', full_name='agrirouter.feed.request.MessageDelete.message_ids', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='senders', full_name='agrirouter.feed.request.MessageDelete.senders', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='validity_period', full_name='agrirouter.feed.request.MessageDelete.validity_period', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=381, + serialized_end=500, +) + +_VALIDITYPERIOD.fields_by_name['sent_from'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_VALIDITYPERIOD.fields_by_name['sent_to'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_MESSAGEQUERY.fields_by_name['validity_period'].message_type = _VALIDITYPERIOD +_MESSAGEDELETE.fields_by_name['validity_period'].message_type = _VALIDITYPERIOD +DESCRIPTOR.message_types_by_name['ValidityPeriod'] = _VALIDITYPERIOD +DESCRIPTOR.message_types_by_name['MessageQuery'] = _MESSAGEQUERY +DESCRIPTOR.message_types_by_name['MessageConfirm'] = _MESSAGECONFIRM +DESCRIPTOR.message_types_by_name['MessageDelete'] = _MESSAGEDELETE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ValidityPeriod = _reflection.GeneratedProtocolMessageType('ValidityPeriod', (_message.Message,), { + 'DESCRIPTOR' : _VALIDITYPERIOD, + '__module__' : 'messaging.request.payload.feed.feed_requests_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.request.ValidityPeriod) + }) +_sym_db.RegisterMessage(ValidityPeriod) + +MessageQuery = _reflection.GeneratedProtocolMessageType('MessageQuery', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGEQUERY, + '__module__' : 'messaging.request.payload.feed.feed_requests_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.request.MessageQuery) + }) +_sym_db.RegisterMessage(MessageQuery) + +MessageConfirm = _reflection.GeneratedProtocolMessageType('MessageConfirm', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGECONFIRM, + '__module__' : 'messaging.request.payload.feed.feed_requests_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.request.MessageConfirm) + }) +_sym_db.RegisterMessage(MessageConfirm) + +MessageDelete = _reflection.GeneratedProtocolMessageType('MessageDelete', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGEDELETE, + '__module__' : 'messaging.request.payload.feed.feed_requests_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.request.MessageDelete) + }) +_sym_db.RegisterMessage(MessageDelete) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/request/request_pb2.py b/agrirouter/generated/messaging/request/request_pb2.py new file mode 100644 index 00000000..b193bf57 --- /dev/null +++ b/agrirouter/generated/messaging/request/request_pb2.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/request/request.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from agrirouter.generated.commons import chunk_pb2 as commons_dot_chunk__pb2 +from agrirouter.generated.commons import message_pb2 as commons_dot_message__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/request/request.proto', + package='agrirouter.request', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x1fmessaging/request/request.proto\x12\x12\x61grirouter.request\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13\x63ommons/chunk.proto\x1a\x15\x63ommons/message.proto\"\xaf\x03\n\x0fRequestEnvelope\x12\x1e\n\x16\x61pplication_message_id\x18\x01 \x01(\t\x12\"\n\x1a\x61pplication_message_seq_no\x18\x02 \x01(\x03\x12\x1e\n\x16technical_message_type\x18\x03 \x01(\t\x12\x1b\n\x13team_set_context_id\x18\x04 \x01(\t\x12\x36\n\x04mode\x18\x05 \x01(\x0e\x32(.agrirouter.request.RequestEnvelope.Mode\x12\x12\n\nrecipients\x18\x06 \x03(\t\x12\x36\n\nchunk_info\x18\x07 \x01(\x0b\x32\".agrirouter.commons.ChunkComponent\x12-\n\ttimestamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x08metadata\x18\t \x01(\x0b\x32\x1c.agrirouter.commons.Metadata\"8\n\x04Mode\x12\n\n\x06\x44IRECT\x10\x00\x12\x0b\n\x07PUBLISH\x10\x01\x12\x17\n\x13PUBLISH_WITH_DIRECT\x10\x02\">\n\x15RequestPayloadWrapper\x12%\n\x07\x64\x65tails\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Anyb\x06proto3' + , + dependencies=[google_dot_protobuf_dot_any__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,commons_dot_chunk__pb2.DESCRIPTOR,commons_dot_message__pb2.DESCRIPTOR,]) + + + +_REQUESTENVELOPE_MODE = _descriptor.EnumDescriptor( + name='Mode', + full_name='agrirouter.request.RequestEnvelope.Mode', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='DIRECT', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PUBLISH', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PUBLISH_WITH_DIRECT', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=535, + serialized_end=591, +) +_sym_db.RegisterEnumDescriptor(_REQUESTENVELOPE_MODE) + + +_REQUESTENVELOPE = _descriptor.Descriptor( + name='RequestEnvelope', + full_name='agrirouter.request.RequestEnvelope', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='application_message_id', full_name='agrirouter.request.RequestEnvelope.application_message_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='application_message_seq_no', full_name='agrirouter.request.RequestEnvelope.application_message_seq_no', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.request.RequestEnvelope.technical_message_type', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='team_set_context_id', full_name='agrirouter.request.RequestEnvelope.team_set_context_id', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='mode', full_name='agrirouter.request.RequestEnvelope.mode', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='recipients', full_name='agrirouter.request.RequestEnvelope.recipients', index=5, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='chunk_info', full_name='agrirouter.request.RequestEnvelope.chunk_info', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='timestamp', full_name='agrirouter.request.RequestEnvelope.timestamp', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='metadata', full_name='agrirouter.request.RequestEnvelope.metadata', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _REQUESTENVELOPE_MODE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=160, + serialized_end=591, +) + + +_REQUESTPAYLOADWRAPPER = _descriptor.Descriptor( + name='RequestPayloadWrapper', + full_name='agrirouter.request.RequestPayloadWrapper', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='details', full_name='agrirouter.request.RequestPayloadWrapper.details', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=593, + serialized_end=655, +) + +_REQUESTENVELOPE.fields_by_name['mode'].enum_type = _REQUESTENVELOPE_MODE +_REQUESTENVELOPE.fields_by_name['chunk_info'].message_type = commons_dot_chunk__pb2._CHUNKCOMPONENT +_REQUESTENVELOPE.fields_by_name['timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_REQUESTENVELOPE.fields_by_name['metadata'].message_type = commons_dot_message__pb2._METADATA +_REQUESTENVELOPE_MODE.containing_type = _REQUESTENVELOPE +_REQUESTPAYLOADWRAPPER.fields_by_name['details'].message_type = google_dot_protobuf_dot_any__pb2._ANY +DESCRIPTOR.message_types_by_name['RequestEnvelope'] = _REQUESTENVELOPE +DESCRIPTOR.message_types_by_name['RequestPayloadWrapper'] = _REQUESTPAYLOADWRAPPER +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +RequestEnvelope = _reflection.GeneratedProtocolMessageType('RequestEnvelope', (_message.Message,), { + 'DESCRIPTOR' : _REQUESTENVELOPE, + '__module__' : 'messaging.request.request_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.RequestEnvelope) + }) +_sym_db.RegisterMessage(RequestEnvelope) + +RequestPayloadWrapper = _reflection.GeneratedProtocolMessageType('RequestPayloadWrapper', (_message.Message,), { + 'DESCRIPTOR' : _REQUESTPAYLOADWRAPPER, + '__module__' : 'messaging.request.request_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.request.RequestPayloadWrapper) + }) +_sym_db.RegisterMessage(RequestPayloadWrapper) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/response/payload/account/endpoints_pb2.py b/agrirouter/generated/messaging/response/payload/account/endpoints_pb2.py new file mode 100644 index 00000000..d0f870b5 --- /dev/null +++ b/agrirouter/generated/messaging/response/payload/account/endpoints_pb2.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/response/payload/account/endpoints.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/response/payload/account/endpoints.proto', + package='agrirouter.response.payload.account', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n2messaging/response/payload/account/endpoints.proto\x12#agrirouter.response.payload.account\"\x82\x04\n\x15ListEndpointsResponse\x12V\n\tendpoints\x18\x01 \x03(\x0b\x32\x43.agrirouter.response.payload.account.ListEndpointsResponse.Endpoint\x1a\x86\x01\n\x0bMessageType\x12\x1e\n\x16technical_message_type\x18\x01 \x01(\t\x12W\n\tdirection\x18\x02 \x01(\x0e\x32\x44.agrirouter.response.payload.account.ListEndpointsResponse.Direction\x1a\xd1\x01\n\x08\x45ndpoint\x12\x13\n\x0b\x65ndpoint_id\x18\x01 \x01(\t\x12\x15\n\rendpoint_name\x18\x02 \x01(\t\x12\x15\n\rendpoint_type\x18\x03 \x01(\t\x12\x0e\n\x06status\x18\x04 \x01(\t\x12]\n\rmessage_types\x18\x05 \x03(\x0b\x32\x46.agrirouter.response.payload.account.ListEndpointsResponse.MessageType\x12\x13\n\x0b\x65xternal_id\x18\x06 \x01(\t\"4\n\tDirection\x12\x08\n\x04SEND\x10\x00\x12\x0b\n\x07RECEIVE\x10\x01\x12\x10\n\x0cSEND_RECEIVE\x10\x02\x62\x06proto3' +) + + + +_LISTENDPOINTSRESPONSE_DIRECTION = _descriptor.EnumDescriptor( + name='Direction', + full_name='agrirouter.response.payload.account.ListEndpointsResponse.Direction', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SEND', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RECEIVE', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SEND_RECEIVE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=554, + serialized_end=606, +) +_sym_db.RegisterEnumDescriptor(_LISTENDPOINTSRESPONSE_DIRECTION) + + +_LISTENDPOINTSRESPONSE_MESSAGETYPE = _descriptor.Descriptor( + name='MessageType', + full_name='agrirouter.response.payload.account.ListEndpointsResponse.MessageType', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.response.payload.account.ListEndpointsResponse.MessageType.technical_message_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='direction', full_name='agrirouter.response.payload.account.ListEndpointsResponse.MessageType.direction', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=206, + serialized_end=340, +) + +_LISTENDPOINTSRESPONSE_ENDPOINT = _descriptor.Descriptor( + name='Endpoint', + full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='endpoint_id', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.endpoint_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='endpoint_name', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.endpoint_name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='endpoint_type', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.endpoint_type', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='status', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.status', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_types', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.message_types', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='external_id', full_name='agrirouter.response.payload.account.ListEndpointsResponse.Endpoint.external_id', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=343, + serialized_end=552, +) + +_LISTENDPOINTSRESPONSE = _descriptor.Descriptor( + name='ListEndpointsResponse', + full_name='agrirouter.response.payload.account.ListEndpointsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='endpoints', full_name='agrirouter.response.payload.account.ListEndpointsResponse.endpoints', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_LISTENDPOINTSRESPONSE_MESSAGETYPE, _LISTENDPOINTSRESPONSE_ENDPOINT, ], + enum_types=[ + _LISTENDPOINTSRESPONSE_DIRECTION, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=92, + serialized_end=606, +) + +_LISTENDPOINTSRESPONSE_MESSAGETYPE.fields_by_name['direction'].enum_type = _LISTENDPOINTSRESPONSE_DIRECTION +_LISTENDPOINTSRESPONSE_MESSAGETYPE.containing_type = _LISTENDPOINTSRESPONSE +_LISTENDPOINTSRESPONSE_ENDPOINT.fields_by_name['message_types'].message_type = _LISTENDPOINTSRESPONSE_MESSAGETYPE +_LISTENDPOINTSRESPONSE_ENDPOINT.containing_type = _LISTENDPOINTSRESPONSE +_LISTENDPOINTSRESPONSE.fields_by_name['endpoints'].message_type = _LISTENDPOINTSRESPONSE_ENDPOINT +_LISTENDPOINTSRESPONSE_DIRECTION.containing_type = _LISTENDPOINTSRESPONSE +DESCRIPTOR.message_types_by_name['ListEndpointsResponse'] = _LISTENDPOINTSRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ListEndpointsResponse = _reflection.GeneratedProtocolMessageType('ListEndpointsResponse', (_message.Message,), { + + 'MessageType' : _reflection.GeneratedProtocolMessageType('MessageType', (_message.Message,), { + 'DESCRIPTOR' : _LISTENDPOINTSRESPONSE_MESSAGETYPE, + '__module__' : 'messaging.response.payload.account.endpoints_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.payload.account.ListEndpointsResponse.MessageType) + }) + , + + 'Endpoint' : _reflection.GeneratedProtocolMessageType('Endpoint', (_message.Message,), { + 'DESCRIPTOR' : _LISTENDPOINTSRESPONSE_ENDPOINT, + '__module__' : 'messaging.response.payload.account.endpoints_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.payload.account.ListEndpointsResponse.Endpoint) + }) + , + 'DESCRIPTOR' : _LISTENDPOINTSRESPONSE, + '__module__' : 'messaging.response.payload.account.endpoints_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.payload.account.ListEndpointsResponse) + }) +_sym_db.RegisterMessage(ListEndpointsResponse) +_sym_db.RegisterMessage(ListEndpointsResponse.MessageType) +_sym_db.RegisterMessage(ListEndpointsResponse.Endpoint) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/response/payload/endpoint/capability_pb2.py b/agrirouter/generated/messaging/response/payload/endpoint/capability_pb2.py new file mode 100644 index 00000000..cea65312 --- /dev/null +++ b/agrirouter/generated/messaging/response/payload/endpoint/capability_pb2.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/response/payload/endpoint/capability.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/response/payload/endpoint/capability.proto', + package='agrirouter.response.payload.endpoint', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n4messaging/response/payload/endpoint/capability.proto\x12$agrirouter.response.payload.endpoint\"\xc5\x01\n\x12\x43\x61pabilityResponse\x12\x1e\n\x16\x61pplication_message_id\x18\x01 \x01(\t\x12V\n\nrecipients\x18\x02 \x03(\x0b\x32\x42.agrirouter.response.payload.endpoint.CapabilityResponse.Recipient\x1a\x37\n\tRecipient\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1e\n\x16technical_message_type\x18\x02 \x03(\tb\x06proto3' +) + + + + +_CAPABILITYRESPONSE_RECIPIENT = _descriptor.Descriptor( + name='Recipient', + full_name='agrirouter.response.payload.endpoint.CapabilityResponse.Recipient', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='agrirouter.response.payload.endpoint.CapabilityResponse.Recipient.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.response.payload.endpoint.CapabilityResponse.Recipient.technical_message_type', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=237, + serialized_end=292, +) + +_CAPABILITYRESPONSE = _descriptor.Descriptor( + name='CapabilityResponse', + full_name='agrirouter.response.payload.endpoint.CapabilityResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='application_message_id', full_name='agrirouter.response.payload.endpoint.CapabilityResponse.application_message_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='recipients', full_name='agrirouter.response.payload.endpoint.CapabilityResponse.recipients', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_CAPABILITYRESPONSE_RECIPIENT, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=95, + serialized_end=292, +) + +_CAPABILITYRESPONSE_RECIPIENT.containing_type = _CAPABILITYRESPONSE +_CAPABILITYRESPONSE.fields_by_name['recipients'].message_type = _CAPABILITYRESPONSE_RECIPIENT +DESCRIPTOR.message_types_by_name['CapabilityResponse'] = _CAPABILITYRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CapabilityResponse = _reflection.GeneratedProtocolMessageType('CapabilityResponse', (_message.Message,), { + + 'Recipient' : _reflection.GeneratedProtocolMessageType('Recipient', (_message.Message,), { + 'DESCRIPTOR' : _CAPABILITYRESPONSE_RECIPIENT, + '__module__' : 'messaging.response.payload.endpoint.capability_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.payload.endpoint.CapabilityResponse.Recipient) + }) + , + 'DESCRIPTOR' : _CAPABILITYRESPONSE, + '__module__' : 'messaging.response.payload.endpoint.capability_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.payload.endpoint.CapabilityResponse) + }) +_sym_db.RegisterMessage(CapabilityResponse) +_sym_db.RegisterMessage(CapabilityResponse.Recipient) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/response/payload/feed/feed_response_pb2.py b/agrirouter/generated/messaging/response/payload/feed/feed_response_pb2.py new file mode 100644 index 00000000..a193a2ee --- /dev/null +++ b/agrirouter/generated/messaging/response/payload/feed/feed_response_pb2.py @@ -0,0 +1,710 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/response/payload/feed/feed-response.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from agrirouter.generated.commons import message_pb2 as commons_dot_message__pb2 +from agrirouter.generated.commons import chunk_pb2 as commons_dot_chunk__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/response/payload/feed/feed-response.proto', + package='agrirouter.feed.response', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n3messaging/response/payload/feed/feed-response.proto\x12\x18\x61grirouter.feed.response\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\x1a\x15\x63ommons/message.proto\x1a\x13\x63ommons/chunk.proto\"%\n\x04Page\x12\x0e\n\x06number\x18\x01 \x01(\x05\x12\r\n\x05total\x18\x02 \x01(\x05\"N\n\x0cQueryMetrics\x12\x1f\n\x17total_messages_in_query\x18\x01 \x01(\x05\x12\x1d\n\x15max_count_restriction\x18\x02 \x01(\x05\"\xe8\x05\n\x13HeaderQueryResponse\x12=\n\rquery_metrics\x18\x01 \x01(\x0b\x32&.agrirouter.feed.response.QueryMetrics\x12,\n\x04page\x18\x02 \x01(\x0b\x32\x1e.agrirouter.feed.response.Page\x12:\n\x0e\x63hunk_contexts\x18\x03 \x03(\x0b\x32\".agrirouter.commons.ChunkComponent\x12@\n\x04\x66\x65\x65\x64\x18\x04 \x03(\x0b\x32\x32.agrirouter.feed.response.HeaderQueryResponse.Feed\x12\x1f\n\x13pending_message_ids\x18\x05 \x03(\tB\x02\x18\x01\x1a\xcd\x02\n\x06Header\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x1e\n\x16technical_message_type\x18\x02 \x01(\t\x12\x1b\n\x13team_set_context_id\x18\x03 \x01(\t\x12\x18\n\x10\x63hunk_context_id\x18\x04 \x01(\t\x12\x14\n\x0cpayload_size\x18\x05 \x01(\x03\x12\x32\n\x0esent_timestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\x0fsequence_number\x18\x07 \x01(\x03\x12\x15\n\rcurrent_chunk\x18\x08 \x01(\x03\x12.\n\ncreated_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x08metadata\x18\n \x01(\x0b\x32\x1c.agrirouter.commons.Metadata\x1au\n\x04\x46\x65\x65\x64\x12\x11\n\tsender_id\x18\x01 \x01(\t\x12\x13\n\x0breceiver_id\x18\x02 \x01(\t\x12\x45\n\x07headers\x18\x03 \x03(\x0b\x32\x34.agrirouter.feed.response.HeaderQueryResponse.Header\"\xd0\x05\n\x14MessageQueryResponse\x12=\n\rquery_metrics\x18\x01 \x01(\x0b\x32&.agrirouter.feed.response.QueryMetrics\x12,\n\x04page\x18\x02 \x01(\x0b\x32\x1e.agrirouter.feed.response.Page\x12L\n\x08messages\x18\x03 \x03(\x0b\x32:.agrirouter.feed.response.MessageQueryResponse.FeedMessage\x1a\xff\x02\n\x06Header\x12\x13\n\x0breceiver_id\x18\x01 \x01(\t\x12\x1e\n\x16technical_message_type\x18\x02 \x01(\t\x12\x1b\n\x13team_set_context_id\x18\x03 \x01(\t\x12\x39\n\rchunk_context\x18\x04 \x01(\x0b\x32\".agrirouter.commons.ChunkComponent\x12\x14\n\x0cpayload_size\x18\x05 \x01(\x03\x12\x32\n\x0esent_timestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\x0fsequence_number\x18\x07 \x01(\x03\x12\x11\n\tsender_id\x18\x08 \x01(\t\x12.\n\ncreated_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x12\n\nmessage_id\x18\n \x01(\t\x12.\n\x08metadata\x18\x0b \x01(\x0b\x32\x1c.agrirouter.commons.Metadata\x1a{\n\x0b\x46\x65\x65\x64Message\x12\x45\n\x06header\x18\x01 \x01(\x0b\x32\x35.agrirouter.feed.response.MessageQueryResponse.Header\x12%\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\"\x96\x03\n\x1a\x46\x61iledMessageQueryResponse\x12=\n\rquery_metrics\x18\x01 \x01(\x0b\x32&.agrirouter.feed.response.QueryMetrics\x12,\n\x04page\x18\x02 \x01(\x0b\x32\x1e.agrirouter.feed.response.Page\x12K\n\x06header\x18\x03 \x01(\x0b\x32;.agrirouter.feed.response.FailedMessageQueryResponse.Header\x12,\n\x07reasons\x18\x04 \x03(\x0b\x32\x1b.agrirouter.commons.Message\x1a\x8f\x01\n\x06Header\x12\x1e\n\x16technical_message_type\x18\x01 \x01(\t\x12\x1b\n\x13team_set_context_id\x18\x02 \x01(\t\x12\x14\n\x0cpayload_size\x18\x03 \x01(\x03\x12\x32\n\x0esent_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestampb\x06proto3' + , + dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,google_dot_protobuf_dot_any__pb2.DESCRIPTOR,commons_dot_message__pb2.DESCRIPTOR,commons_dot_chunk__pb2.DESCRIPTOR,]) + + + + +_PAGE = _descriptor.Descriptor( + name='Page', + full_name='agrirouter.feed.response.Page', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='number', full_name='agrirouter.feed.response.Page.number', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='total', full_name='agrirouter.feed.response.Page.total', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=185, + serialized_end=222, +) + + +_QUERYMETRICS = _descriptor.Descriptor( + name='QueryMetrics', + full_name='agrirouter.feed.response.QueryMetrics', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='total_messages_in_query', full_name='agrirouter.feed.response.QueryMetrics.total_messages_in_query', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_count_restriction', full_name='agrirouter.feed.response.QueryMetrics.max_count_restriction', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=224, + serialized_end=302, +) + + +_HEADERQUERYRESPONSE_HEADER = _descriptor.Descriptor( + name='Header', + full_name='agrirouter.feed.response.HeaderQueryResponse.Header', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='message_id', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.message_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.technical_message_type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='team_set_context_id', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.team_set_context_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='chunk_context_id', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.chunk_context_id', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='payload_size', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.payload_size', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sent_timestamp', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.sent_timestamp', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sequence_number', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.sequence_number', index=6, + number=7, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='current_chunk', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.current_chunk', index=7, + number=8, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='created_at', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.created_at', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='metadata', full_name='agrirouter.feed.response.HeaderQueryResponse.Header.metadata', index=9, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=597, + serialized_end=930, +) + +_HEADERQUERYRESPONSE_FEED = _descriptor.Descriptor( + name='Feed', + full_name='agrirouter.feed.response.HeaderQueryResponse.Feed', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='sender_id', full_name='agrirouter.feed.response.HeaderQueryResponse.Feed.sender_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='receiver_id', full_name='agrirouter.feed.response.HeaderQueryResponse.Feed.receiver_id', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='headers', full_name='agrirouter.feed.response.HeaderQueryResponse.Feed.headers', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=932, + serialized_end=1049, +) + +_HEADERQUERYRESPONSE = _descriptor.Descriptor( + name='HeaderQueryResponse', + full_name='agrirouter.feed.response.HeaderQueryResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='query_metrics', full_name='agrirouter.feed.response.HeaderQueryResponse.query_metrics', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='page', full_name='agrirouter.feed.response.HeaderQueryResponse.page', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='chunk_contexts', full_name='agrirouter.feed.response.HeaderQueryResponse.chunk_contexts', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='feed', full_name='agrirouter.feed.response.HeaderQueryResponse.feed', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='pending_message_ids', full_name='agrirouter.feed.response.HeaderQueryResponse.pending_message_ids', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=b'\030\001', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_HEADERQUERYRESPONSE_HEADER, _HEADERQUERYRESPONSE_FEED, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=305, + serialized_end=1049, +) + + +_MESSAGEQUERYRESPONSE_HEADER = _descriptor.Descriptor( + name='Header', + full_name='agrirouter.feed.response.MessageQueryResponse.Header', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='receiver_id', full_name='agrirouter.feed.response.MessageQueryResponse.Header.receiver_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.feed.response.MessageQueryResponse.Header.technical_message_type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='team_set_context_id', full_name='agrirouter.feed.response.MessageQueryResponse.Header.team_set_context_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='chunk_context', full_name='agrirouter.feed.response.MessageQueryResponse.Header.chunk_context', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='payload_size', full_name='agrirouter.feed.response.MessageQueryResponse.Header.payload_size', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sent_timestamp', full_name='agrirouter.feed.response.MessageQueryResponse.Header.sent_timestamp', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sequence_number', full_name='agrirouter.feed.response.MessageQueryResponse.Header.sequence_number', index=6, + number=7, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sender_id', full_name='agrirouter.feed.response.MessageQueryResponse.Header.sender_id', index=7, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='created_at', full_name='agrirouter.feed.response.MessageQueryResponse.Header.created_at', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_id', full_name='agrirouter.feed.response.MessageQueryResponse.Header.message_id', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='metadata', full_name='agrirouter.feed.response.MessageQueryResponse.Header.metadata', index=10, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1264, + serialized_end=1647, +) + +_MESSAGEQUERYRESPONSE_FEEDMESSAGE = _descriptor.Descriptor( + name='FeedMessage', + full_name='agrirouter.feed.response.MessageQueryResponse.FeedMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='header', full_name='agrirouter.feed.response.MessageQueryResponse.FeedMessage.header', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='content', full_name='agrirouter.feed.response.MessageQueryResponse.FeedMessage.content', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1649, + serialized_end=1772, +) + +_MESSAGEQUERYRESPONSE = _descriptor.Descriptor( + name='MessageQueryResponse', + full_name='agrirouter.feed.response.MessageQueryResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='query_metrics', full_name='agrirouter.feed.response.MessageQueryResponse.query_metrics', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='page', full_name='agrirouter.feed.response.MessageQueryResponse.page', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='messages', full_name='agrirouter.feed.response.MessageQueryResponse.messages', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_MESSAGEQUERYRESPONSE_HEADER, _MESSAGEQUERYRESPONSE_FEEDMESSAGE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1052, + serialized_end=1772, +) + + +_FAILEDMESSAGEQUERYRESPONSE_HEADER = _descriptor.Descriptor( + name='Header', + full_name='agrirouter.feed.response.FailedMessageQueryResponse.Header', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.feed.response.FailedMessageQueryResponse.Header.technical_message_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='team_set_context_id', full_name='agrirouter.feed.response.FailedMessageQueryResponse.Header.team_set_context_id', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='payload_size', full_name='agrirouter.feed.response.FailedMessageQueryResponse.Header.payload_size', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sent_timestamp', full_name='agrirouter.feed.response.FailedMessageQueryResponse.Header.sent_timestamp', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2038, + serialized_end=2181, +) + +_FAILEDMESSAGEQUERYRESPONSE = _descriptor.Descriptor( + name='FailedMessageQueryResponse', + full_name='agrirouter.feed.response.FailedMessageQueryResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='query_metrics', full_name='agrirouter.feed.response.FailedMessageQueryResponse.query_metrics', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='page', full_name='agrirouter.feed.response.FailedMessageQueryResponse.page', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='header', full_name='agrirouter.feed.response.FailedMessageQueryResponse.header', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reasons', full_name='agrirouter.feed.response.FailedMessageQueryResponse.reasons', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_FAILEDMESSAGEQUERYRESPONSE_HEADER, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1775, + serialized_end=2181, +) + +_HEADERQUERYRESPONSE_HEADER.fields_by_name['sent_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_HEADERQUERYRESPONSE_HEADER.fields_by_name['created_at'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_HEADERQUERYRESPONSE_HEADER.fields_by_name['metadata'].message_type = commons_dot_message__pb2._METADATA +_HEADERQUERYRESPONSE_HEADER.containing_type = _HEADERQUERYRESPONSE +_HEADERQUERYRESPONSE_FEED.fields_by_name['headers'].message_type = _HEADERQUERYRESPONSE_HEADER +_HEADERQUERYRESPONSE_FEED.containing_type = _HEADERQUERYRESPONSE +_HEADERQUERYRESPONSE.fields_by_name['query_metrics'].message_type = _QUERYMETRICS +_HEADERQUERYRESPONSE.fields_by_name['page'].message_type = _PAGE +_HEADERQUERYRESPONSE.fields_by_name['chunk_contexts'].message_type = commons_dot_chunk__pb2._CHUNKCOMPONENT +_HEADERQUERYRESPONSE.fields_by_name['feed'].message_type = _HEADERQUERYRESPONSE_FEED +_MESSAGEQUERYRESPONSE_HEADER.fields_by_name['chunk_context'].message_type = commons_dot_chunk__pb2._CHUNKCOMPONENT +_MESSAGEQUERYRESPONSE_HEADER.fields_by_name['sent_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_MESSAGEQUERYRESPONSE_HEADER.fields_by_name['created_at'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_MESSAGEQUERYRESPONSE_HEADER.fields_by_name['metadata'].message_type = commons_dot_message__pb2._METADATA +_MESSAGEQUERYRESPONSE_HEADER.containing_type = _MESSAGEQUERYRESPONSE +_MESSAGEQUERYRESPONSE_FEEDMESSAGE.fields_by_name['header'].message_type = _MESSAGEQUERYRESPONSE_HEADER +_MESSAGEQUERYRESPONSE_FEEDMESSAGE.fields_by_name['content'].message_type = google_dot_protobuf_dot_any__pb2._ANY +_MESSAGEQUERYRESPONSE_FEEDMESSAGE.containing_type = _MESSAGEQUERYRESPONSE +_MESSAGEQUERYRESPONSE.fields_by_name['query_metrics'].message_type = _QUERYMETRICS +_MESSAGEQUERYRESPONSE.fields_by_name['page'].message_type = _PAGE +_MESSAGEQUERYRESPONSE.fields_by_name['messages'].message_type = _MESSAGEQUERYRESPONSE_FEEDMESSAGE +_FAILEDMESSAGEQUERYRESPONSE_HEADER.fields_by_name['sent_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_FAILEDMESSAGEQUERYRESPONSE_HEADER.containing_type = _FAILEDMESSAGEQUERYRESPONSE +_FAILEDMESSAGEQUERYRESPONSE.fields_by_name['query_metrics'].message_type = _QUERYMETRICS +_FAILEDMESSAGEQUERYRESPONSE.fields_by_name['page'].message_type = _PAGE +_FAILEDMESSAGEQUERYRESPONSE.fields_by_name['header'].message_type = _FAILEDMESSAGEQUERYRESPONSE_HEADER +_FAILEDMESSAGEQUERYRESPONSE.fields_by_name['reasons'].message_type = commons_dot_message__pb2._MESSAGE +DESCRIPTOR.message_types_by_name['Page'] = _PAGE +DESCRIPTOR.message_types_by_name['QueryMetrics'] = _QUERYMETRICS +DESCRIPTOR.message_types_by_name['HeaderQueryResponse'] = _HEADERQUERYRESPONSE +DESCRIPTOR.message_types_by_name['MessageQueryResponse'] = _MESSAGEQUERYRESPONSE +DESCRIPTOR.message_types_by_name['FailedMessageQueryResponse'] = _FAILEDMESSAGEQUERYRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Page = _reflection.GeneratedProtocolMessageType('Page', (_message.Message,), { + 'DESCRIPTOR' : _PAGE, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.Page) + }) +_sym_db.RegisterMessage(Page) + +QueryMetrics = _reflection.GeneratedProtocolMessageType('QueryMetrics', (_message.Message,), { + 'DESCRIPTOR' : _QUERYMETRICS, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.QueryMetrics) + }) +_sym_db.RegisterMessage(QueryMetrics) + +HeaderQueryResponse = _reflection.GeneratedProtocolMessageType('HeaderQueryResponse', (_message.Message,), { + + 'Header' : _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), { + 'DESCRIPTOR' : _HEADERQUERYRESPONSE_HEADER, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.HeaderQueryResponse.Header) + }) + , + + 'Feed' : _reflection.GeneratedProtocolMessageType('Feed', (_message.Message,), { + 'DESCRIPTOR' : _HEADERQUERYRESPONSE_FEED, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.HeaderQueryResponse.Feed) + }) + , + 'DESCRIPTOR' : _HEADERQUERYRESPONSE, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.HeaderQueryResponse) + }) +_sym_db.RegisterMessage(HeaderQueryResponse) +_sym_db.RegisterMessage(HeaderQueryResponse.Header) +_sym_db.RegisterMessage(HeaderQueryResponse.Feed) + +MessageQueryResponse = _reflection.GeneratedProtocolMessageType('MessageQueryResponse', (_message.Message,), { + + 'Header' : _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGEQUERYRESPONSE_HEADER, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.MessageQueryResponse.Header) + }) + , + + 'FeedMessage' : _reflection.GeneratedProtocolMessageType('FeedMessage', (_message.Message,), { + 'DESCRIPTOR' : _MESSAGEQUERYRESPONSE_FEEDMESSAGE, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.MessageQueryResponse.FeedMessage) + }) + , + 'DESCRIPTOR' : _MESSAGEQUERYRESPONSE, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.MessageQueryResponse) + }) +_sym_db.RegisterMessage(MessageQueryResponse) +_sym_db.RegisterMessage(MessageQueryResponse.Header) +_sym_db.RegisterMessage(MessageQueryResponse.FeedMessage) + +FailedMessageQueryResponse = _reflection.GeneratedProtocolMessageType('FailedMessageQueryResponse', (_message.Message,), { + + 'Header' : _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), { + 'DESCRIPTOR' : _FAILEDMESSAGEQUERYRESPONSE_HEADER, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.FailedMessageQueryResponse.Header) + }) + , + 'DESCRIPTOR' : _FAILEDMESSAGEQUERYRESPONSE, + '__module__' : 'messaging.response.payload.feed.feed_response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.response.FailedMessageQueryResponse) + }) +_sym_db.RegisterMessage(FailedMessageQueryResponse) +_sym_db.RegisterMessage(FailedMessageQueryResponse.Header) + + +_HEADERQUERYRESPONSE.fields_by_name['pending_message_ids']._options = None +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/response/payload/feed/push_notification_pb2.py b/agrirouter/generated/messaging/response/payload/feed/push_notification_pb2.py new file mode 100644 index 00000000..2cc8a8f0 --- /dev/null +++ b/agrirouter/generated/messaging/response/payload/feed/push_notification_pb2.py @@ -0,0 +1,239 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/response/payload/feed/push-notification.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from agrirouter.generated.commons import message_pb2 as commons_dot_message__pb2 +from agrirouter.generated.commons import chunk_pb2 as commons_dot_chunk__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/response/payload/feed/push-notification.proto', + package='agrirouter.feed.push.notification', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n7messaging/response/payload/feed/push-notification.proto\x12!agrirouter.feed.push.notification\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\x1a\x15\x63ommons/message.proto\x1a\x13\x63ommons/chunk.proto\"\xea\x04\n\x10PushNotification\x12Q\n\x08messages\x18\x01 \x03(\x0b\x32?.agrirouter.feed.push.notification.PushNotification.FeedMessage\x1a\xff\x02\n\x06Header\x12\x13\n\x0breceiver_id\x18\x01 \x01(\t\x12\x1e\n\x16technical_message_type\x18\x02 \x01(\t\x12\x1b\n\x13team_set_context_id\x18\x03 \x01(\t\x12\x39\n\rchunk_context\x18\x04 \x01(\x0b\x32\".agrirouter.commons.ChunkComponent\x12\x14\n\x0cpayload_size\x18\x05 \x01(\x03\x12\x32\n\x0esent_timestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\x0fsequence_number\x18\x07 \x01(\x03\x12\x11\n\tsender_id\x18\x08 \x01(\t\x12.\n\ncreated_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x12\n\nmessage_id\x18\n \x01(\t\x12.\n\x08metadata\x18\x0b \x01(\x0b\x32\x1c.agrirouter.commons.Metadata\x1a\x80\x01\n\x0b\x46\x65\x65\x64Message\x12J\n\x06header\x18\x01 \x01(\x0b\x32:.agrirouter.feed.push.notification.PushNotification.Header\x12%\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Anyb\x06proto3' + , + dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,google_dot_protobuf_dot_any__pb2.DESCRIPTOR,commons_dot_message__pb2.DESCRIPTOR,commons_dot_chunk__pb2.DESCRIPTOR,]) + + + + +_PUSHNOTIFICATION_HEADER = _descriptor.Descriptor( + name='Header', + full_name='agrirouter.feed.push.notification.PushNotification.Header', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='receiver_id', full_name='agrirouter.feed.push.notification.PushNotification.Header.receiver_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='technical_message_type', full_name='agrirouter.feed.push.notification.PushNotification.Header.technical_message_type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='team_set_context_id', full_name='agrirouter.feed.push.notification.PushNotification.Header.team_set_context_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='chunk_context', full_name='agrirouter.feed.push.notification.PushNotification.Header.chunk_context', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='payload_size', full_name='agrirouter.feed.push.notification.PushNotification.Header.payload_size', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sent_timestamp', full_name='agrirouter.feed.push.notification.PushNotification.Header.sent_timestamp', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sequence_number', full_name='agrirouter.feed.push.notification.PushNotification.Header.sequence_number', index=6, + number=7, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sender_id', full_name='agrirouter.feed.push.notification.PushNotification.Header.sender_id', index=7, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='created_at', full_name='agrirouter.feed.push.notification.PushNotification.Header.created_at', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_id', full_name='agrirouter.feed.push.notification.PushNotification.Header.message_id', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='metadata', full_name='agrirouter.feed.push.notification.PushNotification.Header.metadata', index=10, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=303, + serialized_end=686, +) + +_PUSHNOTIFICATION_FEEDMESSAGE = _descriptor.Descriptor( + name='FeedMessage', + full_name='agrirouter.feed.push.notification.PushNotification.FeedMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='header', full_name='agrirouter.feed.push.notification.PushNotification.FeedMessage.header', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='content', full_name='agrirouter.feed.push.notification.PushNotification.FeedMessage.content', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=689, + serialized_end=817, +) + +_PUSHNOTIFICATION = _descriptor.Descriptor( + name='PushNotification', + full_name='agrirouter.feed.push.notification.PushNotification', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='messages', full_name='agrirouter.feed.push.notification.PushNotification.messages', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_PUSHNOTIFICATION_HEADER, _PUSHNOTIFICATION_FEEDMESSAGE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=199, + serialized_end=817, +) + +_PUSHNOTIFICATION_HEADER.fields_by_name['chunk_context'].message_type = commons_dot_chunk__pb2._CHUNKCOMPONENT +_PUSHNOTIFICATION_HEADER.fields_by_name['sent_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_PUSHNOTIFICATION_HEADER.fields_by_name['created_at'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_PUSHNOTIFICATION_HEADER.fields_by_name['metadata'].message_type = commons_dot_message__pb2._METADATA +_PUSHNOTIFICATION_HEADER.containing_type = _PUSHNOTIFICATION +_PUSHNOTIFICATION_FEEDMESSAGE.fields_by_name['header'].message_type = _PUSHNOTIFICATION_HEADER +_PUSHNOTIFICATION_FEEDMESSAGE.fields_by_name['content'].message_type = google_dot_protobuf_dot_any__pb2._ANY +_PUSHNOTIFICATION_FEEDMESSAGE.containing_type = _PUSHNOTIFICATION +_PUSHNOTIFICATION.fields_by_name['messages'].message_type = _PUSHNOTIFICATION_FEEDMESSAGE +DESCRIPTOR.message_types_by_name['PushNotification'] = _PUSHNOTIFICATION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PushNotification = _reflection.GeneratedProtocolMessageType('PushNotification', (_message.Message,), { + + 'Header' : _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), { + 'DESCRIPTOR' : _PUSHNOTIFICATION_HEADER, + '__module__' : 'messaging.response.payload.feed.push_notification_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.push.notification.PushNotification.Header) + }) + , + + 'FeedMessage' : _reflection.GeneratedProtocolMessageType('FeedMessage', (_message.Message,), { + 'DESCRIPTOR' : _PUSHNOTIFICATION_FEEDMESSAGE, + '__module__' : 'messaging.response.payload.feed.push_notification_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.push.notification.PushNotification.FeedMessage) + }) + , + 'DESCRIPTOR' : _PUSHNOTIFICATION, + '__module__' : 'messaging.response.payload.feed.push_notification_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.feed.push.notification.PushNotification) + }) +_sym_db.RegisterMessage(PushNotification) +_sym_db.RegisterMessage(PushNotification.Header) +_sym_db.RegisterMessage(PushNotification.FeedMessage) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/messaging/response/response_pb2.py b/agrirouter/generated/messaging/response/response_pb2.py new file mode 100644 index 00000000..235e58b8 --- /dev/null +++ b/agrirouter/generated/messaging/response/response_pb2.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messaging/response/response.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='messaging/response/response.proto', + package='agrirouter.response', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n!messaging/response/response.proto\x12\x13\x61grirouter.response\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xcb\x03\n\x10ResponseEnvelope\x12\x15\n\rresponse_code\x18\x01 \x01(\x05\x12\x44\n\x04type\x18\x02 \x01(\x0e\x32\x36.agrirouter.response.ResponseEnvelope.ResponseBodyType\x12\x1e\n\x16\x61pplication_message_id\x18\x03 \x01(\t\x12\x12\n\nmessage_id\x18\x04 \x01(\t\x12-\n\ttimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xf6\x01\n\x10ResponseBodyType\x12\x0c\n\x08MESSAGES\x10\x00\x12\x07\n\x03\x41\x43K\x10\x01\x12\x15\n\x11\x41\x43K_WITH_MESSAGES\x10\x02\x12\x14\n\x10\x41\x43K_WITH_FAILURE\x10\x03\x12\x1c\n\x18\x41\x43K_FOR_FEED_HEADER_LIST\x10\x06\x12\x18\n\x14\x41\x43K_FOR_FEED_MESSAGE\x10\x07\x12\x1f\n\x1b\x41\x43K_FOR_FEED_FAILED_MESSAGE\x10\x08\x12\x15\n\x11\x45NDPOINTS_LISTING\x10\n\x12\x17\n\x13\x43LOUD_REGISTRATIONS\x10\x0b\x12\x15\n\x11PUSH_NOTIFICATION\x10\x0c\"?\n\x16ResponsePayloadWrapper\x12%\n\x07\x64\x65tails\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Anyb\x06proto3' + , + dependencies=[google_dot_protobuf_dot_any__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) + + + +_RESPONSEENVELOPE_RESPONSEBODYTYPE = _descriptor.EnumDescriptor( + name='ResponseBodyType', + full_name='agrirouter.response.ResponseEnvelope.ResponseBodyType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='MESSAGES', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK_WITH_MESSAGES', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK_WITH_FAILURE', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK_FOR_FEED_HEADER_LIST', index=4, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK_FOR_FEED_MESSAGE', index=5, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ACK_FOR_FEED_FAILED_MESSAGE', index=6, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENDPOINTS_LISTING', index=7, number=10, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CLOUD_REGISTRATIONS', index=8, number=11, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PUSH_NOTIFICATION', index=9, number=12, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=332, + serialized_end=578, +) +_sym_db.RegisterEnumDescriptor(_RESPONSEENVELOPE_RESPONSEBODYTYPE) + + +_RESPONSEENVELOPE = _descriptor.Descriptor( + name='ResponseEnvelope', + full_name='agrirouter.response.ResponseEnvelope', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='response_code', full_name='agrirouter.response.ResponseEnvelope.response_code', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type', full_name='agrirouter.response.ResponseEnvelope.type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='application_message_id', full_name='agrirouter.response.ResponseEnvelope.application_message_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message_id', full_name='agrirouter.response.ResponseEnvelope.message_id', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='timestamp', full_name='agrirouter.response.ResponseEnvelope.timestamp', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _RESPONSEENVELOPE_RESPONSEBODYTYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=119, + serialized_end=578, +) + + +_RESPONSEPAYLOADWRAPPER = _descriptor.Descriptor( + name='ResponsePayloadWrapper', + full_name='agrirouter.response.ResponsePayloadWrapper', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='details', full_name='agrirouter.response.ResponsePayloadWrapper.details', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=580, + serialized_end=643, +) + +_RESPONSEENVELOPE.fields_by_name['type'].enum_type = _RESPONSEENVELOPE_RESPONSEBODYTYPE +_RESPONSEENVELOPE.fields_by_name['timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_RESPONSEENVELOPE_RESPONSEBODYTYPE.containing_type = _RESPONSEENVELOPE +_RESPONSEPAYLOADWRAPPER.fields_by_name['details'].message_type = google_dot_protobuf_dot_any__pb2._ANY +DESCRIPTOR.message_types_by_name['ResponseEnvelope'] = _RESPONSEENVELOPE +DESCRIPTOR.message_types_by_name['ResponsePayloadWrapper'] = _RESPONSEPAYLOADWRAPPER +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ResponseEnvelope = _reflection.GeneratedProtocolMessageType('ResponseEnvelope', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSEENVELOPE, + '__module__' : 'messaging.response.response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.ResponseEnvelope) + }) +_sym_db.RegisterMessage(ResponseEnvelope) + +ResponsePayloadWrapper = _reflection.GeneratedProtocolMessageType('ResponsePayloadWrapper', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSEPAYLOADWRAPPER, + '__module__' : 'messaging.response.response_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.response.ResponsePayloadWrapper) + }) +_sym_db.RegisterMessage(ResponsePayloadWrapper) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/generated/settings/dh_settings_pb2.py b/agrirouter/generated/settings/dh_settings_pb2.py new file mode 100644 index 00000000..c40238fc --- /dev/null +++ b/agrirouter/generated/settings/dh_settings_pb2.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: settings/dh-settings.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='settings/dh-settings.proto', + package='agrirouter.message.settings', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x1asettings/dh-settings.proto\x12\x1b\x61grirouter.message.settings\"R\n\x16\x44\x61taHubControlSettings\x12\x18\n\x10max_message_size\x18\x01 \x01(\x05\x12\x1e\n\x16max_messages_per_query\x18\x02 \x01(\x05\x62\x06proto3' +) + + + + +_DATAHUBCONTROLSETTINGS = _descriptor.Descriptor( + name='DataHubControlSettings', + full_name='agrirouter.message.settings.DataHubControlSettings', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='max_message_size', full_name='agrirouter.message.settings.DataHubControlSettings.max_message_size', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_messages_per_query', full_name='agrirouter.message.settings.DataHubControlSettings.max_messages_per_query', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=59, + serialized_end=141, +) + +DESCRIPTOR.message_types_by_name['DataHubControlSettings'] = _DATAHUBCONTROLSETTINGS +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +DataHubControlSettings = _reflection.GeneratedProtocolMessageType('DataHubControlSettings', (_message.Message,), { + 'DESCRIPTOR' : _DATAHUBCONTROLSETTINGS, + '__module__' : 'settings.dh_settings_pb2' + # @@protoc_insertion_point(class_scope:agrirouter.message.settings.DataHubControlSettings) + }) +_sym_db.RegisterMessage(DataHubControlSettings) + + +# @@protoc_insertion_point(module_scope) diff --git a/agrirouter/clients/__init__.py b/agrirouter/messaging/__init__.py similarity index 100% rename from agrirouter/clients/__init__.py rename to agrirouter/messaging/__init__.py diff --git a/agrirouter/messaging/certification.py b/agrirouter/messaging/certification.py new file mode 100644 index 00000000..5579b870 --- /dev/null +++ b/agrirouter/messaging/certification.py @@ -0,0 +1,23 @@ +import json +import os +import pathlib +from pathlib import Path +import tempfile + + +from agrirouter.onboarding.response import BaseOnboardingResonse + + +def create_certificate_file(onboard_response: BaseOnboardingResonse): + dir_ = tempfile.mkdtemp() + prefix = onboard_response.get_sensor_alternate_id() + data = onboard_response.get_authentication().get_certificate() + fd, path = tempfile.mkstemp(dir=dir_, prefix=prefix, text=True) + try: + with os.fdopen(fd, 'w') as tmp: + tmp.write(data) + except Exception as exc: + os.remove(path) + raise exc + + return path diff --git a/tests/__init__.py b/agrirouter/messaging/clients/__init__.py similarity index 100% rename from tests/__init__.py rename to agrirouter/messaging/clients/__init__.py diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py new file mode 100644 index 00000000..08530c77 --- /dev/null +++ b/agrirouter/messaging/clients/http.py @@ -0,0 +1,22 @@ +class HttpClient: + + headers = {"Content-Type": "application/json"} + + def __init__(self, + on_message_callback: callable, + timeout=20 + ): + self.on_message_callback = on_message_callback + self.timeout = timeout + + def publish(self): + pass + + def subscribe(self): + pass + + def unsubscribe(self): + pass + + def _start_loop(self): + pass diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py new file mode 100644 index 00000000..4e8dd555 --- /dev/null +++ b/agrirouter/messaging/clients/mqtt.py @@ -0,0 +1,138 @@ +from typing import Any, List, Tuple + +from paho.mqtt import client as mqtt_client +from paho.mqtt.client import MQTTv31, MQTTMessageInfo + + +class MqttClient: + + def __init__(self, + client_id: str = "", + on_message_callback: callable = None, + userdata: Any = None, + clean_session: bool = True + ): + # TODO: Implement on_message_callback parameter validation: + # must take params as described at https://pypi.org/project/paho-mqtt/#callbacks + + self.mqtt_client = mqtt_client.Client( + client_id=client_id, + clean_session=clean_session, + userdata=userdata, + protocol=MQTTv31, + transport="tcp" + ) + self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback() + self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() + self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() + self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() + + def connect(self, host: str, port: str) -> None: + self.mqtt_client.connect_async( + host=host, + port=port + ) + self.mqtt_client.loop_start() + + def disconnect(self): + self.mqtt_client.loop_stop() + self.mqtt_client.disconnect() + + def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: + """ + + :param topic: str representing unique name of the topic that the message should be published on + :param payload: The actual message to send + :param qos: int representing the quality of service level to use. May be [0, 1, 2] + :return: MQTTMessageInfo + """ + message_info = self.mqtt_client.publish( + topic=topic, + payload=payload, + qos=qos + ) + return message_info + + def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: + """ + + :param topics: list of tuples [(topic, qos),] containing topic to subscribe on + and desired quality of service + + topic: str representing unique name of the topic to subscribe on + qos: int representing the quality of service level to use. May be [0, 1, 2] + + Example: topics=[('my/topic/1', 0), ('my/topic/2', 1), ('my/topic/3', 2)] + + :return: tuple + """ + result, mid = self.mqtt_client.subscribe(topics) + return result, mid + + def unsubscribe(self, topics: List[str]) -> tuple: + """ + + :param topics: list of strings [topic, topic] containing topic to unsubscribe from + + topic: str representing unique name of the topic to unsubscribe from + + Example: topics=['my/topic/1', 'my/topic/2', 'my/topic/3'] + + :return: tuple + """ + result, mid = self.mqtt_client.unsubscribe(topics) + return result, mid + + @staticmethod + def _get_on_connect_callback() -> callable: + + def on_connect(client, userdata, flags, rc, properties=None): + if rc == 0: + print("Connected to MQTT Broker!") + else: + print(f"Failed to connect, return code: {rc}") + + return client, userdata, flags, rc, properties + + return on_connect + + @staticmethod + def _get_on_message_callback() -> callable: + + def on_message(client, userdata, msg): + # print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") + + return client, userdata, msg + + return on_message + + @staticmethod + def _get_on_subscribe_callback() -> callable: + + def on_subscribe(client, userdata, mid, granted_qos, properties=None): + # print(f"Subscribed {userdata} to `{properties}`") + + return client, userdata, mid, granted_qos, properties + + return on_subscribe + + @staticmethod + def _get_on_disconnect_callback() -> callable: + + def on_disconnect(client, userdata, rc): + # print(f"Disconnected from from `{properties}`") + + return client, userdata, rc + + return on_disconnect + + @staticmethod + def _get_on_unsubscribe_callback() -> callable: + + def on_unsubscribe(client, userdata, mid): + # print(f"Unsubscribed `{userdata}` from `{properties}`") + + return client, userdata, mid + + return on_unsubscribe diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py new file mode 100644 index 00000000..839a0e48 --- /dev/null +++ b/agrirouter/messaging/decode.py @@ -0,0 +1,50 @@ +import base64 +from ctypes import Union + +from google.protobuf.any_pb2 import Any +from google.protobuf.internal.decoder import _DecodeVarint + +from agrirouter.generated.commons.message_pb2 import Messages +from agrirouter.generated.messaging.response.payload.account.endpoints_pb2 import ListEndpointsResponse +from agrirouter.generated.messaging.response.payload.feed.feed_response_pb2 import HeaderQueryResponse, \ + MessageQueryResponse +from agrirouter.generated.messaging.response.response_pb2 import ResponseEnvelope, ResponsePayloadWrapper +from agrirouter.messaging.messages import DecodedMessage +from agrirouter.utils.type_url import TypeUrl + + +def read_properties_buffers_from_input_stream(input_stream) -> tuple: + result = [] + pos = 0 + while pos < len(input_stream): + msg_len, pos = _DecodeVarint(input_stream, pos) + + msg_buf = input_stream[pos:pos + msg_len] + result.append(msg_buf) + + pos += msg_len + + return tuple(result) + + +def decode_response(message: bytes) -> DecodedMessage: + input_stream = base64.b64decode(message) + response_envelope_buffer, response_payload_buffer = read_properties_buffers_from_input_stream(input_stream) + + envelope = ResponseEnvelope().MergeFromString(response_envelope_buffer) + payload = ResponsePayloadWrapper().MergeFromString(response_payload_buffer) + + message = DecodedMessage(envelope, payload) + + return message + + +def decode_details(details: Any): + if details.type_url == TypeUrl.get_type_url(Messages.__name__): + return Messages().MergeFromString(details.value) + elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse.__name__): + return ListEndpointsResponse().MergeFromString(details.value) + elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse.__name__): + return HeaderQueryResponse().MergeFromString(details.value) + elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse.__name__): + return MessageQueryResponse().MergeFromString(details.value) diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py new file mode 100644 index 00000000..fe74c229 --- /dev/null +++ b/agrirouter/messaging/encode.py @@ -0,0 +1,46 @@ +import base64 + +from google.protobuf.any_pb2 import Any +from google.protobuf.internal.encoder import _VarintBytes + +from agrirouter.generated.messaging.request.request_pb2 import RequestEnvelope, RequestPayloadWrapper +from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters +from agrirouter.utils.utc_time_util import now_as_utc_timestamp +from agrirouter.utils.uuid_util import new_uuid + + +def write_proto_parts_to_buffer(parts: list, buffer: bytes = b""): + for part in parts: + part_size = part.ByteSize() + buffer += _VarintBytes(part_size) + buffer += part.SerializeToString() + + return buffer + + +def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> bytes: + request_envelope = encode_header(header_parameters) + request_payload = encode_payload(payload_parameters) + + raw_data = write_proto_parts_to_buffer([request_envelope, request_payload]) + + return base64.b64encode(raw_data) + + +def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope: + request_envelope = RequestEnvelope() + request_envelope.application_id = header_parameters.get_application_message_id() \ + if header_parameters.get_application_message_id() else new_uuid() + request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() + request_envelope.technical_message_type = header_parameters.get_technical_message_type() + request_envelope.mode = header_parameters.get_mode() + request_envelope.timestamp = now_as_utc_timestamp() + return request_envelope + + +def encode_payload(payload_parameters: MessagePayloadParameters) -> RequestPayloadWrapper: + any_proto_wrapper = Any() + any_proto_wrapper.type_url = payload_parameters.get_type_url() + any_proto_wrapper.value = payload_parameters.get_value() + request_payload = RequestPayloadWrapper(any_proto_wrapper) + return request_payload diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py new file mode 100644 index 00000000..1247118f --- /dev/null +++ b/agrirouter/messaging/enums.py @@ -0,0 +1,15 @@ +from agrirouter.auth.enums import BaseEnum + + +class TechnicalMessageType(BaseEnum): + EMPTY = "" + CAPABILITIES = "dke:capabilities" + SUBSCRIPTION = "dke:subscription" + LIST_ENDPOINTS = "dke:list_endpoints" + LIST_ENDPOINTS_UNFILTERED = "dke:list_endpoints_unfiltered" + FEED_CONFIRM = "dke:feed_confirm" + FEED_DELETE = "dke:feed_delete" + FEED_HEADER_QUERY = "dke:feed_header_query" + FEED_MESSAGE_QUERY = "dke:feed_message_query" + CLOUD_ONBOARD_ENDPOINTS = "dke:cloud_onboard_endpoints" + CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" diff --git a/agrirouter/messaging/exceptions.py b/agrirouter/messaging/exceptions.py new file mode 100644 index 00000000..3df29d4c --- /dev/null +++ b/agrirouter/messaging/exceptions.py @@ -0,0 +1,9 @@ +from agrirouter.onboarding.exceptions import AgriRouuterBaseException + + +class TypeUrlNotFoundError(AgriRouuterBaseException): + _message = "Given type url not found" + + +class WrongFieldError(AgriRouuterBaseException): + _message = "Unknown field" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py new file mode 100644 index 00000000..1e5228fd --- /dev/null +++ b/agrirouter/messaging/messages.py @@ -0,0 +1,115 @@ +import json +from datetime import datetime, timezone +from typing import Union, List, Dict + +from agrirouter.messaging.exceptions import WrongFieldError + + +class EncodedMessage: + + def __init__(self, id_, content): + self.id_ = id_ + self.content = content + + def get_id(self): + return self.id_ + + def get_content(self): + return self.content + + +class DecodedMessage: + def __init__(self, response_envelope, response_payload): + self.response_envelope = response_envelope + self.response_payload = response_payload + + def get_response_payload(self): + return self.response_payload + + def get_response_envelope(self): + return self.response_envelope + + +class Message: + MESSAGE = "message" + TIMESTAMP = "timestamp" + + def __init__(self, content): + self.content = content + self.timestamp = datetime.utcnow() + + def json_serialize(self) -> dict: + return { + self.MESSAGE: self.content, + self.TIMESTAMP: self.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + } + + +class Command: + MESSAGE = "message" + + def __init__(self, message: str = None): + self.message = message + + def json_deserialize(self, data: Union[Dict[str, str], str]): + messages = data if type(data) == list else json.loads(data) + for key, value in messages.keys(): + if key == self.MESSAGE: + self.message = value + else: + raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") + + def get_message(self) -> str: + return self.message + + def set_message(self, message: str): + self.message = message + + +class OutboxMessage: + + CAPABILITY_ALTERNATE_ID = "capabilityAlternateId" + SENSOR_ALTERNATE_ID = "sensorAlternateId" + COMMAND = "command" + + def __init__(self, + capability_alternate_id: str = None, + sensor_alternate_id: str = None, + command: Command = None, + ): + self.capability_alternate_id = capability_alternate_id + self.sensor_alternate_id = sensor_alternate_id + self.command = command + + def json_deserialize(self, data: Union[list, str]): + data = data if type(data) == list else json.loads(data) + for key, value in data.keys(): + if key == self.CAPABILITY_ALTERNATE_ID: + self.capability_alternate_id = value + elif key == self.SENSOR_ALTERNATE_ID: + self.sensor_alternate_id = value + elif key == self.COMMAND: + self.command = Command.json_deserialize(value) + else: + raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") + + def get_capability_alternate_id(self) -> str: + return self.capability_alternate_id + + def set_capability_alternate_id(self, capability_alternate_id: str) -> None: + self.capability_alternate_id = capability_alternate_id + + def get_sensor_alternate_id(self) -> str: + return self.sensor_alternate_id + + def set_sensor_alternate_id(self, sensor_alternate_id: str) -> None: + self.sensor_alternate_id = sensor_alternate_id + + def get_command(self) -> Command: + return self.command + + def set_command(self, command: Command) -> None: + self.command = command + + def json_deserialize(self): + pass diff --git a/agrirouter/constants/keys.py b/agrirouter/messaging/parameters/__init__.py similarity index 100% rename from agrirouter/constants/keys.py rename to agrirouter/messaging/parameters/__init__.py diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py new file mode 100644 index 00000000..3c1404de --- /dev/null +++ b/agrirouter/messaging/parameters/dto.py @@ -0,0 +1,91 @@ +from copy import deepcopy +from typing import List + +from agrirouter.messaging.messages import EncodedMessage +from agrirouter.onboarding.response import BaseOnboardingResonse + + +class Parameters: + def __init__(self, + *, + application_message_seq_no: str, + application_message_id: int = None, + team_set_context_id: str + ): + self.application_message_seq_no = application_message_seq_no + self.application_message_id = application_message_id + self.team_set_context_id = team_set_context_id + + def get_application_message_seq_no(self): + return self.application_message_seq_no + + def get_application_message_id(self): + return self.application_message_id + + def get_team_set_context_id(self): + return self.team_set_context_id + + def set_application_message_seq_no(self, application_message_seq_no): + self.application_message_seq_no = application_message_seq_no + + def set_application_message_id(self, application_message_id): + self.application_message_id = application_message_id + + def set_team_set_context_id(self, team_set_context_id): + self.team_set_context_id = team_set_context_id + + def validate(self): + pass + + +class MessageParameters(Parameters): + def __init__(self, + *, + application_message_seq_no: str, + application_message_id: int, + team_set_context_id: str, + onboarding_response: BaseOnboardingResonse + ): + super(MessageParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + ) + + self.onboarding_response = onboarding_response + + def get_onboarding_response(self): + return self.onboarding_response + + +class MessagingParameters(MessageParameters): + + def __init__(self, + *, + application_message_seq_no: str = None, + application_message_id: int = None, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + encoded_messages=None + ): + + super(MessagingParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response, + ) + + self._encoded_messages = encoded_messages if encoded_messages else [] + + def get_encoded_messages(self): + return deepcopy(self._encoded_messages) + + def set_encoded_messages(self, encoded_messages: List[EncodedMessage]): + self._encoded_messages = encoded_messages + + def append_encoded_messages(self, encoded_message: EncodedMessage): + self._encoded_messages.append(encoded_message) + + def extend_encoded_messages(self, encoded_messages: List[EncodedMessage]): + self._encoded_messages.extend(encoded_messages) diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py new file mode 100644 index 00000000..0769fa08 --- /dev/null +++ b/agrirouter/messaging/parameters/service.py @@ -0,0 +1,348 @@ +from abc import ABC, abstractmethod +from copy import deepcopy +from typing import List + +from agrirouter.generated.commons.chunk_pb2 import ChunkComponent +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.messaging.parameters.dto import MessageParameters, Parameters + + +class MessageHeaderParameters(Parameters): + + def __init__(self, + *, + technical_message_type: str = None, + mode: str = None, + team_set_context_id: str = None, + application_message_seq_no: str = None, + recipients: list = None, + chunk_component: ChunkComponent = None, + application_message_id: int = None, + ): + super(MessageHeaderParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id + ) + + self.technical_message_type = technical_message_type + self.mode = mode + self.recipients = recipients + self.chunk_component = chunk_component + + def get_technical_message_type(self) -> str: + return self.technical_message_type + + def get_mode(self) -> str: + return self.mode + + def get_recipients(self) -> list: + return self.recipients + + def get_chunk_component(self) -> ChunkComponent: + return self.chunk_component + + +class MessagePayloadParameters: + + def __init__(self, + *, + type_url: str, + value: str, + ): + + self.type_url = type_url + self.value = value + + def get_type_url(self) -> str: + return self.type_url + + def get_value(self) -> str: + return self.value + + +class CloudOnboardParameters(MessageParameters): + + def __init__(self, + # List[EndpointRegistrationDetails] - must be defined in generated by by proto schemes, + # but they are not + onboarding_requests: list = None, + **kwargs + ): + self.onboarding_requests = onboarding_requests if onboarding_requests else [] + super(CloudOnboardParameters, self).__init__(**kwargs) + + def get_onboarding_requests(self) -> list: + return self.onboarding_requests + + def set_onboarding_requests(self, onboarding_requests: list) -> None: + self.onboarding_requests = onboarding_requests + + def add_onboarding_requests(self, onboarding_request) -> None: + self.onboarding_requests.append(onboarding_request) + + def extend_onboarding_requests(self, onboarding_requests: list) -> None: + self.onboarding_requests.extend(onboarding_requests) + + +class CloudOffboardParameters(MessageParameters): + + def __init__(self, + endpoints: List[str] = None, + **kwargs + ): + self.endpoints = endpoints if endpoints else [] + super(CloudOffboardParameters, self).__init__(**kwargs) + + def get_endpoints(self) -> List[str]: + return self.endpoints + + def set_endpoints(self, endpoints: list) -> None: + self.endpoints = endpoints + + def add_endpoints(self, endpoint: str) -> None: + self.endpoints.append(endpoint) + + def extend_endpoints(self, endpoints: List[str]) -> None: + self.endpoints.extend(endpoints) + + +class CapabilityParameters(MessageParameters): + + def __init__(self, + application_id, + certification_version_id, + enable_push_notification, + capability_parameters: list = None, + **kwargs + ): + self.application_id = application_id + self.certification_version_id = certification_version_id + self.enable_push_notification = enable_push_notification + self.capability_parameters = capability_parameters if capability_parameters else [] + super(CapabilityParameters, self).__init__(**kwargs) + + def get_application_id(self): + return self.application_id + + def get_certification_version_id(self): + return self.certification_version_id + + def get_enable_push_notification(self): + return self.enable_push_notification + + def get_capability_parameters(self): + return deepcopy(self.capability_parameters) + + def set_application_id(self, application_id): + self.application_id = application_id + + def set_certification_version_id(self, certification_version_id): + self.certification_version_id = certification_version_id + + def set_enable_push_notification(self, enable_push_notification): + self.enable_push_notification = enable_push_notification + + def set_capability_parameters(self, capability_parameters: list): + self.capability_parameters = capability_parameters + + def add_capability_parameters(self, capability_parameter): + self.capability_parameters.append(capability_parameter) + + def extend_capability_parameters(self, capability_parameters: list): + self.capability_parameters.extend(capability_parameters) + + +class FeedConfirmParameters(MessageParameters): + def __init__(self, message_ids: list = None, **kwargs): + self.message_ids = message_ids if message_ids else [] + super(FeedConfirmParameters, self).__init__(**kwargs) + + def get_message_ids(self): + return deepcopy(self.message_ids) + + def set_message_ids(self, message_ids: list): + self.message_ids = message_ids + + def add_message_ids(self, message_id): + self.message_ids.append(message_id) + + def extend_message_ids(self, message_ids): + self.message_ids.extend(message_ids) + + +class FeedDeleteParameters(MessageParameters): + def __init__(self, + message_ids: list = None, + receivers: list = None, + validity_period: ValidityPeriod = None, + **kwargs): + self.message_ids = message_ids if message_ids else [] + self.receivers = receivers if receivers else [] + self.validity_period = validity_period + super(FeedDeleteParameters, self).__init__(**kwargs) + + def get_message_ids(self): + return deepcopy(self.message_ids) + + def set_message_ids(self, message_ids: list): + self.message_ids = message_ids + + def add_message_ids(self, message_id): + self.message_ids.append(message_id) + + def extend_message_ids(self, message_ids): + self.message_ids.extend(message_ids) + + def get_receivers(self): + return deepcopy(self.receivers) + + def set_receivers(self, receivers: list): + self.receivers = receivers + + def add_receivers(self, receiver): + self.receivers.append(receiver) + + def extend_receivers(self, receivers): + self.receivers.extend(receivers) + + def get_validity_period(self): + return self.validity_period + + def set_validity_period(self, validity_period: ValidityPeriod): + self.validity_period = validity_period + + +class ListEndpointsParameters(MessageParameters): + def __init__(self, + technical_message_type: str = None, + direction: str = None, + filtered: bool = False, + **kwargs): + self.technical_message_type = technical_message_type + self.direction = direction + self.filtered = filtered + super(ListEndpointsParameters, self).__init__(**kwargs) + + def get_technical_message_type(self) -> str: + return self.technical_message_type + + def set_technical_message_type(self, technical_message_type: str): + self.technical_message_type = technical_message_type + + def get_direction(self) -> str: + return self.direction + + def set_direction(self, direction: str): + self.direction = direction + + def is_filtered(self): + return self.filtered + + def set_filtered(self, filtered: bool): + self.filtered = filtered + + +class QueryMessageParameters(MessageParameters): + def __init__(self, + senders: list = None, + message_ids: list = None, + validity_period: ValidityPeriod = None, + **kwargs): + self.senders = senders + self.message_ids = message_ids + self.validity_period = validity_period + super(QueryMessageParameters, self).__init__(**kwargs) + + def get_senders(self) -> list: + return self.senders + + def set_senders(self, senders: list) -> None: + self.senders = senders + + def add_senders(self, sender) -> None: + self.senders.append(sender) + + def extend_senders(self, senders) -> None: + self.senders.extend(senders) + + def get_message_ids(self) -> list: + return self.message_ids + + def set_message_ids(self, message_ids: list) -> None: + self.message_ids = message_ids + + def add_message_ids(self, message_id) -> None: + self.message_ids.append(message_id) + + def extend_message_ids(self, message_ids) -> None: + self.message_ids.extend(message_ids) + + def get_validity_period(self) -> ValidityPeriod: + return self.validity_period + + def set_validity_period(self, validity_period: list) -> None: + self.validity_period = validity_period + + +class QueryHeaderParameters(MessageParameters): + def __init__(self, + senders: list = None, + message_ids: list = None, + validity_period: ValidityPeriod = None, + **kwargs): + self.senders = senders + self.message_ids = message_ids + self.validity_period = validity_period + super(QueryHeaderParameters, self).__init__(**kwargs) + + def get_senders(self) -> list: + return self.senders + + def set_senders(self, senders: list) -> None: + self.senders = senders + + def add_senders(self, sender) -> None: + self.senders.append(sender) + + def extend_senders(self, senders) -> None: + self.senders.extend(senders) + + def get_message_ids(self) -> list: + return self.message_ids + + def set_message_ids(self, message_ids: list) -> None: + self.message_ids = message_ids + + def add_message_ids(self, message_id) -> None: + self.message_ids.append(message_id) + + def extend_message_ids(self, message_ids) -> None: + self.message_ids.extend(message_ids) + + def get_validity_period(self) -> ValidityPeriod: + return self.validity_period + + def set_validity_period(self, validity_period: list) -> None: + self.validity_period = validity_period + + +class SubscriptionParameters(MessageParameters): + def __init__(self, + subscription_items: List[Subscription.MessageTypeSubscriptionItem] = None, + **kwargs): + self.subscription_items = subscription_items if subscription_items else [] + super(SubscriptionParameters, self).__init__(**kwargs) + + def get_subscription_items(self) -> List[Subscription.MessageTypeSubscriptionItem]: + return self.subscription_items + + def set_subscription_items(self, subscription_items: List[Subscription.MessageTypeSubscriptionItem]) -> None: + self.subscription_items = subscription_items + + def add_subscription_items(self, subscription_item: Subscription.MessageTypeSubscriptionItem) -> None: + self.subscription_items.append(subscription_item) + + def extend_direction(self, subscription_items: List[Subscription.MessageTypeSubscriptionItem]) -> None: + self.subscription_items.extend(subscription_items) diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py new file mode 100644 index 00000000..4de0a69e --- /dev/null +++ b/agrirouter/messaging/request.py @@ -0,0 +1,25 @@ +from typing import List + +from agrirouter.messaging.messages import Message + + +class MessageRequest: + SENSOR_ALTERNATE_ID = "sensorAlternateId" + CAPABILITY_ALTERNATE_ID = "capabilityAlternateId" + MESSAGES = "measures" + + def __init__(self, + sensor_alternate_id: str, + capability_alternate_id: str, + messages: List[Message] + ): + self.sensor_alternate_id = sensor_alternate_id + self.capability_alternate_id = capability_alternate_id + self.messages = messages + + def json_serialize(self) -> dict: + return { + self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, + self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, + self.MESSAGES: self.messages + } diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py new file mode 100644 index 00000000..ba2df495 --- /dev/null +++ b/agrirouter/messaging/result.py @@ -0,0 +1,47 @@ +import json +from typing import List, Union + +from agrirouter.messaging.messages import OutboxMessage + + +class MessagingResult: + def __init__(self, messages_ids: List): + self.messages_ids = messages_ids + + def set_messages_ids(self, messages_ids): + self.messages_ids = messages_ids + + def get_messages_ids(self): + return self.messages_ids + + +class OutboxResponse: + def __init__(self, + status_code: int = None, + messages: List[OutboxMessage] = None + ): + + self.status_code = status_code + self.messages = messages if messages else [] + + def json_deserialize(self, data: Union[list, str]): + messages = data if type(data) == list else json.loads(data) + self.set_messages([OutboxMessage.json_deserialize(message) for message in messages]) + + def get_status_code(self) -> int: + return self.status_code + + def set_status_code(self, status_code: int) -> None: + self.status_code = status_code + + def get_messages(self) -> List[OutboxMessage]: + return self.messages + + def set_messages(self, messages: List[OutboxMessage]) -> None: + self.messages = messages + + def add_messages(self, message: OutboxMessage) -> None: + self.messages.append(message) + + def extend_messages(self, messages: List[OutboxMessage]) -> None: + self.messages.extend(messages) diff --git a/agrirouter/environments/enums.py b/agrirouter/messaging/services/__init__.py similarity index 100% rename from agrirouter/environments/enums.py rename to agrirouter/messaging/services/__init__.py diff --git a/agrirouter/messaging/services/cloud.py b/agrirouter/messaging/services/cloud.py new file mode 100644 index 00000000..cd40a465 --- /dev/null +++ b/agrirouter/messaging/services/cloud.py @@ -0,0 +1,71 @@ +from agrirouter.generated.cloud_provider_integration.cloud_virtualized_app_registration_pb2 import OnboardingRequest, \ + OffboardingRequest +from agrirouter.generated.messaging.request.request_pb2 import RequestEnvelope +from agrirouter.messaging.encode import encode_message +from agrirouter.messaging.enums import TechnicalMessageType +from agrirouter.messaging.messages import EncodedMessage +from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters, \ + CloudOnboardParameters, CloudOffboardParameters +from agrirouter.messaging.services.messaging import AbstractService +from agrirouter.utils.type_url import TypeUrl +from agrirouter.utils.uuid_util import new_uuid + + +class CloudOnboardService(AbstractService): + + @staticmethod + def encode(parameters: CloudOnboardParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.CLOUD_ONBOARD_ENDPOINTS.value + ) + + onboarding_request = OnboardingRequest( + onboarding_requests=parameters.get_onboarding_requests() + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(OnboardingRequest.__name__), + value=onboarding_request.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class CloudOffboardService(AbstractService): + + @staticmethod + def encode(parameters: CloudOffboardParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.CLOUD_OFFBOARD_ENDPOINTS.value + ) + + offboarding_request = OffboardingRequest( + endpoints=parameters.get_endpoints() + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(OffboardingRequest.__name__), + value=offboarding_request.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py new file mode 100644 index 00000000..3e368632 --- /dev/null +++ b/agrirouter/messaging/services/commons.py @@ -0,0 +1,90 @@ +import os +from abc import ABC, abstractmethod + +import requests + +from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.clients.mqtt import MqttClient +from agrirouter.messaging.messages import Message +from agrirouter.messaging.request import MessageRequest +from agrirouter.messaging.result import MessagingResult + + +class AbstractMessagingClient(ABC): + + @staticmethod + def create_message_request(parameters) -> MessageRequest: + messages = [] + for encoded_message in parameters.get_encoded_messages(): + message = Message(encoded_message) + messages.append(message) + message_request = MessageRequest( + parameters.get_sensor_alternate_id(), + parameters.get_capability_alternate_id(), + messages + ) + return message_request + + @abstractmethod + def send(self, parameters): + ... + + +class HttpMessagingService(AbstractMessagingClient): + + def send(self, parameters) -> MessagingResult: + request = self.create_message_request(parameters) + cert_file_path = create_certificate_file(parameters.get_onboarding_response()) + try: + response = requests.post( + url=parameters.get_onboarding_response().get_connection_criteria().get_measures(), + headers={"Content-type": "application/json"}, + data=request.json_serialize(), + cert=( + cert_file_path, + parameters.get_onboarding_response().get_authentication().get_secret() + ), + ) + finally: + os.remove(cert_file_path) + result = MessagingResult([parameters.get_message_id()]) + return result + + def subscribe(self): + pass + + def unsubscribe(self): + pass + + +class MqttMessagingService(AbstractMessagingClient): + + def __init__(self, + onboarding_response, + on_message_callback: callable = None, + ): + + self.onboarding_response = onboarding_response + self.client = MqttClient( + client_id=self.onboarding_response.get_client_id(), + on_message_callback=on_message_callback, + ) + self.client.connect( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) + + def send(self, parameters, qos: int = 0) -> MessagingResult: + mqtt_payload = self.create_message_request(parameters) + self.client.publish( + self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, + qos=qos + ) + result = MessagingResult([parameters.get_message_id()]) + return result + + def subscribe(self): + pass + + def unsubscribe(self): + pass diff --git a/agrirouter/messaging/services/http/__init__.py b/agrirouter/messaging/services/http/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py new file mode 100644 index 00000000..b81bfcbf --- /dev/null +++ b/agrirouter/messaging/services/http/outbox.py @@ -0,0 +1,30 @@ +import os + +import requests + +from agrirouter.messaging.result import OutboxResponse + +from agrirouter.messaging.certification import create_certificate_file + + +class OutboxService: + + def fetch(self, onboarding_response) -> OutboxResponse: + cert_file_path = create_certificate_file(onboarding_response) + try: + response = requests.get( + url=onboarding_response.get_connection_criteria().get_commands(), + headers={"Content-type": "application/json"}, + cert=( + cert_file_path, + onboarding_response.get_authentication().get_secret() + ), + ) + finally: + os.remove(cert_file_path) + + outbox_response = OutboxResponse(status_code=response.status_code) + outbox_response.json_deserialize(response.json()["contents"]) + + return outbox_response + diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py new file mode 100644 index 00000000..e071b028 --- /dev/null +++ b/agrirouter/messaging/services/messaging.py @@ -0,0 +1,254 @@ +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import MessageConfirm, MessageQuery +from agrirouter.generated.messaging.request.request_pb2 import RequestEnvelope +from agrirouter.messaging.encode import encode_message +from agrirouter.messaging.enums import TechnicalMessageType +from agrirouter.messaging.messages import EncodedMessage +from agrirouter.messaging.parameters.dto import MessageParameters, MessagingParameters +from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters, \ + CapabilityParameters, FeedConfirmParameters, FeedDeleteParameters, ListEndpointsParameters, \ + SubscriptionParameters, QueryHeaderParameters, QueryMessageParameters +from agrirouter.utils.type_url import TypeUrl +from agrirouter.utils.uuid_util import new_uuid + + +class AbstractService: + + def __init__(self, messaging_service): + self.messaging_service = messaging_service + + def send(self, parameters): + messaging_parameters = MessagingParameters( + onboarding_response=parameters.get_onboarding_response(), + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + ) + encoded_messages = self.encode(parameters) + messaging_parameters.set_encoded_messages([encoded_messages.get_content()]) + return self.messaging_service.send(messaging_parameters) + + @staticmethod + def encode(*args, **kwargs) -> EncodedMessage: + ... + + +class CapabilityService(AbstractService): + + @staticmethod + def encode(parameters: CapabilityParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.CAPABILITIES.value + ) + + capability_specification = CapabilitySpecification( + app_certification_id=parameters.get_application_id(), + app_certification_version_id=parameters.get_certification_version_id(), + enable_push_notifications=parameters.get_enable_push_notification() + ) + if parameters.get_capability_parameters(): + capability_specification.capabilities = parameters.get_capability_parameters() + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(CapabilitySpecification.__name__), + value=capability_specification.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class FeedConfirmService(AbstractService): + + @staticmethod + def encode(parameters: FeedConfirmParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.FEED_CONFIRM.value + ) + + message_confirm = MessageConfirm( + message_ids=parameters.get_message_ids() + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + value=message_confirm.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class FeedDeleteService(AbstractService): + + @staticmethod + def encode(parameters: FeedDeleteParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.FEED_CONFIRM.value + ) + + message_confirm = MessageConfirm( + message_ids=parameters.get_message_ids() + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + value=message_confirm.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class ListEndpointsService(AbstractService): + + @staticmethod + def encode(parameters: ListEndpointsParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.LIST_ENDPOINTS.value if parameters.is_filtered() + else TechnicalMessageType.LIST_ENDPOINTS_UNFILTERED.value + ) + + list_endpoints_query = ListEndpointsQuery( + technical_message_type=parameters.get_technical_message_type(), + direction=parameters.get_direction() + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(ListEndpointsQuery.__name__), + value=list_endpoints_query.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class QueryMessagesService(AbstractService): + + @staticmethod + def encode(parameters: QueryMessageParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.FEED_MESSAGE_QUERY.value + ) + + message_query = MessageQuery( + senders=parameters.get_senders(), + message_ids=parameters.get_message_ids(), + validity_period=parameters.get_validity_period(), + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(MessageQuery.__name__), + value=message_query.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class QueryHeaderService(AbstractService): + + @staticmethod + def encode(parameters: QueryHeaderParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.FEED_HEADER_QUERY.value + ) + + message_query = MessageQuery( + senders=parameters.get_senders(), + message_ids=parameters.get_message_ids(), + validity_period=parameters.get_validity_period(), + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(MessageQuery.__name__), + value=message_query.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message + + +class SubscriptionService(AbstractService): + + @staticmethod + def encode(parameters: SubscriptionParameters) -> EncodedMessage: + message_header_parameters = MessageHeaderParameters( + application_message_id=parameters.get_application_message_id(), + application_message_seq_no=parameters.get_application_message_seq_no(), + team_set_context_id=parameters.get_team_set_context_id(), + mode=RequestEnvelope.Mode.Value("DIRECT"), + technical_message_type=TechnicalMessageType.SUBSCRIPTION.value + ) + + subscription = Subscription( + technical_message_types=parameters.get_subscription_items(), + ) + + message_payload_parameters = MessagePayloadParameters( + type_url=TypeUrl.get_type_url(Subscription.__name__), + value=subscription.SerializeToString() + ) + + message_content = encode_message(message_header_parameters, message_payload_parameters) + encoded_message = EncodedMessage( + id_=new_uuid(), + content=message_content + ) + + return encoded_message diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index 20fcf6fa..a1e0d61c 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,3 +1,4 @@ +import base64 from abc import ABC, abstractmethod from agrirouter.constants.media_types import ContentTypes @@ -34,16 +35,17 @@ def __init__(self, def get_header(self) -> dict: return self.params - def sign(self, signature): - self.params["X-Agrirouter-Signature"] = signature + def sign(self, signature: bytes): + print(signature) + self.params["X-Agrirouter-Signature"] = base64.b64encode(signature).decode() + print(self.params["X-Agrirouter-Signature"]) def _set_params(self, reg_code: str, application_id: str, signature: str, content_type: str): header = dict() header["Authorization"] = f"Bearer {reg_code}" header["Content-Type"] = content_type header["X-Agrirouter-ApplicationId"] = application_id - if signature: - header["X-Agrirouter-Signature"] = signature + header["X-Agrirouter-Signature"] = signature if signature else "" self.params = header diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 0d0094e6..155d08dc 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -1,3 +1,5 @@ +import json + import requests from agrirouter.environments.environmental_services import EnvironmentalService @@ -6,7 +8,8 @@ from agrirouter.onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter from agrirouter.onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest from agrirouter.onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, CUOnboardingResponse +from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, \ + CUOnboardingResponse class SoftwareOnboarding(EnvironmentalService): @@ -31,7 +34,7 @@ def _perform_request(self, params: BaseOnboardingParameter, url: str) -> request if request.is_signed: return requests.post( url=request.get_url(), - data=request.get_data(), + data=json.dumps(request.get_data()), headers=request.get_header() ) raise RequestNotSigned diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index f88be38c..1ca75312 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from datetime import datetime from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes @@ -20,15 +21,16 @@ def get_body_params(self, *args, **kwargs): class SoftwareOnboardingParameter(BaseOnboardingParameter): def __init__(self, + *, id_, application_id, certification_version_id, gateway_id, - utc_timestamp, time_zone, reg_code, + utc_timestamp=None, content_type=ContentTypes.APPLICATION_JSON.value, - certificate_type=CertificateTypes.P12.value, + certificate_type=CertificateTypes.PEM.value, ): self.id_ = id_ @@ -37,7 +39,8 @@ def __init__(self, self.certification_version_id = certification_version_id self.gateway_id = str(gateway_id) self.certificate_type = certificate_type - self.utc_timestamp = str(utc_timestamp) + self.utc_timestamp = str(utc_timestamp) if utc_timestamp \ + else datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") self.time_zone = str(time_zone) self.reg_code = reg_code diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index aae1e653..1e7113b0 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -19,7 +19,7 @@ def get_header(self): return self.header.get_header() def sign(self, private_key): - signature = create_signature(self.body.json(new_lines=False), private_key) + signature = create_signature(self.body.json(), private_key) self.header.sign(signature) @property diff --git a/agrirouter/onboarding/request_body.py b/agrirouter/onboarding/request_body.py index d1c108eb..951ab01d 100644 --- a/agrirouter/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -1,5 +1,6 @@ import json from abc import ABC, abstractmethod +from datetime import datetime from agrirouter.onboarding.enums import CertificateTypes, GateWays from agrirouter.onboarding.exceptions import WrongCertificationType, WrongGateWay @@ -25,18 +26,21 @@ def json(self, *args, **kwargs): class SoftwareOnboardingBody(BaseOnboardingBody): def __init__(self, + *, id_, application_id, certification_version_id, gateway_id, certificate_type, - utc_timestamp, - time_zone + time_zone, + utc_timestamp=None ): self._validate_certificate_type(certificate_type) self._validate_gateway_id(gateway_id) + utc_timestamp = utc_timestamp if utc_timestamp else datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self._set_params( id_, application_id, @@ -70,11 +74,8 @@ def _set_params(self, "timeZone": time_zone, } - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result + def json(self) -> str: + return json.dumps(self.get_parameters()) @staticmethod def _validate_certificate_type(certificate_type: str) -> None: diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 4718b641..cab6bca0 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -6,6 +6,18 @@ class BaseOnboardingResonse: def __init__(self, http_response: Response): self.response: Response = http_response + def get_connection_criteria(self) -> dict: + response_data = self.data() + return response_data.get("connectionCriteria") + + def get_sensor_alternate_id(self): + response_data = self.data() + return response_data.get("sensorAlternateId") + + def get_authentication(self): + response_data = self.data() + return response_data.get("authentication") + @property def data(self): return self.response.json() @@ -30,7 +42,18 @@ class SoftwareOnboardingResponse(BaseOnboardingResonse): """ Response from onboarding request used for Farming Software or Telemetry Platform """ - pass + + def get_connection_criteria(self) -> dict: + response_data = self.data() + return response_data.get("connectionCriteria") + + def get_sensor_alternate_id(self): + response_data = self.data() + return response_data.get("sensorAlternateId") + + def get_authentication(self): + response_data = self.data() + return response_data.get("authentication") class CUOnboardingResponse(BaseOnboardingResonse): diff --git a/agrirouter/utils/__init__.py b/agrirouter/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py new file mode 100644 index 00000000..71f4c6df --- /dev/null +++ b/agrirouter/utils/type_url.py @@ -0,0 +1,45 @@ +from agrirouter.generated.cloud_provider_integration.cloud_virtualized_app_registration_pb2 import OnboardingResponse, \ + OnboardingRequest +from agrirouter.generated.commons.message_pb2 import Messages +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import MessageDelete, MessageConfirm, \ + MessageQuery +from agrirouter.generated.messaging.response.payload.account.endpoints_pb2 import ListEndpointsResponse +from agrirouter.generated.messaging.response.payload.feed.feed_response_pb2 import HeaderQueryResponse, \ + MessageQueryResponse +from agrirouter.messaging.exceptions import TypeUrlNotFoundError + + +class TypeUrl: + prefix = "types.agrirouter.com/" + + @classmethod + def get_type_url(cls, class_): + if class_.__name__ == Messages.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == ListEndpointsResponse.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == HeaderQueryResponse.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == MessageQueryResponse.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == MessageDelete.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == MessageConfirm.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == OnboardingResponse.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == OnboardingRequest.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == CapabilitySpecification.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == Subscription.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == MessageQuery.__name__: + return cls.prefix + class_.__name__ + elif class_.__name__ == ListEndpointsQuery.__name__: + return cls.prefix + class_.__name__ + else: + raise TypeUrlNotFoundError(f"The {class_} type url not found") diff --git a/agrirouter/utils/utc_time_util.py b/agrirouter/utils/utc_time_util.py new file mode 100644 index 00000000..b86f3afb --- /dev/null +++ b/agrirouter/utils/utc_time_util.py @@ -0,0 +1,6 @@ +from datetime import datetime + + +def now_as_utc_timestamp(): + timestamp = datetime.utcnow() + return timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") diff --git a/agrirouter/utils/uuid_util.py b/agrirouter/utils/uuid_util.py new file mode 100644 index 00000000..0c9bd872 --- /dev/null +++ b/agrirouter/utils/uuid_util.py @@ -0,0 +1,5 @@ +import uuid + + +def new_uuid(): + return uuid.uuid4() diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..c36194f0 --- /dev/null +++ b/conftest.py @@ -0,0 +1,13 @@ +import pytest +from tests.constants import public_key, private_key, auth_result_url + + +@pytest.fixture(scope="session") +def authorization(): + from agrirouter.auth.auth import Authorization + + auth_client = Authorization("QA", public_key=public_key, private_key=private_key) + auth_response = auth_client.extract_auth_response(auth_result_url) + auth_client.verify_auth_response(auth_response) + auth_data = auth_response.get_auth_result() + return auth_data diff --git a/examples.txt b/examples.txt new file mode 100644 index 00000000..f0fa895a --- /dev/null +++ b/examples.txt @@ -0,0 +1,72 @@ +>>> private_key = ... # store here your private key you get in AR UI during application creation +>>> public_key = ... # store here your public key you get in AR UI during application creation +>>> application_id = "8c947a45-c57d-4fd2-affc-206e2sdg3a50" # # store here your application id. You can find it in AR UI + + +>>> ######################################################## +>>> # Authorization + +>>> import agrirouter as ar + +>>> auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") +>>> auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) +>>> auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization + +>>> auth_result_url = ... # the url the user was redirected after his authorization. +>>> auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response containing the results of the auth process +>>> auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + +>>> auth_response.is_successfull # True if user accepted application, False if he rejected +True +>>> auth_response.is_valid # Result of verification, if False, response was not validated by public key. Doesn't indicate was the auth successfull. Accessible only after response verifying +True + +>>> # Get dict containing data from auth process you will use for futher communication. +>>> # If auth was rejected, contains {"error"} key. +>>> # If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys +>>> # Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. +>>> auth_data = auth_response.get_auth_result() + +{'credentials': {'account': '0ba223gh-cg3b-4te5-boc9-77ghcfn136a0', + 'expires': '2021-09-23T16:08:43.888Z', + 'regcode': '8eloz190fd'}, + 'signature': 'SULf4QMWAfG4/EyT0rejkRfApnkJIOs4sxI5wxeB8TkIiv0MR6YFKw1tPIkM4lluZKHEIgr5WvM3b3op4I9TtEbzZf995R8GIlNP6yyP51TF/4vZMbka0q+2g1o0qw/yuDQcGz1RpOJWCuBOjMXu9quzGO8xvDW7LjrN+MA9rzJZYb1toNf51O0sdg21oLvrKrqvaErKcIoRJtTVJ51awOWMARDkGZahcRdWrZbdGUbQwIyKJQu4vH8+4ytlyXPSWEYwKE2VFoAjhzWsKODdRRxDbNNLWsW8sxKamdsPmOC8inHUFsFNoxLbwZEnKROm2s3OfKGYuibXOpXw==', + 'state': '46c81f94-d117-4658-9a38-a85692448219', + 'token': 'eyJhY2sdoT4IjoiMGJhMjRlZWUtYzMwYi00N2U1LWJkYzktNzcwM2NmYjEzNmEwIiwicmVnY29kZSI6IjhlYWNiMTk4ZmMiLCJleHBpcmVzIopGjjAyMS0wOS0y1O5xNjowODo0My44ODhaIn0='} + + + +>>> ######################################################## + +>>> # Onboarding + + +>>> from agrirouter.onboarding.enums import GateWays + +>>> id_ = "mydeviceid" +>>> certification_version_id = ... # get from AR UI +>>> utc_timestamp = "2018-06-20T07:29:23.457Z" +>>> time_zone = "+03:00" + +>>> onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) +>>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, utc_timestamp=utc_timestamp, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +>>> onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) +>>> onboarding_response = onboarding_client.onboard(onboarding_parameters) +>>> onboarding_response.status_code() +>>> onboarding_response.data() # or onboarding_response.text() + +{ + "authentication": { + "certificate": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n...\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n", + "secret": "77R8cjOGi9yTCBt2", + "type": "PEM" + }, + "capabilityAlternateId": "7bc8ab05-a0de-40db-a259-7deefb1265e9", + "connectionCriteria": { + "gatewayId": "3", + "measures": "https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/c067272a-d3a7-4dcf-ab58-5c45ba66ad60", + "commands": "https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/c067272a-d3a7-4dcf-ab58-5c45ba66ad60" + }, + "deviceAlternateId": "c067272a-d3a7-4dcf-ab58-5c45ba66ad60", + "sensorAlternateId": "5564ce96-385f-448a-9502-9ea3c940a259" +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e2da2ee2..9cd51d4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,12 @@ cffi==1.14.6 charset-normalizer==2.0.6 cryptography==3.4.8 idna==3.2 +paho-mqtt==1.5.1 +protobuf==3.18.0 pycparser==2.20 requests==2.26.0 urllib3==1.26.7 +pytest==6.2.5 +black==21.9b0 +flake8==3.9.2 +pyflakes==2.3.1 diff --git a/tests/auth_test/test_response.py b/tests/auth_test/test_response.py new file mode 100644 index 00000000..75d0f679 --- /dev/null +++ b/tests/auth_test/test_response.py @@ -0,0 +1,22 @@ +"""Tests agrirouter/auth/response.py""" + +import re +from agrirouter.auth.response import AuthResponse + + +def test_decode_token(): + token = ( + "eyJhY2NvdW50IjoiMGJhMjRlZWUtYzMwYi00N2U1LWJkYzktNzcwM" + "2NmYjEzNmEwIiwicmVnY29kZSI6IjhlYWNiMTk4ZmMiLCJleHBpcm" + "VzIjoiMjAyMS0wOS0yM1QxNjowODo0My44ODhaIn0=" + ) + decoded_token = AuthResponse.decode_token(token) + assert isinstance(decoded_token["account"], str) + assert isinstance(decoded_token["expires"], str) + assert re.search(r"[\w]", decoded_token["regcode"]) + assert re.search(r"[\w]", decoded_token["account"]) + + +def test_get_auth_result(authorization): + assert isinstance(AuthResponse(authorization).get_auth_result(), dict) + assert AuthResponse(authorization).get_auth_result()["credentials"] diff --git a/tests/constants.py b/tests/constants.py new file mode 100644 index 00000000..c07044eb --- /dev/null +++ b/tests/constants.py @@ -0,0 +1,60 @@ +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" +ENV = "QA" +auth_result_url = ( + "http://fuf.me/?state=46c81f94-d117-4658-9a38-a85692448219&token=eyJhY2NvdW50IjoiMGJhMjRlZWUtYzMwY" + "i00N2U1LWJkYzktNzcwM2NmYjEzNmEwIiwicmVnY29kZSI6IjhlYWNiMTk4ZmMiLCJleHBpcmVzIjoiMjAyMS0wOS0yM1QxNj" + "owODo0My44ODhaIn0%3D&signature=SUL9SQMWAfG4%2FEyT0rejkRfAyioxJIOs4sxI5wxeB8TkIiv0MR6YFKw1tPIkM4ll" + "uZKHEIgr5WvM3b3SvII9TtEbzZf995R8GIlNP6yyP51TF%2F4vZMbkMjq%2B2g1o0qw%2FyuDQcGz1RpOJWCuBOjMXu9quzGO" + "8xvDW7LjrN%2BMA9rzJZYb1toNf51O0eO4BDWL5L1oLvrKrqvaErKcIoRJtTVJ51awOWMARDkGZahcRdWrZbdGUbQwIyKJQu4" + "vH8%2B4ytlyXPSWEYwKE2VFoAjhzWsKODdRRxDbNNLWsW8sxKamdXjSOC8inHUFsFNoxLbwZEnKROm2s3OfKGYuibXOpXw%3D%3D" +) +private_key = ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8WVUWBhEiQYvo\n" + "DL9q/Z5LbIhqDtcdFYWBUV7A96AXJaNRA9J7m3a6es05S7pRynv9yT8i+bhz/qDf\n" + "18MPOaqLglTw58ylVtTGmIlIZr85KJecO5aQnKN1pz8LyKnQSk4IWMBZ0T6mcaEO\n" + "fLVcDCmLi3T9a07xT4g5i2PGnLI6nd1pf3sLuKEzUjp455xU3oJrP0HfwSc+DXZQ\n" + "YDk2Rk6mfPgEczBhRmqDFp44+WHgyq4CcXl92M5XdOSYVaRs/W6fcEmRTqjBxp1u\n" + "M3kNAIaOC4XNfxVV8kSCfZW0gVtuGqxGK+80OZay3Vc1nQZGm+IUVBHKbPVauhXS\n" + "UH0/28qbAgMBAAECggEANVX8vMBaEL/L/RnDCOqp7UTeOl5adx91j2G5+d4FhRiA\n" + "73usGpmzHOqSe/OgXvH+e6cGDIL3w00rREgGsiSL0XbGU/PoJTf6CAUA9zI1W1vN\n" + "1w2evPPGbBZAybb4s4WfJEjxq12QJrUNvRr+hoLhLuV+axb8o2P4uQbqab9Mz0ER\n" + "lczCbHi4VDs1fwmNR3o47T1J4Qffzv1nMlor3pSrDzRDebic7/DC5JFkYZNGUtHk\n" + "jKDF5Uv7Vzxgb4Of+i3JA5mRMqvG33pdenvvetwl9X69WOiC29bVlymSHyybBE4A\n" + "ItfCAHIiY3nUL7UqzoIXpsyPs3ftkiy3Hn7isVSpLQKBgQDjadkGlqIgXCKZ8RS6\n" + "a4iLTTTlh8Ur+vMrejBLPul1oxz2dRWZy8zykfNN2MPz7q2xT8wXGuxgj+jei/fi\n" + "Gk08+UudMhV5Dtshb3fFq0NFCBe1ZUEX/wAcKC4Ed9xuuHpe7HOKAG0AsnzS8MPC\n" + "lcMiL1/vz0GuRbsiyMY6hXweZQKBgQDUBmQNqOBWDTQkO/8MFHopo6Ju9iNvZ4fC\n" + "u4SWqL+5BO3nnQHAQyslsj8FNilqhgMI+zaFFbZMZPv5opBSaAR0CQanKxMe3c9I\n" + "XYkAJH2+M0fpp80LtxwShD411UDhIypzumfKe8vUXRW/8TWfl6VidfEVjxw6Rc2D\n" + "g9btI4k0/wKBgQC42plnGZq/4yTdLXJD9pUPZrrQuQQ1M8/mT3RiNclfri8kxxe/\n" + "5EG8C5dSeBkQd7sInmyve1sZQuFvxSbBy89s+NfV95gsxz6odwtMymHsAyACe0Pm\n" + "VYmpWZ/OUgAEoEAYWOuyCZaRMoT0knEOAt6TMx8wt7AUEOqE497+QvMZYQKBgQC6\n" + "ARlJenvEQjUaDKBFYrmBShK4MasIktThG0zINyZrFE35wR3GI6b4nRT4Z3mSABst\n" + "h+Vef5u8DWOYrurZwHMXsMtrYDiX/ZNZMuV7gIfnkmlmLFWQD4XLIMTKyVjvqcAW\n" + "YtOnKU+58CeiieO3LHxkkn97oF7tKEuRMtock+5M1QKBgC2fquqxXMrBEIoMGCZs\n" + "ooU5V9gOjFVKC52VWnTNgmOWTqgZuqxPJtCTN5wPvhOSggQuHPwBHa9ioshJ0dGE\n" + "6jdxGaJjAc82q2KZu9VEqoH/Xa2aS8dPEHwfJtzUVTia6WkrFtMFNaDMFd6byWDQ\n" + "ai+T4i2J3/SDL0BfsFWdQuje\n" + "-----END PRIVATE KEY-----" +) + +public_key = ( + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFlVFgYRIkGL6Ay/av2e\n" + "S2yIag7XHRWFgVFewPegFyWjUQPSe5t2unrNOUu6Ucp7/ck/Ivm4c/6g39fDDzmq\n" + "i4JU8OfMpVbUxpiJSGa/OSiXnDuWkJyjdac/C8ip0EpOCFjAWdE+pnGhDny1XAwp\n" + "i4t0/WtO8U+IOYtjxpyyOp3daX97C7ihM1I6eOecVN6Caz9B38EnPg12UGA5NkZO\n" + "pnz4BHMwYUZqgxaeOPlh4MquAnF5fdjOV3TkmFWkbP1un3BJkU6owcadbjN5DQCG\n" + "jguFzX8VVfJEgn2VtIFbbhqsRivvNDmWst1XNZ0GRpviFFQRymz1WroV0lB9P9vK\n" + "mwIDAQAB\n" + "-----END PUBLIC KEY-----" +) + +AR_PUBLIC_KEY = ( + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n3HvTfUV" + "cismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4" + "u2FVJphC1m2BfLxelGLjzx\nVAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n1/7MLD6IE2+" + "HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\nY8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wN" + "vxXWtkng3yh+7WiZ/Y\nqwIDAQAB\n-----END PUBLIC KEY-----" +) diff --git a/tests/enviroments_test/test_environmental_services.py b/tests/enviroments_test/test_environmental_services.py new file mode 100644 index 00000000..611e3172 --- /dev/null +++ b/tests/enviroments_test/test_environmental_services.py @@ -0,0 +1,11 @@ +"""Test agrirouter/environments/environmental_services.py""" + +import pytest +from agrirouter.environments.exceptions import InvalidEnvironmentSetup +from agrirouter.environments.environmental_services import EnvironmentalService +from tests.constants import ENV + + +def test_arclient_set_env(): + with pytest.raises(InvalidEnvironmentSetup): + assert EnvironmentalService("WRONG")._set_env("WRONG") diff --git a/tests/enviroments_test/test_environments.py b/tests/enviroments_test/test_environments.py new file mode 100644 index 00000000..8619bdc4 --- /dev/null +++ b/tests/enviroments_test/test_environments.py @@ -0,0 +1,213 @@ +"""Test agrirouter/environments/environments.py""" + +import pytest +from agrirouter.environments.environments import ProductionEnvironment, QAEnvironment +from tests.constants import application_id, auth_result_url + + +class TestProductionEnvironment: + def test_get_base_url(self): + assert ( + ProductionEnvironment().get_base_url() + == ProductionEnvironment._ENV_BASE_URL + ) + + def test_get_api_prefix(self): + assert ( + ProductionEnvironment().get_api_prefix() + == ProductionEnvironment._API_PREFIX + ) + + def test_get_registration_service_url(self): + assert ( + ProductionEnvironment().get_registration_service_url() + == ProductionEnvironment._REGISTRATION_SERVICE_URL + ) + + def test_get_onboard_url(self): + assert ( + ProductionEnvironment().get_onboard_url() + == ProductionEnvironment._REGISTRATION_SERVICE_URL + + ProductionEnvironment._API_PREFIX + + "/registration/onboard" + ) + + def test_get_secured_onboard_url(self): + assert ( + ProductionEnvironment().get_secured_onboard_url() + == ProductionEnvironment._REGISTRATION_SERVICE_URL + + ProductionEnvironment._API_PREFIX + + "/registration/onboard/request" + ) + + def test_get_verify_onboard_request_url(self): + assert ( + ProductionEnvironment().get_verify_onboard_request_url() + == ProductionEnvironment._REGISTRATION_SERVICE_URL + + ProductionEnvironment._API_PREFIX + + "/registration/onboard/verify" + ) + + def test_get_revoke_url(self): + assert ( + ProductionEnvironment().get_revoke_url() + == ProductionEnvironment._REGISTRATION_SERVICE_URL + + ProductionEnvironment._API_PREFIX + + "/registration/onboard/revoke" + ) + + def test_get_agrirouter_login_url(self): + assert ( + ProductionEnvironment().get_agrirouter_login_url() + == ProductionEnvironment._ENV_BASE_URL + + ProductionEnvironment._AGRIROUTER_LOGIN_URL + ) + + def test_get_secured_onboarding_authorization_url(self): + assert ProductionEnvironment().get_secured_onboarding_authorization_url( + application_id, str, "state", auth_result_url + ) == ProductionEnvironment._ENV_BASE_URL + ProductionEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="state", + redirect_uri=auth_result_url, + ) + with pytest.raises(AssertionError): + assert ProductionEnvironment().get_secured_onboarding_authorization_url( + application_id, str, "state", auth_result_url + ) == ProductionEnvironment._ENV_BASE_URL + ProductionEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="123", + redirect_uri=auth_result_url, + ) + with pytest.raises(AssertionError): + assert ProductionEnvironment().get_secured_onboarding_authorization_url( + application_id, dict, "state", auth_result_url + ) == ProductionEnvironment._ENV_BASE_URL + ProductionEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="state", + redirect_uri=auth_result_url, + ) + + def test_get_mqtt_server_url(self): + assert ProductionEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == ProductionEnvironment._MQTT_URL_TEMPLATE.format( + host="localhost", port="5000" + ) + with pytest.raises(AssertionError): + assert ProductionEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == ProductionEnvironment._MQTT_URL_TEMPLATE.format( + host="127.0.0.1", port="5000" + ) + with pytest.raises(AssertionError): + assert ProductionEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == ProductionEnvironment._MQTT_URL_TEMPLATE.format( + host="localhost", port="80" + ) + + def test_get_env_public_key(self): + assert ( + ProductionEnvironment().get_env_public_key() + == ProductionEnvironment.AR_PUBLIC_KEY + ) + + +class TestQAEnvironment: + def test_get_base_url(self): + assert QAEnvironment().get_base_url() == QAEnvironment._ENV_BASE_URL + + def test_get_api_prefix(self): + assert QAEnvironment().get_api_prefix() == QAEnvironment._API_PREFIX + + def test_get_registration_service_url(self): + assert ( + QAEnvironment().get_registration_service_url() + == QAEnvironment._REGISTRATION_SERVICE_URL + ) + + def test_get_onboard_url(self): + assert ( + QAEnvironment().get_onboard_url() + == QAEnvironment._REGISTRATION_SERVICE_URL + + QAEnvironment._API_PREFIX + + "/registration/onboard" + ) + + def test_get_secured_onboard_url(self): + assert ( + QAEnvironment().get_secured_onboard_url() + == QAEnvironment._REGISTRATION_SERVICE_URL + + QAEnvironment._API_PREFIX + + "/registration/onboard/request" + ) + + def test_get_verify_onboard_request_url(self): + assert ( + QAEnvironment().get_verify_onboard_request_url() + == QAEnvironment._REGISTRATION_SERVICE_URL + + QAEnvironment._API_PREFIX + + "/registration/onboard/verify" + ) + + def test_get_revoke_url(self): + assert ( + QAEnvironment().get_revoke_url() + == QAEnvironment._REGISTRATION_SERVICE_URL + + QAEnvironment._API_PREFIX + + "/registration/onboard/revoke" + ) + + def test_get_agrirouter_login_url(self): + assert ( + QAEnvironment().get_agrirouter_login_url() + == QAEnvironment._ENV_BASE_URL + QAEnvironment._AGRIROUTER_LOGIN_URL + ) + + def test_get_secured_onboarding_authorization_url(self): + assert QAEnvironment().get_secured_onboarding_authorization_url( + application_id, str, "state", auth_result_url + ) == QAEnvironment._ENV_BASE_URL + QAEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="state", + redirect_uri=auth_result_url, + ) + with pytest.raises(AssertionError): + assert QAEnvironment().get_secured_onboarding_authorization_url( + application_id, str, "state", auth_result_url + ) == QAEnvironment._ENV_BASE_URL + QAEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="123", + redirect_uri=auth_result_url, + ) + with pytest.raises(AssertionError): + assert QAEnvironment().get_secured_onboarding_authorization_url( + application_id, dict, "state", auth_result_url + ) == QAEnvironment._ENV_BASE_URL + QAEnvironment._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=str, + state="state", + redirect_uri=auth_result_url, + ) + + def test_get_mqtt_server_url(self): + assert QAEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == QAEnvironment._MQTT_URL_TEMPLATE.format(host="localhost", port="5000") + with pytest.raises(AssertionError): + assert QAEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == QAEnvironment._MQTT_URL_TEMPLATE.format(host="127.0.0.1", port="5000") + with pytest.raises(AssertionError): + assert QAEnvironment().get_mqtt_server_url( + "localhost", "5000" + ) == QAEnvironment._MQTT_URL_TEMPLATE.format(host="localhost", port="80") + + def test_get_env_public_key(self): + assert QAEnvironment().get_env_public_key() == QAEnvironment.AR_PUBLIC_KEY diff --git a/tests/messaging_test/test_request.py b/tests/messaging_test/test_request.py new file mode 100644 index 00000000..5b978ec6 --- /dev/null +++ b/tests/messaging_test/test_request.py @@ -0,0 +1,15 @@ +"""Test agrirouter/messaging/request.py""" + +from agrirouter.messaging.messages import Message +from agrirouter.messaging.request import MessageRequest + + +def test_json_serialize(): + message_request = MessageRequest( + sensor_alternate_id="1", + capability_alternate_id="1", + messages=[Message(content="content")], + ).json_serialize() + assert isinstance(message_request, dict) + assert message_request["capabilityAlternateId"] == "1" + assert message_request["sensorAlternateId"] == "1" diff --git a/tests/onboarding_test/test_headers.py b/tests/onboarding_test/test_headers.py new file mode 100644 index 00000000..4a412082 --- /dev/null +++ b/tests/onboarding_test/test_headers.py @@ -0,0 +1,23 @@ +"""Test agrirouter/onboarding/headers.py""" +from agrirouter.constants.media_types import ContentTypes +from agrirouter.onboarding.headers import SoftwareOnboardingHeader + + +class TestSoftwareOnboardingHeader: + reg_code = "1AC2cs21W" + test_object = SoftwareOnboardingHeader(reg_code=reg_code, application_id=1) + test_object_1 = SoftwareOnboardingHeader( + reg_code=reg_code, application_id=1, content_type="json" + ) + + def test_get_header(self): + assert isinstance(self.test_object.get_header(), dict) + assert ( + self.test_object.get_header()["Authorization"] == "Bearer " + self.reg_code + ) + assert ( + self.test_object.get_header()["Content-Type"] + == ContentTypes.APPLICATION_JSON.value + ) + + assert self.test_object_1.get_header()["Content-Type"] == "json" diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..2aae4364 --- /dev/null +++ b/tox.ini @@ -0,0 +1,16 @@ +[flake8] +ignore = D203 +exclude = + .git, + __pycache__, + .pytest_cache + agrirouter_sdk_python.egg-info, + assets, + old, + build, + dist, + venv, + conftest.py, + *_pb2.py +max-line-length = 120 +max-complexity = 10 From 65e1a750c6cf8ae395765bca0df04c0ddd47ec37 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 15 Oct 2021 13:18:54 +0300 Subject: [PATCH 19/78] Add onboarding dto (#11) --- agrirouter/auth/response.py | 4 +- agrirouter/messaging/certification.py | 5 +- agrirouter/messaging/clients/http.py | 58 +++- agrirouter/messaging/services/commons.py | 22 +- agrirouter/messaging/services/http/outbox.py | 23 +- agrirouter/onboarding/dto.py | 289 +++++++++++++++++++ agrirouter/onboarding/headers.py | 6 +- agrirouter/onboarding/onboarding.py | 4 +- agrirouter/onboarding/request.py | 8 +- agrirouter/onboarding/response.py | 95 ++++-- agrirouter/onboarding/signature.py | 12 +- examples.txt | 60 +++- 12 files changed, 498 insertions(+), 88 deletions(-) create mode 100644 agrirouter/onboarding/dto.py diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index a711c4a6..9aac71b3 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -46,12 +46,14 @@ def verify(self, public_key) -> None: encoded_data = self._state + self._token unquoted_signature = unquote(self._signature) encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) - self._was_verified = True + try: verify_signature(encoded_data, encoded_signature, public_key) except InvalidSignature: print("Response is invalid: invalid signature.") self._is_valid = False + finally: + self._was_verified = True self._is_valid = True diff --git a/agrirouter/messaging/certification.py b/agrirouter/messaging/certification.py index 5579b870..50de6778 100644 --- a/agrirouter/messaging/certification.py +++ b/agrirouter/messaging/certification.py @@ -5,10 +5,11 @@ import tempfile -from agrirouter.onboarding.response import BaseOnboardingResonse +from agrirouter.onboarding.response import SoftwareOnboardingResponse -def create_certificate_file(onboard_response: BaseOnboardingResonse): +def create_certificate_file_from_pen(onboard_response: SoftwareOnboardingResponse): + dir_ = tempfile.mkdtemp() prefix = onboard_response.get_sensor_alternate_id() data = onboard_response.get_authentication().get_certificate() diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 08530c77..fa3110e9 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -1,16 +1,62 @@ +import http.client +import json +import os +import ssl + +from agrirouter.messaging.certification import create_certificate_file_from_pen +from agrirouter.onboarding.dto import ConnectionCriteria +from agrirouter.onboarding.response import SoftwareOnboardingResponse + + class HttpClient: headers = {"Content-Type": "application/json"} - def __init__(self, - on_message_callback: callable, - timeout=20 - ): + def __init__( + self, + on_message_callback: callable, + timeout=20 + ): self.on_message_callback = on_message_callback self.timeout = timeout - def publish(self): - pass + @staticmethod + def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.load_cert_chain( + certfile=certificate_file_path, + keyfile=certificate_file_path, + password=onboard_response.get_authentication().get_secret(), + ) + connection = http.client.HTTPSConnection( + host=onboard_response.connection_criteria.get_host(), + port=onboard_response.connection_criteria.get_port(), + context=context + ) + return connection + + def send(self, method: str, onboard_response: SoftwareOnboardingResponse, request_body=None): + certificate_file_path = create_certificate_file_from_pen(onboard_response) + try: + connection = self.make_connection(certificate_file_path, onboard_response) + if request_body is not None: + connection.request( + method=method, + url=onboard_response.get_connection_criteria().get_measures(), + headers=self.headers, + body=json.dumps(request_body) + ) + else: + connection.request( + method=method, + url=onboard_response.get_connection_criteria().get_measures(), + headers=self.headers, + ) + response = connection.getresponse() + finally: + os.remove(certificate_file_path) + + return response def subscribe(self): pass diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 3e368632..939a6fdc 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -4,6 +4,7 @@ import requests from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message from agrirouter.messaging.request import MessageRequest @@ -32,21 +33,16 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): + def __init__(self, on_message_callback, timeout): + self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) - cert_file_path = create_certificate_file(parameters.get_onboarding_response()) - try: - response = requests.post( - url=parameters.get_onboarding_response().get_connection_criteria().get_measures(), - headers={"Content-type": "application/json"}, - data=request.json_serialize(), - cert=( - cert_file_path, - parameters.get_onboarding_response().get_authentication().get_secret() - ), - ) - finally: - os.remove(cert_file_path) + response = self.client.send( + "POST", + parameters.get_onboarding_response(), + request + ) result = MessagingResult([parameters.get_message_id()]) return result diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index b81bfcbf..72328072 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -2,26 +2,23 @@ import requests +from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.result import OutboxResponse -from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.certification import create_certificate_file_from_pen class OutboxService: + def __init__(self, on_message_callback, timeout): + self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def fetch(self, onboarding_response) -> OutboxResponse: - cert_file_path = create_certificate_file(onboarding_response) - try: - response = requests.get( - url=onboarding_response.get_connection_criteria().get_commands(), - headers={"Content-type": "application/json"}, - cert=( - cert_file_path, - onboarding_response.get_authentication().get_secret() - ), - ) - finally: - os.remove(cert_file_path) + response = self.client.send( + "GET", + onboarding_response, + None + ) outbox_response = OutboxResponse(status_code=response.status_code) outbox_response.json_deserialize(response.json()["contents"]) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py new file mode 100644 index 00000000..233c907e --- /dev/null +++ b/agrirouter/onboarding/dto.py @@ -0,0 +1,289 @@ +import json +from typing import Union + +from agrirouter.messaging.exceptions import WrongFieldError + + +class ConnectionCriteria: + CLIENT_ID = 'clientId' + COMMANDS = 'commands' + GATEWAY_ID = 'gatewayId' + HOST = 'host' + MEASURES = 'measures' + PORT = 'port' + + def __init__(self, + *, + gateway_id: str = None, + measures: str = None, + commands: str = None, + host: str = None, + port: str = None, + client_id: str = None + ): + self.gateway_id = gateway_id + self.measures = measures + self.commands = commands + self.host = host + self.port = port + self.client_id = client_id + + def json_serialize(self) -> dict: + return { + self.GATEWAY_ID: self.gateway_id, + self.MEASURES: self.measures, + self.COMMANDS: self.commands, + self.PORT: self.port, + self.CLIENT_ID: self.client_id + } + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.GATEWAY_ID: + self.gateway_id = value + elif key == self.MEASURES: + self.measures = value + elif key == self.COMMANDS: + self.commands = value + elif key == self.HOST: + self.host = value + elif key == self.PORT: + self.port = value + elif key == self.CLIENT_ID: + self.client_id = value + else: + raise WrongFieldError(f"Unknown field {key} for Connection Criteria class") + + def get_gateway_id(self) -> str: + return self.gateway_id + + def set_gateway_id(self, gateway_id: str) -> None: + self.gateway_id = gateway_id + + def get_measures(self) -> str: + return self.measures + + def set_measures(self, measures: str) -> None: + self.measures = measures + + def get_commands(self) -> str: + return self.commands + + def set_commands(self, commands: str) -> None: + self.commands = commands + + def get_host(self) -> str: + return self.host + + def set_host(self, host: str) -> None: + self.host = host + + def get_port(self) -> str: + return self.port + + def set_port(self, port: str) -> None: + self.port = port + + def get_client_id(self) -> str: + return self.client_id + + def set_client_id(self, client_id: str) -> None: + self.client_id = client_id + + +class Authentication: + TYPE = 'type' + SECRET = 'secret' + CERTIFICATE = 'certificate' + + def __init__(self, + *, + type: str = None, + secret: str = None, + certificate: str = None, + ): + self.type = type + self.secret = secret + self.certificate = certificate + + def json_serialize(self) -> dict: + return { + self.TYPE: self.type, + self.SECRET: self.secret, + self.CERTIFICATE: self.certificate, + } + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.TYPE: + self.type = value + elif key == self.SECRET: + self.secret = value + elif key == self.CERTIFICATE: + self.certificate = value + else: + raise WrongFieldError(f"Unknown field {key} for Authentication class") + + def get_type(self) -> str: + return self.type + + def set_type(self, type: str) -> None: + self.type = type + + def get_secret(self) -> str: + return self.secret + + def set_secret(self, secret: str) -> None: + self.secret = secret + + def get_certificate(self) -> str: + return self.certificate + + def set_certificate(self, certificate: str) -> None: + self.certificate = certificate + + +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + +class AuthorizationToken: + ACCOUNT = 'account' + REGISTRATION_CODE = 'regcode' + EXPIRES = 'expires' + + def __init__(self, + *, + account: str = None, + regcode: str = None, + expires: str = None + ): + self.account = account + self.regcode = regcode + self.expires = expires + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.ACCOUNT: + self.account = value + elif key == self.REGISTRATION_CODE: + self.regcode = value + elif key == self.EXPIRES: + self.expires = value + else: + raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") + + def get_account(self) -> str: + return self.account + + def set_account(self, account: str) -> None: + self.account = account + + def get_regcode(self) -> str: + return self.regcode + + def set_regcode(self, regcode: str) -> None: + self.regcode = regcode + + def get_expires(self) -> str: + return self.expires + + def set_expires(self, expires: str) -> None: + self.expires = expires + + +class AuthorizationResult: + def __init__(self, + *, + authorization_url: str = None, + state: str = None, + ): + self.authorization_url = authorization_url + self.state = state + + def get_authorization_url(self) -> str: + return self.authorization_url + + def set_authorization_url(self, authorization_url: str) -> None: + self.authorization_url = authorization_url + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + +class ErrorResponse: + def __init__(self, + *, + code, + message, + target, + details + ): + self.code = code + self.message = message + self.target = target + self.details = details + + def get_code(self) -> str: + return self.code + + def set_code(self, code: str) -> None: + self.code = code + + def get_message(self) -> str: + return self.message + + def set_message(self, message: str) -> None: + self.message = message + + def get_target(self) -> str: + return self.target + + def set_target(self, target: str) -> None: + self.target = target + + def get_details(self) -> str: + return self.details + + def set_details(self, details: str) -> None: + self.details = details diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index a1e0d61c..a19fe008 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -35,10 +35,8 @@ def __init__(self, def get_header(self) -> dict: return self.params - def sign(self, signature: bytes): - print(signature) - self.params["X-Agrirouter-Signature"] = base64.b64encode(signature).decode() - print(self.params["X-Agrirouter-Signature"]) + def sign(self, signature: str): + self.params["X-Agrirouter-Signature"] = signature def _set_params(self, reg_code: str, application_id: str, signature: str, content_type: str): header = dict() diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 155d08dc..ad07b896 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -30,11 +30,11 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) - request.sign(self._private_key) + request.sign(self._private_key, self._public_key) if request.is_signed: return requests.post( url=request.get_url(), - data=json.dumps(request.get_data()), + json=request.get_data(), headers=request.get_header() ) raise RequestNotSigned diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index 1e7113b0..e02e0ec7 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -1,6 +1,6 @@ from agrirouter.onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader from agrirouter.onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody -from agrirouter.onboarding.signature import create_signature +from agrirouter.onboarding.signature import create_signature, verify_signature class BaseOnboardingRequest: @@ -18,8 +18,10 @@ def get_data(self): def get_header(self): return self.header.get_header() - def sign(self, private_key): - signature = create_signature(self.body.json(), private_key) + def sign(self, private_key, public_key): + body = self.body.json().replace("\n", "") + signature = create_signature(body, private_key) + verify_signature(body, bytes.fromhex(signature), public_key) self.header.sign(signature) @property diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index cab6bca0..50826c4c 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,59 +1,92 @@ from requests import Response +from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication + class BaseOnboardingResonse: def __init__(self, http_response: Response): - self.response: Response = http_response - - def get_connection_criteria(self) -> dict: - response_data = self.data() - return response_data.get("connectionCriteria") - - def get_sensor_alternate_id(self): - response_data = self.data() - return response_data.get("sensorAlternateId") - - def get_authentication(self): - response_data = self.data() - return response_data.get("authentication") - @property - def data(self): - return self.response.json() + self._status_code = http_response.status_code + self._text = http_response.text @property def status_code(self): - return self.response.status_code + return self._status_code @property def text(self): - return self.response.text + return self._text class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): """ Response from verify request used for Farming Software or Telemetry Platform before onboarding """ - pass + + def __init__(self, http_response: Response): + super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + + self.account_id = response_body.get("accountId", None) + + self.error = ErrorResponse( + code=response_body.get("error").get("code"), + message=response_body.get("error").get("message"), + target=response_body.get("error").get("target"), + details=response_body.get("error").get("details"), + ) if response_body.get("error", None) else None + + def get_account_id(self) -> str: + return self.account_id class SoftwareOnboardingResponse(BaseOnboardingResonse): """ Response from onboarding request used for Farming Software or Telemetry Platform """ - - def get_connection_criteria(self) -> dict: - response_data = self.data() - return response_data.get("connectionCriteria") - - def get_sensor_alternate_id(self): - response_data = self.data() - return response_data.get("sensorAlternateId") - - def get_authentication(self): - response_data = self.data() - return response_data.get("authentication") + def __init__(self, http_response: Response): + super(SoftwareOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + + self.connection_criteria = ConnectionCriteria( + gateway_id=response_body.get("connectionCriteria").get("gatewayId"), + measures=response_body.get("connectionCriteria").get("measures"), + commands=response_body.get("connectionCriteria").get("commands"), + host=response_body.get("connectionCriteria").get("host"), + ) if response_body.get("connectionCriteria", None) else None + + self.authentication = Authentication( + type=response_body.get("authentication").get("type"), + secret=response_body.get("authentication").get("secret"), + certificate=response_body.get("authentication").get("certificate") + ) if response_body.get("authentication", None) else None + + self.capability_alternate_id = response_body.get("capabilityAlternateId", None) + self.device_alternate_id = response_body.get("deviceAlternateId", None) + self.sensor_alternate_id = response_body.get("sensorAlternateId", None) + + self.error = ErrorResponse( + code=response_body.get("error").get("code"), + message=response_body.get("error").get("message"), + target=response_body.get("error").get("target"), + details=response_body.get("error").get("details"), + ) if response_body.get("error", None) else None + + def get_connection_criteria(self) -> ConnectionCriteria: + return self.connection_criteria + + def get_authentication(self) -> Authentication: + return self.authentication + + def get_sensor_alternate_id(self) -> str: + return self.sensor_alternate_id + + def get_device_alternate_id(self) -> str: + return self.device_alternate_id + + def get_capability_alternate_id(self) -> str: + return self.capability_alternate_id class CUOnboardingResponse(BaseOnboardingResonse): diff --git a/agrirouter/onboarding/signature.py b/agrirouter/onboarding/signature.py index 6bedc3bd..64174adc 100644 --- a/agrirouter/onboarding/signature.py +++ b/agrirouter/onboarding/signature.py @@ -2,20 +2,24 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding -from pprint import pprint SIGNATURE_ALGORITHM = "SHA256withRSA" -def create_signature(request_body: str, private_key: str) -> bytes: +def to_hex(sign: bytes): + return sign.hex() + + +def create_signature(request_body: str, private_key: str) -> str: private_key_bytes = bytearray(private_key.encode('utf-8')) private_key_data = load_pem_private_key(private_key_bytes, None) signature = private_key_data.sign( - request_body.encode('utf-8'), + request_body.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256() ) - return signature + + return to_hex(signature) def verify_signature(request_body: str, signature: bytes, public_key: str) -> None: diff --git a/examples.txt b/examples.txt index f0fa895a..e59fe910 100644 --- a/examples.txt +++ b/examples.txt @@ -1,3 +1,44 @@ + +public_key = "-----BEGIN PUBLIC KEY-----\n" \ + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFlVFgYRIkGL6Ay/av2e\n" \ + "S2yIag7XHRWFgVFewPegFyWjUQPSe5t2unrNOUu6Ucp7/ck/Ivm4c/6g39fDDzmq\n" \ + "i4JU8OfMpVbUxpiJSGa/OSiXnDuWkJyjdac/C8ip0EpOCFjAWdE+pnGhDny1XAwp\n" \ + "i4t0/WtO8U+IOYtjxpyyOp3daX97C7ihM1I6eOecVN6Caz9B38EnPg12UGA5NkZO\n" \ + "pnz4BHMwYUZqgxaeOPlh4MquAnF5fdjOV3TkmFWkbP1un3BJkU6owcadbjN5DQCG\n" \ + "jguFzX8VVfJEgn2VtIFbbhqsRivvNDmWst1XNZ0GRpviFFQRymz1WroV0lB9P9vK\n" \ + "mwIDAQAB\n" \ + "-----END PUBLIC KEY-----" + +private_key = "-----BEGIN PRIVATE KEY-----\n" \ + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8WVUWBhEiQYvo\n" \ + "DL9q/Z5LbIhqDtcdFYWBUV7A96AXJaNRA9J7m3a6es05S7pRynv9yT8i+bhz/qDf\n" \ + "18MPOaqLglTw58ylVtTGmIlIZr85KJecO5aQnKN1pz8LyKnQSk4IWMBZ0T6mcaEO\n" \ + "fLVcDCmLi3T9a07xT4g5i2PGnLI6nd1pf3sLuKEzUjp455xU3oJrP0HfwSc+DXZQ\n" \ + "YDk2Rk6mfPgEczBhRmqDFp44+WHgyq4CcXl92M5XdOSYVaRs/W6fcEmRTqjBxp1u\n" \ + "M3kNAIaOC4XNfxVV8kSCfZW0gVtuGqxGK+80OZay3Vc1nQZGm+IUVBHKbPVauhXS\n" \ + "UH0/28qbAgMBAAECggEANVX8vMBaEL/L/RnDCOqp7UTeOl5adx91j2G5+d4FhRiA\n" \ + "73usGpmzHOqSe/OgXvH+e6cGDIL3w00rREgGsiSL0XbGU/PoJTf6CAUA9zI1W1vN\n" \ + "1w2evPPGbBZAybb4s4WfJEjxq12QJrUNvRr+hoLhLuV+axb8o2P4uQbqab9Mz0ER\n" \ + "lczCbHi4VDs1fwmNR3o47T1J4Qffzv1nMlor3pSrDzRDebic7/DC5JFkYZNGUtHk\n" \ + "jKDF5Uv7Vzxgb4Of+i3JA5mRMqvG33pdenvvetwl9X69WOiC29bVlymSHyybBE4A\n" \ + "ItfCAHIiY3nUL7UqzoIXpsyPs3ftkiy3Hn7isVSpLQKBgQDjadkGlqIgXCKZ8RS6\n" \ + "a4iLTTTlh8Ur+vMrejBLPul1oxz2dRWZy8zykfNN2MPz7q2xT8wXGuxgj+jei/fi\n" \ + "Gk08+UudMhV5Dtshb3fFq0NFCBe1ZUEX/wAcKC4Ed9xuuHpe7HOKAG0AsnzS8MPC\n" \ + "lcMiL1/vz0GuRbsiyMY6hXweZQKBgQDUBmQNqOBWDTQkO/8MFHopo6Ju9iNvZ4fC\n" \ + "u4SWqL+5BO3nnQHAQyslsj8FNilqhgMI+zaFFbZMZPv5opBSaAR0CQanKxMe3c9I\n" \ + "XYkAJH2+M0fpp80LtxwShD411UDhIypzumfKe8vUXRW/8TWfl6VidfEVjxw6Rc2D\n" \ + "g9btI4k0/wKBgQC42plnGZq/4yTdLXJD9pUPZrrQuQQ1M8/mT3RiNclfri8kxxe/\n" \ + "5EG8C5dSeBkQd7sInmyve1sZQuFvxSbBy89s+NfV95gsxz6odwtMymHsAyACe0Pm\n" \ + "VYmpWZ/OUgAEoEAYWOuyCZaRMoT0knEOAt6TMx8wt7AUEOqE497+QvMZYQKBgQC6\n" \ + "ARlJenvEQjUaDKBFYrmBShK4MasIktThG0zINyZrFE35wR3GI6b4nRT4Z3mSABst\n" \ + "h+Vef5u8DWOYrurZwHMXsMtrYDiX/ZNZMuV7gIfnkmlmLFWQD4XLIMTKyVjvqcAW\n" \ + "YtOnKU+58CeiieO3LHxkkn97oF7tKEuRMtock+5M1QKBgC2fquqxXMrBEIoMGCZs\n" \ + "ooU5V9gOjFVKC52VWnTNgmOWTqgZuqxPJtCTN5wPvhOSggQuHPwBHa9ioshJ0dGE\n" \ + "6jdxGaJjAc82q2KZu9VEqoH/Xa2aS8dPEHwfJtzUVTia6WkrFtMFNaDMFd6byWDQ\n" \ + "ai+T4i2J3/SDL0BfsFWdQuje\n" \ + "-----END PRIVATE KEY-----" + + >>> private_key = ... # store here your private key you get in AR UI during application creation >>> public_key = ... # store here your public key you get in AR UI during application creation >>> application_id = "8c947a45-c57d-4fd2-affc-206e2sdg3a50" # # store here your application id. You can find it in AR UI @@ -12,13 +53,13 @@ >>> auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) >>> auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization ->>> auth_result_url = ... # the url the user was redirected after his authorization. ->>> auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response containing the results of the auth process +>>> auth_result_url = ... # the url the user was redirected to after his authorization. +>>> auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process >>> auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR ->>> auth_response.is_successfull # True if user accepted application, False if he rejected +>>> auth_response.is_successful # True if user accepted application, False if he rejected True ->>> auth_response.is_valid # Result of verification, if False, response was not validated by public key. Doesn't indicate was the auth successfull. Accessible only after response verifying +>>> auth_response.is_valid # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying True >>> # Get dict containing data from auth process you will use for futher communication. @@ -45,15 +86,16 @@ True >>> id_ = "mydeviceid" >>> certification_version_id = ... # get from AR UI ->>> utc_timestamp = "2018-06-20T07:29:23.457Z" >>> time_zone = "+03:00" >>> onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) ->>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, utc_timestamp=utc_timestamp, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +>>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) >>> onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) +>>> onboarding_verifying_response.status_code +>>> onboarding_verifying_response.text >>> onboarding_response = onboarding_client.onboard(onboarding_parameters) ->>> onboarding_response.status_code() ->>> onboarding_response.data() # or onboarding_response.text() +>>> onboarding_response.status_code +>>> onboarding_response.text { "authentication": { @@ -69,4 +111,4 @@ True }, "deviceAlternateId": "c067272a-d3a7-4dcf-ab58-5c45ba66ad60", "sensorAlternateId": "5564ce96-385f-448a-9502-9ea3c940a259" -} \ No newline at end of file +} From f6e7db43b035b025ebdec4cc19629b1defb33fd9 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 21 Oct 2021 17:03:00 +0300 Subject: [PATCH 20/78] Fix onboarding signing (#14) --- agrirouter/onboarding/onboarding.py | 10 +++++----- agrirouter/onboarding/request.py | 5 ++++- agrirouter/onboarding/request_body.py | 2 +- agrirouter/revoking/request.py | 7 ------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index ad07b896..306dba00 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -34,16 +34,16 @@ def _perform_request(self, params: BaseOnboardingParameter, url: str) -> request if request.is_signed: return requests.post( url=request.get_url(), - json=request.get_data(), + data=request.get_body_content(), headers=request.get_header() ) raise RequestNotSigned - def verify(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResponse: + def verify(self, params: SoftwareOnboardingParameter) -> SoftwareVerifyOnboardingResponse: url = self._environment.get_verify_onboard_request_url() http_response = self._perform_request(params=params, url=url) - return SoftwareOnboardingResponse(http_response) + return SoftwareVerifyOnboardingResponse(http_response) def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResponse: url = self._environment.get_secured_onboard_url() @@ -70,11 +70,11 @@ def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardi def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) - request.sign(self._private_key) + request.sign(self._private_key, self._public_key) if request.is_signed: return requests.post( url=request.get_url(), - data=request.get_data(), + data=request.get_body_content(), headers=request.get_header() ) raise RequestNotSigned diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index e02e0ec7..c39d9d7a 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -18,8 +18,11 @@ def get_data(self): def get_header(self): return self.header.get_header() + def get_body_content(self): + return self.body.json().replace("\n", "") + def sign(self, private_key, public_key): - body = self.body.json().replace("\n", "") + body = self.get_body_content() signature = create_signature(body, private_key) verify_signature(body, bytes.fromhex(signature), public_key) self.header.sign(signature) diff --git a/agrirouter/onboarding/request_body.py b/agrirouter/onboarding/request_body.py index 951ab01d..045c46a8 100644 --- a/agrirouter/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -75,7 +75,7 @@ def _set_params(self, } def json(self) -> str: - return json.dumps(self.get_parameters()) + return json.dumps(self.get_parameters(), separators=(',', ':')) @staticmethod def _validate_certificate_type(certificate_type: str) -> None: diff --git a/agrirouter/revoking/request.py b/agrirouter/revoking/request.py index d45b7787..cb8605fa 100644 --- a/agrirouter/revoking/request.py +++ b/agrirouter/revoking/request.py @@ -28,10 +28,3 @@ def is_signed(self) -> bool: if header_has_signature: return True return False - - @property - def is_valid(self) -> bool: - if not self.is_signed: - return False - signature = self.get_header().get("X-Agrirouter-Signature") - # return validate_signature(signature) From 4d398bcf6c7d754eee4436dfa8870fdd6296b357 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 21 Oct 2021 17:18:54 +0300 Subject: [PATCH 21/78] Add unittests (#13) * add test_signature.py * add wrong variables * change with black * add TestAuthorization * add new asserts * add new asserts * add test_dto.py * delete assert isinstance * change with black * delete assert isinstance * add tests --- tests/auth_test/test_auth.py | 47 ++++++ tests/auth_test/test_dto.py | 137 ++++++++++++++++++ tests/auth_test/test_response.py | 3 - tests/constants.py | 52 +++++++ .../test_environmental_services.py | 2 + tests/messaging_test/test_request.py | 2 +- tests/onboarding_test/test_headers.py | 1 - tests/onboarding_test/test_onboarding.py | 61 ++++++++ .../test_request_onboarding.py | 51 +++++++ tests/onboarding_test/test_signature.py | 12 ++ tests/test_revoking/test_parameters.py | 30 ++++ 11 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 tests/auth_test/test_auth.py create mode 100644 tests/auth_test/test_dto.py create mode 100644 tests/onboarding_test/test_onboarding.py create mode 100644 tests/onboarding_test/test_request_onboarding.py create mode 100644 tests/onboarding_test/test_signature.py create mode 100644 tests/test_revoking/test_parameters.py diff --git a/tests/auth_test/test_auth.py b/tests/auth_test/test_auth.py new file mode 100644 index 00000000..8ab7cf28 --- /dev/null +++ b/tests/auth_test/test_auth.py @@ -0,0 +1,47 @@ +"""Tests agrirouter/auth/auth.py""" + +from agrirouter import AuthUrlParameter +from agrirouter.auth.auth import Authorization +from tests.constants import ( + public_key, + private_key, + auth_result_url, + ENV, + application_id, +) +from re import search + + +class TestAuthorization: + def test_extract_auth_response(self): + auth_client = Authorization(ENV, public_key=public_key, private_key=private_key) + assert search( + " Date: Thu, 21 Oct 2021 22:50:50 +0300 Subject: [PATCH 22/78] Fix message sending --- agrirouter/messaging/clients/http.py | 17 ++++++++++++----- agrirouter/messaging/decode.py | 8 ++++---- agrirouter/messaging/encode.py | 10 +++++----- agrirouter/messaging/enums.py | 15 +++++++++++++++ agrirouter/messaging/messages.py | 5 +++-- agrirouter/messaging/parameters/dto.py | 10 +++++----- agrirouter/messaging/parameters/service.py | 8 ++++---- agrirouter/messaging/request.py | 2 +- agrirouter/messaging/services/cloud.py | 4 ++-- agrirouter/messaging/services/commons.py | 19 +++++++++++-------- agrirouter/messaging/services/messaging.py | 14 +++++++------- agrirouter/onboarding/exceptions.py | 4 ++++ agrirouter/onboarding/response.py | 4 ++++ agrirouter/utils/utc_time_util.py | 4 ++++ agrirouter/utils/uuid_util.py | 2 +- 15 files changed, 82 insertions(+), 44 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index fa3110e9..08b19664 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -2,6 +2,7 @@ import json import os import ssl +from urllib.parse import urlparse from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.onboarding.dto import ConnectionCriteria @@ -20,8 +21,7 @@ def __init__( self.on_message_callback = on_message_callback self.timeout = timeout - @staticmethod - def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( certfile=certificate_file_path, @@ -29,8 +29,8 @@ def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboar password=onboard_response.get_authentication().get_secret(), ) connection = http.client.HTTPSConnection( - host=onboard_response.connection_criteria.get_host(), - port=onboard_response.connection_criteria.get_port(), + host=self.get_host(onboard_response.connection_criteria.get_measures()), + port=self.get_port(onboard_response.connection_criteria.get_measures()), context=context ) return connection @@ -44,7 +44,7 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques method=method, url=onboard_response.get_connection_criteria().get_measures(), headers=self.headers, - body=json.dumps(request_body) + body=json.dumps(request_body.json_serialize()) ) else: connection.request( @@ -66,3 +66,10 @@ def unsubscribe(self): def _start_loop(self): pass + + def get_host(self, uri): + return urlparse(uri).netloc + + def get_port(self, uri): + return urlparse(uri).port if urlparse(uri).port else None + diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index 839a0e48..dd9672b0 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -40,11 +40,11 @@ def decode_response(message: bytes) -> DecodedMessage: def decode_details(details: Any): - if details.type_url == TypeUrl.get_type_url(Messages.__name__): + if details.type_url == TypeUrl.get_type_url(Messages): return Messages().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse): return ListEndpointsResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse): return HeaderQueryResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse): return MessageQueryResponse().MergeFromString(details.value) diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py index fe74c229..5ad1361c 100644 --- a/agrirouter/messaging/encode.py +++ b/agrirouter/messaging/encode.py @@ -18,23 +18,23 @@ def write_proto_parts_to_buffer(parts: list, buffer: bytes = b""): return buffer -def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> bytes: +def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> str: request_envelope = encode_header(header_parameters) request_payload = encode_payload(payload_parameters) raw_data = write_proto_parts_to_buffer([request_envelope, request_payload]) - return base64.b64encode(raw_data) + return base64.b64encode(raw_data).decode() def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope: request_envelope = RequestEnvelope() - request_envelope.application_id = header_parameters.get_application_message_id() \ + request_envelope.application_message_id = header_parameters.get_application_message_id() \ if header_parameters.get_application_message_id() else new_uuid() request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() request_envelope.technical_message_type = header_parameters.get_technical_message_type() request_envelope.mode = header_parameters.get_mode() - request_envelope.timestamp = now_as_utc_timestamp() + request_envelope.timestamp.FromDatetime(now_as_utc_timestamp()) return request_envelope @@ -42,5 +42,5 @@ def encode_payload(payload_parameters: MessagePayloadParameters) -> RequestPaylo any_proto_wrapper = Any() any_proto_wrapper.type_url = payload_parameters.get_type_url() any_proto_wrapper.value = payload_parameters.get_value() - request_payload = RequestPayloadWrapper(any_proto_wrapper) + request_payload = RequestPayloadWrapper(details=any_proto_wrapper) return request_payload diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py index 1247118f..0b9354cf 100644 --- a/agrirouter/messaging/enums.py +++ b/agrirouter/messaging/enums.py @@ -13,3 +13,18 @@ class TechnicalMessageType(BaseEnum): FEED_MESSAGE_QUERY = "dke:feed_message_query" CLOUD_ONBOARD_ENDPOINTS = "dke:cloud_onboard_endpoints" CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" + + +class CapabilityTypeDefinitions(BaseEnum): + ISO_11783_TASKDATA_ZIP = "iso:11783:-10:taskdata:zip" + ISO_11783_DEVICE_DESCRIPTION_PROTOBUF = "iso:11783:-10:device_description:protobuf" + ISO_11783_TIMELOG_PROTOBUF = "iso:11783:-10:time_log:protobuf" + IMG_BMP = "img:bmp" + IMG_JPEG = "img:jpeg" + IMG_PNG = "img:png" + SHP_SHAPE_ZIP = "shp:shape:zip" + DOC_PDF = "doc:pdf" + VID_AVI = "vid:avi" + VID_MP4 = "vid:mp4" + VID_WMV = "vid:wmv" + GPS_INFO = "gps:info" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 1e5228fd..2d5d1671 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -3,6 +3,7 @@ from typing import Union, List, Dict from agrirouter.messaging.exceptions import WrongFieldError +from agrirouter.utils.utc_time_util import now_as_utc_str class EncodedMessage: @@ -36,12 +37,12 @@ class Message: def __init__(self, content): self.content = content - self.timestamp = datetime.utcnow() + self.timestamp = now_as_utc_str() def json_serialize(self) -> dict: return { self.MESSAGE: self.content, - self.TIMESTAMP: self.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.TIMESTAMP: self.timestamp } diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py index 3c1404de..4646a1b6 100644 --- a/agrirouter/messaging/parameters/dto.py +++ b/agrirouter/messaging/parameters/dto.py @@ -9,7 +9,7 @@ class Parameters: def __init__(self, *, application_message_seq_no: str, - application_message_id: int = None, + application_message_id: str = None, team_set_context_id: str ): self.application_message_seq_no = application_message_seq_no @@ -42,8 +42,8 @@ class MessageParameters(Parameters): def __init__(self, *, application_message_seq_no: str, - application_message_id: int, - team_set_context_id: str, + application_message_id: str, + team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse ): super(MessageParameters, self).__init__( @@ -54,7 +54,7 @@ def __init__(self, self.onboarding_response = onboarding_response - def get_onboarding_response(self): + def get_onboarding_response(self) -> BaseOnboardingResonse: return self.onboarding_response @@ -63,7 +63,7 @@ class MessagingParameters(MessageParameters): def __init__(self, *, application_message_seq_no: str = None, - application_message_id: int = None, + application_message_id: str = None, team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse, encoded_messages=None diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 0769fa08..f4ee63b6 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -18,7 +18,7 @@ def __init__(self, application_message_seq_no: str = None, recipients: list = None, chunk_component: ChunkComponent = None, - application_message_id: int = None, + application_message_id: str = None, ): super(MessageHeaderParameters, self).__init__( application_message_seq_no=application_message_seq_no, @@ -217,7 +217,7 @@ def set_validity_period(self, validity_period: ValidityPeriod): class ListEndpointsParameters(MessageParameters): def __init__(self, technical_message_type: str = None, - direction: str = None, + direction: int = None, filtered: bool = False, **kwargs): self.technical_message_type = technical_message_type @@ -231,10 +231,10 @@ def get_technical_message_type(self) -> str: def set_technical_message_type(self, technical_message_type: str): self.technical_message_type = technical_message_type - def get_direction(self) -> str: + def get_direction(self) -> int: return self.direction - def set_direction(self, direction: str): + def set_direction(self, direction: int): self.direction = direction def is_filtered(self): diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py index 4de0a69e..9cc9507e 100644 --- a/agrirouter/messaging/request.py +++ b/agrirouter/messaging/request.py @@ -11,7 +11,7 @@ class MessageRequest: def __init__(self, sensor_alternate_id: str, capability_alternate_id: str, - messages: List[Message] + messages: List[dict] ): self.sensor_alternate_id = sensor_alternate_id self.capability_alternate_id = capability_alternate_id diff --git a/agrirouter/messaging/services/cloud.py b/agrirouter/messaging/services/cloud.py index cd40a465..217462e2 100644 --- a/agrirouter/messaging/services/cloud.py +++ b/agrirouter/messaging/services/cloud.py @@ -28,7 +28,7 @@ def encode(parameters: CloudOnboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OnboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OnboardingRequest), value=onboarding_request.SerializeToString() ) @@ -58,7 +58,7 @@ def encode(parameters: CloudOffboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OffboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OffboardingRequest), value=offboarding_request.SerializeToString() ) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 939a6fdc..ca2b4cad 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -3,12 +3,13 @@ import requests -from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult +from agrirouter.onboarding.exceptions import BadMessagingResult class AbstractMessagingClient(ABC): @@ -18,10 +19,10 @@ def create_message_request(parameters) -> MessageRequest: messages = [] for encoded_message in parameters.get_encoded_messages(): message = Message(encoded_message) - messages.append(message) + messages.append(message.json_serialize()) message_request = MessageRequest( - parameters.get_sensor_alternate_id(), - parameters.get_capability_alternate_id(), + parameters.get_onboarding_response().get_sensor_alternate_id(), + parameters.get_onboarding_response().get_capability_alternate_id(), messages ) return message_request @@ -33,8 +34,8 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self, on_message_callback): + self.client = HttpClient(on_message_callback=on_message_callback) def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) @@ -43,7 +44,9 @@ def send(self, parameters) -> MessagingResult: parameters.get_onboarding_response(), request ) - result = MessagingResult([parameters.get_message_id()]) + if response.status in [400, 401, 403, 404, 500]: + raise BadMessagingResult(f"Messaging Request failed with status code {response.status}") + result = MessagingResult([parameters.get_application_message_id()]) return result def subscribe(self): @@ -76,7 +79,7 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, qos=qos ) - result = MessagingResult([parameters.get_message_id()]) + result = MessagingResult([parameters.get_application_message_id()]) return result def subscribe(self): diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index e071b028..5a6cdb80 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -55,7 +55,7 @@ def encode(parameters: CapabilityParameters) -> EncodedMessage: capability_specification.capabilities = parameters.get_capability_parameters() message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(CapabilitySpecification.__name__), + type_url=TypeUrl.get_type_url(CapabilitySpecification), value=capability_specification.SerializeToString() ) @@ -85,7 +85,7 @@ def encode(parameters: FeedConfirmParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -115,7 +115,7 @@ def encode(parameters: FeedDeleteParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -147,7 +147,7 @@ def encode(parameters: ListEndpointsParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(ListEndpointsQuery.__name__), + type_url=TypeUrl.get_type_url(ListEndpointsQuery), value=list_endpoints_query.SerializeToString() ) @@ -179,7 +179,7 @@ def encode(parameters: QueryMessageParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -211,7 +211,7 @@ def encode(parameters: QueryHeaderParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -241,7 +241,7 @@ def encode(parameters: SubscriptionParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(Subscription.__name__), + type_url=TypeUrl.get_type_url(Subscription), value=subscription.SerializeToString() ) diff --git a/agrirouter/onboarding/exceptions.py b/agrirouter/onboarding/exceptions.py index 490e51e3..fb3b024a 100644 --- a/agrirouter/onboarding/exceptions.py +++ b/agrirouter/onboarding/exceptions.py @@ -21,3 +21,7 @@ class RequestNotSigned(AgriRouuterBaseException): Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/ integration/onboarding.html#signing-requests """ + + +class BadMessagingResult(AgriRouuterBaseException): + _message = "Messaging Request failed" diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 50826c4c..d62ce45f 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,3 +1,5 @@ +import json + from requests import Response from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication @@ -54,6 +56,8 @@ def __init__(self, http_response: Response): measures=response_body.get("connectionCriteria").get("measures"), commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), + port=response_body.get("connectionCriteria").get("port"), + client_id=response_body.get("connectionCriteria").get("client_id") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( diff --git a/agrirouter/utils/utc_time_util.py b/agrirouter/utils/utc_time_util.py index b86f3afb..b87ca808 100644 --- a/agrirouter/utils/utc_time_util.py +++ b/agrirouter/utils/utc_time_util.py @@ -2,5 +2,9 @@ def now_as_utc_timestamp(): + return datetime.utcnow() + + +def now_as_utc_str(): timestamp = datetime.utcnow() return timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") diff --git a/agrirouter/utils/uuid_util.py b/agrirouter/utils/uuid_util.py index 0c9bd872..8e500ead 100644 --- a/agrirouter/utils/uuid_util.py +++ b/agrirouter/utils/uuid_util.py @@ -2,4 +2,4 @@ def new_uuid(): - return uuid.uuid4() + return str(uuid.uuid4()) From c52a9b69da78984c32c35c9cf16275a6d0a29dea Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 22 Oct 2021 00:05:23 +0300 Subject: [PATCH 23/78] Fix revoking --- agrirouter/revoking/parameters.py | 6 +++--- agrirouter/revoking/request.py | 6 +++++- agrirouter/revoking/request_body.py | 11 ++++------- agrirouter/revoking/revoking.py | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/agrirouter/revoking/parameters.py b/agrirouter/revoking/parameters.py index 95a73332..cf706b02 100644 --- a/agrirouter/revoking/parameters.py +++ b/agrirouter/revoking/parameters.py @@ -8,7 +8,7 @@ def __init__(self, account_id, endpoint_ids, utc_timestamp, - timestamp, + time_zone, content_type=ContentTypes.APPLICATION_JSON.value ): @@ -17,7 +17,7 @@ def __init__(self, self.account_id = account_id self.endpoint_ids = endpoint_ids self.utc_timestamp = utc_timestamp - self.timestamp = timestamp + self.time_zone = time_zone def get_header_params(self): return { @@ -30,5 +30,5 @@ def get_body_params(self): "account_id": self.account_id, "endpoint_ids": self.endpoint_ids, "utc_timestamp": self.utc_timestamp, - "timestamp": self.timestamp, + "time_zone": self.time_zone, } diff --git a/agrirouter/revoking/request.py b/agrirouter/revoking/request.py index cb8605fa..1fea09e1 100644 --- a/agrirouter/revoking/request.py +++ b/agrirouter/revoking/request.py @@ -18,8 +18,12 @@ def get_data(self): def get_header(self): return self.header.get_header() + def get_body_content(self): + return self.body.json().replace("\n", "") + def sign(self, private_key): - signature = create_signature(self.body.json(new_lines=False), private_key) + body = self.get_body_content() + signature = create_signature(body, private_key) self.header.sign(signature) @property diff --git a/agrirouter/revoking/request_body.py b/agrirouter/revoking/request_body.py index 6ec70b2d..7bdea6e5 100644 --- a/agrirouter/revoking/request_body.py +++ b/agrirouter/revoking/request_body.py @@ -26,14 +26,11 @@ def _set_params(self, ) -> None: self.params = { - "account_id": account_id, - "endpoint_ids": endpoint_ids, + "accountId": account_id, + "endpointIds": endpoint_ids, "UTCTimestamp": utc_timestamp, "timeZone": time_zone, } - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result + def json(self) -> str: + return json.dumps(self.get_parameters(), separators=(',', ':')) diff --git a/agrirouter/revoking/revoking.py b/agrirouter/revoking/revoking.py index aaa8f871..b835bc8f 100644 --- a/agrirouter/revoking/revoking.py +++ b/agrirouter/revoking/revoking.py @@ -29,9 +29,9 @@ def _perform_request(self, params: RevokingParameter, url: str) -> requests.Resp request = self._create_request(params, url) request.sign(self._private_key) if request.is_signed: - return requests.post( + return requests.delete( url=request.get_url(), - data=request.get_data(), + json=request.get_data(), headers=request.get_header() ) raise RequestNotSigned @@ -40,4 +40,4 @@ def revoke(self, params: RevokingParameter) -> RevokingResponse: url = self._environment.get_revoke_url() http_response = self._perform_request(params=params, url=url) - return RevokingResponse(http_response) \ No newline at end of file + return RevokingResponse(http_response) From e0b69e807921bf679e30ef5065e5d1abcb0b7761 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 22 Oct 2021 00:13:40 +0300 Subject: [PATCH 24/78] Fix MqttMessagingService --- agrirouter/messaging/services/commons.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index ca2b4cad..7afa7d7c 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -1,3 +1,4 @@ +import json import os from abc import ABC, abstractmethod @@ -65,7 +66,7 @@ def __init__(self, self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=self.onboarding_response.get_client_id(), + client_id="asdfg", on_message_callback=on_message_callback, ) self.client.connect( @@ -74,9 +75,10 @@ def __init__(self, ) def send(self, parameters, qos: int = 0) -> MessagingResult: - mqtt_payload = self.create_message_request(parameters) + message_request = self.create_message_request(parameters) + mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, + self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), qos=qos ) result = MessagingResult([parameters.get_application_message_id()]) From 81bae132c90289bd0197b332d7a79b1f2112c33f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 14:39:45 +0300 Subject: [PATCH 25/78] Update examples.txt --- examples.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples.txt b/examples.txt index e59fe910..2fce2d32 100644 --- a/examples.txt +++ b/examples.txt @@ -112,3 +112,21 @@ True "deviceAlternateId": "c067272a-d3a7-4dcf-ab58-5c45ba66ad60", "sensorAlternateId": "5564ce96-385f-448a-9502-9ea3c940a259" } + + +>>> ########################## +>>> Messaging + + +>>> client = HttpClient(lambda x: x) +>>> messaging_service = HttpMessagingService(client) +>>> list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +>>> list_endpoint_service = ListEndpointsService(messaging_service) +>>> list_endpoint_service.send(list_endpoint_parameters) From 0ad04c936e354302ea3e4ebd2354b3900ab99b0c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 15:16:28 +0300 Subject: [PATCH 26/78] Fix messaging in examples --- examples.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples.txt b/examples.txt index 2fce2d32..cf24a7a3 100644 --- a/examples.txt +++ b/examples.txt @@ -118,6 +118,7 @@ True >>> Messaging +>>> from agrirouter.messaging.clients.http import HttpClient >>> client = HttpClient(lambda x: x) >>> messaging_service = HttpMessagingService(client) >>> list_endpoint_parameters = ListEndpointsParameters( From 9abf794a5bafec5f319ef9a56df9e64862a9179a Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 18:51:59 +0300 Subject: [PATCH 27/78] Refactor Mqtt client --- agrirouter/messaging/clients/mqtt.py | 9 ++++++++- agrirouter/messaging/services/commons.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 4e8dd555..72ff399d 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -7,7 +7,7 @@ class MqttClient: def __init__(self, - client_id: str = "", + client_id, on_message_callback: callable = None, userdata: Any = None, clean_session: bool = True @@ -88,6 +88,13 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback() -> callable: def on_connect(client, userdata, flags, rc, properties=None): + print("Connection started") + with open("connection.txt", "w") as file: + file.write("Connection started") + if rc == 0: + file.write("Connected!!") + else: + file.write("Do not Connected!!") if rc == 0: print("Connected to MQTT Broker!") else: diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 7afa7d7c..11702561 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -60,13 +60,14 @@ def unsubscribe(self): class MqttMessagingService(AbstractMessagingClient): def __init__(self, + client_id, onboarding_response, on_message_callback: callable = None, ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id="asdfg", + client_id=client_id, on_message_callback=on_message_callback, ) self.client.connect( From c90ba11c9cd9975383416e4cd186eaefe9d5bedf Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 21:28:02 +0300 Subject: [PATCH 28/78] Update examples --- examples.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples.txt b/examples.txt index cf24a7a3..b4ebf13d 100644 --- a/examples.txt +++ b/examples.txt @@ -119,7 +119,12 @@ True >>> from agrirouter.messaging.clients.http import HttpClient ->>> client = HttpClient(lambda x: x) +>>> from agrirouter.messaging.clients.mqtt import MqttClient +>>> from agrirouter.messaging.enums import CapabilityTypeDefinitions +>>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +>>> from agrirouter import ListEndpointsParameters, ListEndpointsService + +>>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback >>> messaging_service = HttpMessagingService(client) >>> list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, From f944e84308b999c444cdaf692055cf2f6152ecec Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 26 Oct 2021 00:45:36 +0300 Subject: [PATCH 29/78] add test script --- example_script.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 example_script.py diff --git a/example_script.py b/example_script.py new file mode 100644 index 00000000..3d2b25aa --- /dev/null +++ b/example_script.py @@ -0,0 +1,116 @@ +public_key = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN +6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk +F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR +IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE +SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg +uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj +qwIDAQAB +-----END PUBLIC KEY-----""" + +private_key = """-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7 +Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz +9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21 +8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe +rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA +NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5 +jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK +9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD +IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb +XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL +uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn +Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb +sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7 +5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0 +OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9 +Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi +vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz +XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9 +pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/ +NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5 +mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV +Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F +CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR +qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65 +KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr +HjmgzBRxXFy5uph6Ue6dxyszaA== +-----END PRIVATE KEY-----""" + + +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI + + +######################################################## +# Authorization +print("Authorization...\n") + +import agrirouter as ar + +auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") +auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) +auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization +print(f"auth_url={auth_url}") + +auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. +auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process +auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + +print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + +print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + + +# Get dict containing data from auth process you will use for futher communication. +# If auth was rejected, contains {"error"} key. +# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys +# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. +auth_data = auth_response.get_auth_result() +print(f"auth_data: {auth_data}") + +######################################################## + +# Onboarding +print("Onboarding...\n") + + +from agrirouter.onboarding.enums import GateWays + +id_ = "urn:myapp:snr00003234" # just unique +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI +time_zone = "+03:00" + +onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) +onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) +print(f"onboarding_verifying_response: {onboarding_verifying_response}") +print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") +print(f"onboarding_verifying_response.text: {onboarding_verifying_response}") +onboarding_response = onboarding_client.onboard(onboarding_parameters) +print(f"onboarding_response.status_code: {onboarding_response.status_code}") +print(f"onboarding_response.text: {onboarding_response.text}") + + +########################## +# Messaging + + +from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.clients.mqtt import MqttClient +from agrirouter.messaging.enums import CapabilityTypeDefinitions +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService +from agrirouter.utils.uuid_util import new_uuid + +client = HttpClient(on_message_callback=lambda x: x) # Enter your client_id and on_message_callback +messaging_service = HttpMessagingService(client) +list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +list_endpoint_service = ListEndpointsService(messaging_service) +list_endpoint_service.send(list_endpoint_parameters) From 68674dfea85f8ba252ffaa35b599af52b32dbcda Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 00:52:55 +0300 Subject: [PATCH 30/78] Update example --- examples.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples.txt b/examples.txt index b4ebf13d..a8850774 100644 --- a/examples.txt +++ b/examples.txt @@ -123,6 +123,8 @@ True >>> from agrirouter.messaging.enums import CapabilityTypeDefinitions >>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService >>> from agrirouter import ListEndpointsParameters, ListEndpointsService +>>> from agrirouter.utils.uuid_util import new_uuid + >>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback >>> messaging_service = HttpMessagingService(client) From 005c7caff53e5100f803494f2a62982d6b5d42b1 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 01:07:07 +0300 Subject: [PATCH 31/78] remove odd prints --- example_script.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example_script.py b/example_script.py index 3d2b25aa..2a9badc3 100644 --- a/example_script.py +++ b/example_script.py @@ -83,9 +83,8 @@ onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) -print(f"onboarding_verifying_response: {onboarding_verifying_response}") print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") -print(f"onboarding_verifying_response.text: {onboarding_verifying_response}") +print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") onboarding_response = onboarding_client.onboard(onboarding_parameters) print(f"onboarding_response.status_code: {onboarding_response.status_code}") print(f"onboarding_response.text: {onboarding_response.text}") From 2cc77078490e2c3a36aaee164cb7f71352db4813 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 10:47:13 +0300 Subject: [PATCH 32/78] Refactor http client --- agrirouter/messaging/clients/http.py | 24 ++++-------------------- agrirouter/messaging/services/commons.py | 4 ++-- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 08b19664..20e9746a 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -13,14 +13,6 @@ class HttpClient: headers = {"Content-Type": "application/json"} - def __init__( - self, - on_message_callback: callable, - timeout=20 - ): - self.on_message_callback = on_message_callback - self.timeout = timeout - def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( @@ -58,18 +50,10 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques return response - def subscribe(self): - pass - - def unsubscribe(self): - pass - - def _start_loop(self): - pass - - def get_host(self, uri): + @staticmethod + def get_host(uri): return urlparse(uri).netloc - def get_port(self, uri): + @staticmethod + def get_port(uri): return urlparse(uri).port if urlparse(uri).port else None - diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 11702561..84717719 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -35,8 +35,8 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): - def __init__(self, on_message_callback): - self.client = HttpClient(on_message_callback=on_message_callback) + def __init__(self): + self.client = HttpClient() def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) From a44be3db12630d31f68765469cb303b037cface7 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 10:53:31 +0300 Subject: [PATCH 33/78] Fix examples --- example_script.py | 4 ++-- examples.txt | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/example_script.py b/example_script.py index 2a9badc3..8d08c0ed 100644 --- a/example_script.py +++ b/example_script.py @@ -101,8 +101,8 @@ from agrirouter import ListEndpointsParameters, ListEndpointsService from agrirouter.utils.uuid_util import new_uuid -client = HttpClient(on_message_callback=lambda x: x) # Enter your client_id and on_message_callback -messaging_service = HttpMessagingService(client) + +messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, direction=2, diff --git a/examples.txt b/examples.txt index a8850774..78e169a5 100644 --- a/examples.txt +++ b/examples.txt @@ -126,8 +126,7 @@ True >>> from agrirouter.utils.uuid_util import new_uuid ->>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback ->>> messaging_service = HttpMessagingService(client) +>>> messaging_service = HttpMessagingService() >>> list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, direction=2, From a1362f78b131034bd89103e5b2c4c6b4fcd8332b Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 12:39:32 +0300 Subject: [PATCH 34/78] Add subscription to examples --- example_script.py | 21 ++++++++++++++++++--- examples.txt | 23 +++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/example_script.py b/example_script.py index 8d08c0ed..0995c898 100644 --- a/example_script.py +++ b/example_script.py @@ -94,13 +94,13 @@ # Messaging -from agrirouter.messaging.clients.http import HttpClient -from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.enums import CapabilityTypeDefinitions +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters from agrirouter.utils.uuid_util import new_uuid +# List Endpoints messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( @@ -113,3 +113,18 @@ ) list_endpoint_service = ListEndpointsService(messaging_service) list_endpoint_service.send(list_endpoint_parameters) + +# Subscription + +messaging_service = HttpMessagingService() +subscription_service = SubscriptionService(messaging_service) + +tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +subscription_service.send(subscription_parameters) diff --git a/examples.txt b/examples.txt index 78e169a5..fcb13cd4 100644 --- a/examples.txt +++ b/examples.txt @@ -115,16 +115,16 @@ True >>> ########################## ->>> Messaging +>>> # Messaging ->>> from agrirouter.messaging.clients.http import HttpClient ->>> from agrirouter.messaging.clients.mqtt import MqttClient >>> from agrirouter.messaging.enums import CapabilityTypeDefinitions +>>> from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription >>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService ->>> from agrirouter import ListEndpointsParameters, ListEndpointsService +>>> from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters >>> from agrirouter.utils.uuid_util import new_uuid +>>> # List Endpoints >>> messaging_service = HttpMessagingService() >>> list_endpoint_parameters = ListEndpointsParameters( @@ -137,3 +137,18 @@ True ) >>> list_endpoint_service = ListEndpointsService(messaging_service) >>> list_endpoint_service.send(list_endpoint_parameters) + +>>> # Subscription + +>>> messaging_service = HttpMessagingService() +>>> subscription_service = SubscriptionService(messaging_service) + +>>> tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +>>> subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +>>> subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +>>> subscription_service.send(subscription_parameters) From f0bc39f0b133e8aff00846cbe76b051cf6e4c450 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 12:44:47 +0300 Subject: [PATCH 35/78] Refactor SubscriptionParameters --- agrirouter/messaging/parameters/dto.py | 4 ++-- agrirouter/messaging/parameters/service.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py index 4646a1b6..17998bf0 100644 --- a/agrirouter/messaging/parameters/dto.py +++ b/agrirouter/messaging/parameters/dto.py @@ -8,7 +8,7 @@ class Parameters: def __init__(self, *, - application_message_seq_no: str, + application_message_seq_no: int, application_message_id: str = None, team_set_context_id: str ): @@ -41,7 +41,7 @@ def validate(self): class MessageParameters(Parameters): def __init__(self, *, - application_message_seq_no: str, + application_message_seq_no: int, application_message_id: str, team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index f4ee63b6..48ccbd59 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -6,6 +6,7 @@ from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.messaging.parameters.dto import MessageParameters, Parameters +from agrirouter.onboarding.response import BaseOnboardingResonse class MessageHeaderParameters(Parameters): @@ -330,10 +331,20 @@ def set_validity_period(self, validity_period: list) -> None: class SubscriptionParameters(MessageParameters): def __init__(self, + *, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, subscription_items: List[Subscription.MessageTypeSubscriptionItem] = None, - **kwargs): + ): self.subscription_items = subscription_items if subscription_items else [] - super(SubscriptionParameters, self).__init__(**kwargs) + super(SubscriptionParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_subscription_items(self) -> List[Subscription.MessageTypeSubscriptionItem]: return self.subscription_items From 0c47fb8e481c65db301a47e1928e8dfd537761d5 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 18:06:34 +0300 Subject: [PATCH 36/78] Fix Http Client --- agrirouter/messaging/clients/http.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 20e9746a..4381d983 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -11,7 +11,10 @@ class HttpClient: - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) @@ -34,14 +37,14 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques if request_body is not None: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(onboard_response.get_connection_criteria().get_measures()), headers=self.headers, body=json.dumps(request_body.json_serialize()) ) else: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(onboard_response.get_connection_criteria().get_measures()), headers=self.headers, ) response = connection.getresponse() @@ -57,3 +60,7 @@ def get_host(uri): @staticmethod def get_port(uri): return urlparse(uri).port if urlparse(uri).port else None + + @staticmethod + def get_path(uri): + return urlparse(uri).path From 738db91ab440f940cd7fda79a8f658494e14f5c9 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 18:47:53 +0300 Subject: [PATCH 37/78] Fix revoking test --- tests/test_revoking/test_parameters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_revoking/test_parameters.py b/tests/test_revoking/test_parameters.py index 6cb97298..2c1c737e 100644 --- a/tests/test_revoking/test_parameters.py +++ b/tests/test_revoking/test_parameters.py @@ -8,15 +8,15 @@ class TestRevokingParameter: content_type = "json" account_id = "111" endpoint_ids = "endpoint_1" - utc_timestamp = "+03:00" - timestamp = "01-01-2021" + time_zone = "+03:00" + utc_timestamp = "01-01-2021" test_object = RevokingParameter( application_id=application_id, content_type=content_type, account_id=account_id, endpoint_ids=endpoint_ids, utc_timestamp=utc_timestamp, - timestamp=timestamp, + time_zone=time_zone ) def test_get_header_params(self): @@ -27,4 +27,4 @@ def test_get_body_params(self): assert self.test_object.get_body_params()["account_id"] == self.account_id assert self.test_object.get_body_params()["endpoint_ids"] == self.endpoint_ids assert self.test_object.get_body_params()["utc_timestamp"] == self.utc_timestamp - assert self.test_object.get_body_params()["timestamp"] == self.timestamp + assert self.test_object.get_body_params()["time_zone"] == self.time_zone From 6b389f1cac79f3c258b18092ce691ea3b465766c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 20:31:53 +0300 Subject: [PATCH 38/78] Fix outbox --- agrirouter/messaging/clients/http.py | 30 +++++++++--- agrirouter/messaging/decode.py | 25 +++++++--- agrirouter/messaging/exceptions.py | 8 ++++ agrirouter/messaging/messages.py | 8 ++-- agrirouter/messaging/result.py | 8 +++- agrirouter/messaging/services/commons.py | 8 +--- agrirouter/messaging/services/http/outbox.py | 23 ++++++---- agrirouter/utils/type_url.py | 48 ++++++++++---------- 8 files changed, 101 insertions(+), 57 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 4381d983..429be1e7 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -16,7 +16,7 @@ class HttpClient: "Accept": "application/json" } - def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + def make_connection(self, certificate_file_path: str, uri: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( certfile=certificate_file_path, @@ -24,27 +24,43 @@ def make_connection(self, certificate_file_path: str, onboard_response: Software password=onboard_response.get_authentication().get_secret(), ) connection = http.client.HTTPSConnection( - host=self.get_host(onboard_response.connection_criteria.get_measures()), - port=self.get_port(onboard_response.connection_criteria.get_measures()), + host=self.get_host(uri), + port=self.get_port(uri), context=context ) return connection - def send(self, method: str, onboard_response: SoftwareOnboardingResponse, request_body=None): + def send_measure(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="POST", + uri=onboard_response.get_connection_criteria().get_measures(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send_command(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="GET", + uri=onboard_response.get_connection_criteria().get_commands(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send(self, method: str, uri: str, onboard_response: SoftwareOnboardingResponse, request_body=None): certificate_file_path = create_certificate_file_from_pen(onboard_response) try: - connection = self.make_connection(certificate_file_path, onboard_response) + connection = self.make_connection(certificate_file_path, uri, onboard_response) if request_body is not None: connection.request( method=method, - url=self.get_path(onboard_response.get_connection_criteria().get_measures()), + url=self.get_path(uri), headers=self.headers, body=json.dumps(request_body.json_serialize()) ) else: connection.request( method=method, - url=self.get_path(onboard_response.get_connection_criteria().get_measures()), + url=self.get_path(uri), headers=self.headers, ) response = connection.getresponse() diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index dd9672b0..02387153 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -9,6 +9,7 @@ from agrirouter.generated.messaging.response.payload.feed.feed_response_pb2 import HeaderQueryResponse, \ MessageQueryResponse from agrirouter.generated.messaging.response.response_pb2 import ResponseEnvelope, ResponsePayloadWrapper +from agrirouter.messaging.exceptions import DecodeMessageException from agrirouter.messaging.messages import DecodedMessage from agrirouter.utils.type_url import TypeUrl @@ -31,8 +32,10 @@ def decode_response(message: bytes) -> DecodedMessage: input_stream = base64.b64decode(message) response_envelope_buffer, response_payload_buffer = read_properties_buffers_from_input_stream(input_stream) - envelope = ResponseEnvelope().MergeFromString(response_envelope_buffer) - payload = ResponsePayloadWrapper().MergeFromString(response_payload_buffer) + envelope = ResponseEnvelope() + envelope.ParseFromString(response_envelope_buffer) + payload = ResponsePayloadWrapper() + payload.ParseFromString(response_payload_buffer) message = DecodedMessage(envelope, payload) @@ -41,10 +44,20 @@ def decode_response(message: bytes) -> DecodedMessage: def decode_details(details: Any): if details.type_url == TypeUrl.get_type_url(Messages): - return Messages().MergeFromString(details.value) + messages = Messages() + messages.MergeFromString(details.value) + return messages elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse): - return ListEndpointsResponse().MergeFromString(details.value) + list_endpoints_response = ListEndpointsResponse() + list_endpoints_response.MergeFromString(details.value) + return list_endpoints_response elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse): - return HeaderQueryResponse().MergeFromString(details.value) + header_query_response = HeaderQueryResponse() + header_query_response.MergeFromString(details.value) + return header_query_response elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse): - return MessageQueryResponse().MergeFromString(details.value) + message_query_response = MessageQueryResponse() + message_query_response.MergeFromString(details.value) + return message_query_response + else: + raise DecodeMessageException(f"Could not handle type {details.type_url} while decoding details.") diff --git a/agrirouter/messaging/exceptions.py b/agrirouter/messaging/exceptions.py index 3df29d4c..2bc8b9a7 100644 --- a/agrirouter/messaging/exceptions.py +++ b/agrirouter/messaging/exceptions.py @@ -7,3 +7,11 @@ class TypeUrlNotFoundError(AgriRouuterBaseException): class WrongFieldError(AgriRouuterBaseException): _message = "Unknown field" + + +class DecodeMessageException(AgriRouuterBaseException): + _message = "Can't decode message" + + +class OutboxException(AgriRouuterBaseException): + _message = "Can't fetch outbox message" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 2d5d1671..1655d2a7 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -82,15 +82,17 @@ def __init__(self, self.sensor_alternate_id = sensor_alternate_id self.command = command - def json_deserialize(self, data: Union[list, str]): - data = data if type(data) == list else json.loads(data) + def json_deserialize(self, data: Union[dict, str]): + data = data if type(data) == dict else json.loads(data) for key, value in data.keys(): if key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value elif key == self.COMMAND: - self.command = Command.json_deserialize(value) + command = Command() + command.json_deserialize(value) + self.command = command else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py index ba2df495..165469ac 100644 --- a/agrirouter/messaging/result.py +++ b/agrirouter/messaging/result.py @@ -26,7 +26,13 @@ def __init__(self, def json_deserialize(self, data: Union[list, str]): messages = data if type(data) == list else json.loads(data) - self.set_messages([OutboxMessage.json_deserialize(message) for message in messages]) + outbox_messages = [] + for message in messages: + outbox_message = OutboxMessage() + outbox_message.json_deserialize(message) + outbox_messages.append(outbox_message) + + self.set_messages(outbox_messages) def get_status_code(self) -> int: return self.status_code diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 84717719..10518340 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -40,12 +40,8 @@ def __init__(self): def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) - response = self.client.send( - "POST", - parameters.get_onboarding_response(), - request - ) - if response.status in [400, 401, 403, 404, 500]: + response = self.client.send_measure(parameters.get_onboarding_response(), request) + if response.status != 200: raise BadMessagingResult(f"Messaging Request failed with status code {response.status}") result = MessagingResult([parameters.get_application_message_id()]) return result diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index 72328072..13db25cf 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -1,8 +1,11 @@ +import json import os import requests from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.exceptions import OutboxException +from agrirouter.messaging.messages import OutboxMessage from agrirouter.messaging.result import OutboxResponse from agrirouter.messaging.certification import create_certificate_file_from_pen @@ -10,18 +13,18 @@ class OutboxService: - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self): + self.client = HttpClient() def fetch(self, onboarding_response) -> OutboxResponse: - response = self.client.send( - "GET", - onboarding_response, - None - ) - - outbox_response = OutboxResponse(status_code=response.status_code) - outbox_response.json_deserialize(response.json()["contents"]) + response = self.client.send_command(onboarding_response, None) + + if response.status == 200: + outbox_response = OutboxResponse(status_code=response.status) + response_body = response.read() + outbox_response.json_deserialize(response_body) + else: + raise OutboxException(f"Could not fetch messages from outbox. Status code was {response.status}") return outbox_response diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py index 71f4c6df..7d256bf3 100644 --- a/agrirouter/utils/type_url.py +++ b/agrirouter/utils/type_url.py @@ -17,29 +17,29 @@ class TypeUrl: @classmethod def get_type_url(cls, class_): - if class_.__name__ == Messages.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == HeaderQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageDelete.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageConfirm.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingRequest.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == CapabilitySpecification.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == Subscription.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQuery.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsQuery.__name__: - return cls.prefix + class_.__name__ + if class_ == Messages: + return cls.prefix + Messages.DESCRIPTOR.full_name + elif class_ == ListEndpointsResponse: + return cls.prefix + ListEndpointsResponse.DESCRIPTOR.full_name + elif class_ == HeaderQueryResponse: + return cls.prefix + HeaderQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageQueryResponse: + return cls.prefix + MessageQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageDelete: + return cls.prefix + MessageDelete.DESCRIPTOR.full_name + elif class_ == MessageConfirm: + return cls.prefix + MessageConfirm.DESCRIPTOR.full_name + elif class_ == OnboardingResponse: + return cls.prefix + OnboardingResponse.DESCRIPTOR.full_name + elif class_ == OnboardingRequest: + return cls.prefix + OnboardingRequest.DESCRIPTOR.full_name + elif class_ == CapabilitySpecification: + return cls.prefix + CapabilitySpecification.DESCRIPTOR.full_name + elif class_ == Subscription: + return cls.prefix + Subscription.DESCRIPTOR.full_name + elif class_ == MessageQuery: + return cls.prefix + MessageQuery.DESCRIPTOR.full_name + elif class_ == ListEndpointsQuery: + return cls.prefix + ListEndpointsQuery..DESCRIPTOR.full_name else: raise TypeUrlNotFoundError(f"The {class_} type url not found") From 4c9e6d156100c98c6f8053542bf2c9b7af1e7cdd Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 20:32:26 +0300 Subject: [PATCH 39/78] Fix TypeUrl --- agrirouter/utils/type_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py index 7d256bf3..2457b9da 100644 --- a/agrirouter/utils/type_url.py +++ b/agrirouter/utils/type_url.py @@ -40,6 +40,6 @@ def get_type_url(cls, class_): elif class_ == MessageQuery: return cls.prefix + MessageQuery.DESCRIPTOR.full_name elif class_ == ListEndpointsQuery: - return cls.prefix + ListEndpointsQuery..DESCRIPTOR.full_name + return cls.prefix + ListEndpointsQuery.DESCRIPTOR.full_name else: raise TypeUrlNotFoundError(f"The {class_} type url not found") From 0a3afaad7fde4809bc878a9ee27e4368a1251d54 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 22:00:49 +0300 Subject: [PATCH 40/78] Fix OutboxMessage --- agrirouter/messaging/messages.py | 6 +++--- agrirouter/messaging/result.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 1655d2a7..dab8e2e9 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -53,8 +53,8 @@ def __init__(self, message: str = None): self.message = message def json_deserialize(self, data: Union[Dict[str, str], str]): - messages = data if type(data) == list else json.loads(data) - for key, value in messages.keys(): + messages = data if type(data) == dict else json.loads(data) + for key, value in messages.items(): if key == self.MESSAGE: self.message = value else: @@ -84,7 +84,7 @@ def __init__(self, def json_deserialize(self, data: Union[dict, str]): data = data if type(data) == dict else json.loads(data) - for key, value in data.keys(): + for (key, value) in data.items(): if key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value elif key == self.SENSOR_ALTERNATE_ID: diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py index 165469ac..d08c7f4b 100644 --- a/agrirouter/messaging/result.py +++ b/agrirouter/messaging/result.py @@ -26,13 +26,13 @@ def __init__(self, def json_deserialize(self, data: Union[list, str]): messages = data if type(data) == list else json.loads(data) - outbox_messages = [] + outbox_message_list = [] for message in messages: outbox_message = OutboxMessage() outbox_message.json_deserialize(message) - outbox_messages.append(outbox_message) + outbox_message_list.append(outbox_message) - self.set_messages(outbox_messages) + self.set_messages(outbox_message_list) def get_status_code(self) -> int: return self.status_code From 2c9c247b534d57c12d7dd79229e066ab77d1fa80 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 22:01:22 +0300 Subject: [PATCH 41/78] Fix OutboxMessage --- agrirouter/messaging/messages.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index dab8e2e9..da475631 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -113,6 +113,3 @@ def get_command(self) -> Command: def set_command(self, command: Command) -> None: self.command = command - - def json_deserialize(self): - pass From 565c69aee9ef7cd73bbd9f8c3771f0afb4b74c9f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 00:48:57 +0300 Subject: [PATCH 42/78] Implement SubscriptionItemBuilder, CapabilityBuilder --- agrirouter/messaging/builders.py | 201 +++++++++++++++++++++++++++++++ agrirouter/messaging/enums.py | 2 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 agrirouter/messaging/builders.py diff --git a/agrirouter/messaging/builders.py b/agrirouter/messaging/builders.py new file mode 100644 index 00000000..94e56ce8 --- /dev/null +++ b/agrirouter/messaging/builders.py @@ -0,0 +1,201 @@ +from typing import List + +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.enums import CapabilityType + + +class SubscriptionItemBuilder: + + def __init__(self): + self._subscription_items = [] + + def build(self): + return self._subscription_items + + def clear(self): + self._subscription_items = [] + + def with_task_data(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_device_description(self, ddis: List[int]=None, position: bool=None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_time_log(self, ddis: List[int] = None, position: bool = None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_bmp(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_BMP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_jpg(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_JPEG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_png(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_PNG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_shape(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.SHP_SHAPE_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_pdf(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.DOC_PDF.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_avi(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_AVI.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_mp4(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_MP4.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_wmv(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_WMV.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_gps_info(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.GPS_INFO.value + ) + self._subscription_items.append(subscription_item) + return self + + +class CapabilityBuilder: + + def __init__(self): + self._capabilities = [] + + def build(self) -> list: + return self._capabilities + + def clear(self): + self._capabilities = [] + + def with_task_data(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TASKDATA_ZIP.value + self._capabilities.append(capability) + return self + + def with_device_description(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_time_log(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_bmp(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_BMP.value + self._capabilities.append(capability) + return self + + def with_jpg(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_JPEG.value + self._capabilities.append(capability) + return self + + def with_png(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_PNG.value + self._capabilities.append(capability) + return self + + def with_shape(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.SHP_SHAPE_ZIP.value + self._capabilities.append(capability) + return self + + def with_pdf(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.DOC_PDF.value + self._capabilities.append(capability) + return self + + def with_avi(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_AVI.value + self._capabilities.append(capability) + return self + + def with_mp4(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_MP4.value + self._capabilities.append(capability) + return self + + def with_wmv(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_WMV.value + self._capabilities.append(capability) + return self + + def with_gps_info(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.GPS_INFO.value + self._capabilities.append(capability) + return self diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py index 0b9354cf..53265ec5 100644 --- a/agrirouter/messaging/enums.py +++ b/agrirouter/messaging/enums.py @@ -15,7 +15,7 @@ class TechnicalMessageType(BaseEnum): CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" -class CapabilityTypeDefinitions(BaseEnum): +class CapabilityType(BaseEnum): ISO_11783_TASKDATA_ZIP = "iso:11783:-10:taskdata:zip" ISO_11783_DEVICE_DESCRIPTION_PROTOBUF = "iso:11783:-10:device_description:protobuf" ISO_11783_TIMELOG_PROTOBUF = "iso:11783:-10:time_log:protobuf" From 26d8971c1baa26bfcf116abe042b4a19d589697d Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:10:06 +0300 Subject: [PATCH 43/78] Refactor encode_header method --- agrirouter/messaging/encode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py index 5ad1361c..f1f40513 100644 --- a/agrirouter/messaging/encode.py +++ b/agrirouter/messaging/encode.py @@ -34,7 +34,10 @@ def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() request_envelope.technical_message_type = header_parameters.get_technical_message_type() request_envelope.mode = header_parameters.get_mode() + if header_parameters.get_team_set_context_id() is not None: + request_envelope.team_set_context_id = header_parameters.get_team_set_context_id() request_envelope.timestamp.FromDatetime(now_as_utc_timestamp()) + return request_envelope From f01f8417e786b7b8cfaa8b595ad182821b6ac0aa Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:10:54 +0300 Subject: [PATCH 44/78] Fix CapabilityService --- agrirouter/messaging/services/messaging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index 5a6cdb80..685934ba 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -52,7 +52,7 @@ def encode(parameters: CapabilityParameters) -> EncodedMessage: enable_push_notifications=parameters.get_enable_push_notification() ) if parameters.get_capability_parameters(): - capability_specification.capabilities = parameters.get_capability_parameters() + capability_specification.capabilities.extend(parameters.get_capability_parameters()) message_payload_parameters = MessagePayloadParameters( type_url=TypeUrl.get_type_url(CapabilitySpecification), From ddacb852d60ba9d95a3773829ca11fc0f26a264f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:11:29 +0300 Subject: [PATCH 45/78] Refactor CapabilityParameters --- agrirouter/messaging/parameters/service.py | 52 ++++++++++++++++------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 48ccbd59..e26167e6 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -3,6 +3,7 @@ from typing import List from agrirouter.generated.commons.chunk_pb2 import ChunkComponent +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.messaging.parameters.dto import MessageParameters, Parameters @@ -16,7 +17,7 @@ def __init__(self, technical_message_type: str = None, mode: str = None, team_set_context_id: str = None, - application_message_seq_no: str = None, + application_message_seq_no: int = None, recipients: list = None, chunk_component: ChunkComponent = None, application_message_id: str = None, @@ -66,13 +67,20 @@ def get_value(self) -> str: class CloudOnboardParameters(MessageParameters): def __init__(self, - # List[EndpointRegistrationDetails] - must be defined in generated by by proto schemes, - # but they are not + *, onboarding_requests: list = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.onboarding_requests = onboarding_requests if onboarding_requests else [] - super(CloudOnboardParameters, self).__init__(**kwargs) + super(CloudOnboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_onboarding_requests(self) -> list: return self.onboarding_requests @@ -90,11 +98,20 @@ def extend_onboarding_requests(self, onboarding_requests: list) -> None: class CloudOffboardParameters(MessageParameters): def __init__(self, + *, endpoints: List[str] = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.endpoints = endpoints if endpoints else [] - super(CloudOffboardParameters, self).__init__(**kwargs) + super(CloudOffboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_endpoints(self) -> List[str]: return self.endpoints @@ -112,17 +129,26 @@ def extend_endpoints(self, endpoints: List[str]) -> None: class CapabilityParameters(MessageParameters): def __init__(self, - application_id, - certification_version_id, - enable_push_notification, - capability_parameters: list = None, - **kwargs + *, + application_id: str, + certification_version_id: str, + enable_push_notification: int = CapabilitySpecification.PushNotification.Value("DISABLED"), + capability_parameters: List[CapabilitySpecification.Capability] = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.application_id = application_id self.certification_version_id = certification_version_id self.enable_push_notification = enable_push_notification self.capability_parameters = capability_parameters if capability_parameters else [] - super(CapabilityParameters, self).__init__(**kwargs) + super(CapabilityParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_application_id(self): return self.application_id From 6f62a2bc891a3121325501d0e1162021cea43ed9 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 10:41:14 +0300 Subject: [PATCH 46/78] Refactor Parameters --- agrirouter/messaging/parameters/service.py | 72 +++++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index e26167e6..631188ae 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -182,9 +182,21 @@ def extend_capability_parameters(self, capability_parameters: list): class FeedConfirmParameters(MessageParameters): - def __init__(self, message_ids: list = None, **kwargs): + def __init__(self, + *, + message_ids: list = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse + ): self.message_ids = message_ids if message_ids else [] - super(FeedConfirmParameters, self).__init__(**kwargs) + super(FeedConfirmParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -201,14 +213,24 @@ def extend_message_ids(self, message_ids): class FeedDeleteParameters(MessageParameters): def __init__(self, + *, message_ids: list = None, receivers: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.message_ids = message_ids if message_ids else [] self.receivers = receivers if receivers else [] self.validity_period = validity_period - super(FeedDeleteParameters, self).__init__(**kwargs) + super(FeedDeleteParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -243,14 +265,24 @@ def set_validity_period(self, validity_period: ValidityPeriod): class ListEndpointsParameters(MessageParameters): def __init__(self, + *, technical_message_type: str = None, direction: int = None, filtered: bool = False, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.technical_message_type = technical_message_type self.direction = direction self.filtered = filtered - super(ListEndpointsParameters, self).__init__(**kwargs) + super(ListEndpointsParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_technical_message_type(self) -> str: return self.technical_message_type @@ -273,14 +305,24 @@ def set_filtered(self, filtered: bool): class QueryMessageParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryMessageParameters, self).__init__(**kwargs) + super(QueryMessageParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders @@ -315,14 +357,24 @@ def set_validity_period(self, validity_period: list) -> None: class QueryHeaderParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryHeaderParameters, self).__init__(**kwargs) + super(QueryHeaderParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders From c711db362bf41f984e1a0974f6543e6813fe3b45 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 15:47:01 +0300 Subject: [PATCH 47/78] Remove redundant methods in HttpMessagingService --- agrirouter/messaging/services/commons.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 10518340..425aa976 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -46,12 +46,6 @@ def send(self, parameters) -> MessagingResult: result = MessagingResult([parameters.get_application_message_id()]) return result - def subscribe(self): - pass - - def unsubscribe(self): - pass - class MqttMessagingService(AbstractMessagingClient): From ba81a1c5c09a8d588f00180ee11a50a6a0c57d6f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:37:09 +0300 Subject: [PATCH 48/78] Move AuthorizationResultUrl, AuthorizationToken, AuthorizationResult to auth/dto.py --- agrirouter/auth/dto.py | 110 +++++++++++++++++++++++++++++++++++ agrirouter/onboarding/dto.py | 106 --------------------------------- 2 files changed, 110 insertions(+), 106 deletions(-) create mode 100644 agrirouter/auth/dto.py diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py new file mode 100644 index 00000000..fec51c0a --- /dev/null +++ b/agrirouter/auth/dto.py @@ -0,0 +1,110 @@ +import json +from typing import Union + +from agrirouter.messaging.exceptions import WrongFieldError + + +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + +class AuthorizationToken: + ACCOUNT = 'account' + REGISTRATION_CODE = 'regcode' + EXPIRES = 'expires' + + def __init__(self, + *, + account: str = None, + regcode: str = None, + expires: str = None + ): + self.account = account + self.regcode = regcode + self.expires = expires + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.ACCOUNT: + self.account = value + elif key == self.REGISTRATION_CODE: + self.regcode = value + elif key == self.EXPIRES: + self.expires = value + else: + raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") + + def get_account(self) -> str: + return self.account + + def set_account(self, account: str) -> None: + self.account = account + + def get_regcode(self) -> str: + return self.regcode + + def set_regcode(self, regcode: str) -> None: + self.regcode = regcode + + def get_expires(self) -> str: + return self.expires + + def set_expires(self, expires: str) -> None: + self.expires = expires + + +class AuthorizationResult: + def __init__(self, + *, + authorization_url: str = None, + state: str = None, + ): + self.authorization_url = authorization_url + self.state = state + + def get_authorization_url(self) -> str: + return self.authorization_url + + def set_authorization_url(self, authorization_url: str) -> None: + self.authorization_url = authorization_url + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state \ No newline at end of file diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index 233c907e..c4cbc314 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -145,112 +145,6 @@ def set_certificate(self, certificate: str) -> None: self.certificate = certificate -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - -class AuthorizationToken: - ACCOUNT = 'account' - REGISTRATION_CODE = 'regcode' - EXPIRES = 'expires' - - def __init__(self, - *, - account: str = None, - regcode: str = None, - expires: str = None - ): - self.account = account - self.regcode = regcode - self.expires = expires - - def json_deserialize(self, data: Union[str, dict]) -> None: - data = data if type(data) == dict else json.loads(data) - for key, value in data.items(): - if key == self.ACCOUNT: - self.account = value - elif key == self.REGISTRATION_CODE: - self.regcode = value - elif key == self.EXPIRES: - self.expires = value - else: - raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") - - def get_account(self) -> str: - return self.account - - def set_account(self, account: str) -> None: - self.account = account - - def get_regcode(self) -> str: - return self.regcode - - def set_regcode(self, regcode: str) -> None: - self.regcode = regcode - - def get_expires(self) -> str: - return self.expires - - def set_expires(self, expires: str) -> None: - self.expires = expires - - -class AuthorizationResult: - def __init__(self, - *, - authorization_url: str = None, - state: str = None, - ): - self.authorization_url = authorization_url - self.state = state - - def get_authorization_url(self) -> str: - return self.authorization_url - - def set_authorization_url(self, authorization_url: str) -> None: - self.authorization_url = authorization_url - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - class ErrorResponse: def __init__(self, *, From 8c29477294d76cbdd6a2e01328406b1700eb74fc Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:37:45 +0300 Subject: [PATCH 49/78] Create helper method let_agrirouter_process_the_message --- tests/sleeper.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/sleeper.py diff --git a/tests/sleeper.py b/tests/sleeper.py new file mode 100644 index 00000000..73eb1d28 --- /dev/null +++ b/tests/sleeper.py @@ -0,0 +1,5 @@ +import time + + +def let_agrirouter_process_the_message(seconds: int = 3): + time.sleep(3) From 4c4927bccb20d5d10a53c9786d396cc9f6bd35a3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:39:09 +0300 Subject: [PATCH 50/78] Remove redundant CU-classes in onboarding --- agrirouter/onboarding/headers.py | 25 +------------ agrirouter/onboarding/onboarding.py | 49 ++++--------------------- agrirouter/onboarding/parameters.py | 52 +-------------------------- agrirouter/onboarding/request.py | 22 +++--------- agrirouter/onboarding/request_body.py | 39 +------------------- agrirouter/onboarding/response.py | 8 ----- 6 files changed, 14 insertions(+), 181 deletions(-) diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index a19fe008..fb598310 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,28 +1,9 @@ import base64 -from abc import ABC, abstractmethod from agrirouter.constants.media_types import ContentTypes -class BaseOnboardingHeader(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - self._set_params(*args, **kwargs) - - @abstractmethod - def get_header(self) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def sign(self, *args, **kwargs): - ... - - -class SoftwareOnboardingHeader(BaseOnboardingHeader): +class SoftwareOnboardingHeader: def __init__(self, reg_code, application_id, @@ -46,7 +27,3 @@ def _set_params(self, reg_code: str, application_id: str, signature: str, conten header["X-Agrirouter-Signature"] = signature if signature else "" self.params = header - - -class CUOnboardingHeader(BaseOnboardingHeader): - pass diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 306dba00..74ded694 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -4,12 +4,11 @@ from agrirouter.environments.environmental_services import EnvironmentalService from agrirouter.onboarding.exceptions import RequestNotSigned -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader -from agrirouter.onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter -from agrirouter.onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, \ - CUOnboardingResponse +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter +from agrirouter.onboarding.request import SoftwareOnboardingRequest +from agrirouter.onboarding.request_body import SoftwareOnboardingBody +from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse class SoftwareOnboarding(EnvironmentalService): @@ -19,7 +18,7 @@ def __init__(self, *args, **kwargs): self._private_key = kwargs.pop("private_key") super(SoftwareOnboarding, self).__init__(*args, **kwargs) - def _create_request(self, params: BaseOnboardingParameter, url: str) -> SoftwareOnboardingRequest: + def _create_request(self, params: SoftwareOnboardingParameter, url: str) -> SoftwareOnboardingRequest: body_params = params.get_body_params() request_body = SoftwareOnboardingBody(**body_params) @@ -28,7 +27,7 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software return SoftwareOnboardingRequest(header=request_header, body=request_body, url=url) - def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: + def _perform_request(self, params: SoftwareOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) request.sign(self._private_key, self._public_key) if request.is_signed: @@ -50,37 +49,3 @@ def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResp http_response = self._perform_request(params=params, url=url) return SoftwareOnboardingResponse(http_response) - - -class CUOnboarding(EnvironmentalService): - - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(CUOnboarding, self).__init__(*args, **kwargs) - - def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardingRequest: - body_params = params.get_body_params() - request_body = CUOnboardingBody(**body_params) - - header_params = params.get_header_params() - request_header = CUOnboardingHeader(**header_params) - - return CUOnboardingRequest(header=request_header, body=request_body, url=url) - - def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: - request = self._create_request(params, url) - request.sign(self._private_key, self._public_key) - if request.is_signed: - return requests.post( - url=request.get_url(), - data=request.get_body_content(), - headers=request.get_header() - ) - raise RequestNotSigned - - def onboard(self, params: CUOnboardingParameter) -> CUOnboardingResponse: - url = self._environment.get_onboard_url() - http_response = self._perform_request(params=params, url=url) - - return CUOnboardingResponse(http_response) diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index 1ca75312..ba5e01e3 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,25 +1,10 @@ -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes -class BaseOnboardingParameter(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_header_params(self, *args, **kwargs): - ... - - @abstractmethod - def get_body_params(self, *args, **kwargs): - ... - - -class SoftwareOnboardingParameter(BaseOnboardingParameter): +class SoftwareOnboardingParameter: def __init__(self, *, id_, @@ -61,38 +46,3 @@ def get_body_params(self): "utc_timestamp": self.utc_timestamp, "time_zone": self.time_zone, } - - -class CUOnboardingParameter(BaseOnboardingParameter): - def __init__(self, - id_, - application_id, - certification_version_id, - gateway_id, - reg_code, - content_type=ContentTypes.APPLICATION_JSON.value, - certificate_type=CertificateTypes.P12.value, - ): - - self.id_ = id_ - self.application_id = application_id - self.content_type = content_type - self.certification_version_id = certification_version_id - self.gateway_id = gateway_id - self.certificate_type = certificate_type - self.reg_code = reg_code - - def get_header_params(self): - return { - "content_type": self.content_type, - "reg_code": self.reg_code, - } - - def get_body_params(self): - return { - "id_": self.id_, - "application_id": self.application_id, - "certification_version_id": self.certification_version_id, - "gateway_id": self.gateway_id, - "certificate_type": self.certificate_type, - } diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index c39d9d7a..0728f149 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -1,10 +1,10 @@ -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.request_body import SoftwareOnboardingBody from agrirouter.onboarding.signature import create_signature, verify_signature -class BaseOnboardingRequest: - def __init__(self, header: BaseOnboardingHeader, body: BaseOnboardingBody, url: str): +class SoftwareOnboardingRequest: + def __init__(self, header: SoftwareOnboardingHeader, body: SoftwareOnboardingBody, url: str): self.header = header self.body = body self.url = url @@ -33,17 +33,3 @@ def is_signed(self): if header_has_signature: return True return False - - -class SoftwareOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard Farming Software or Telemetry Platform - """ - pass - - -class CUOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard CUs - """ - pass diff --git a/agrirouter/onboarding/request_body.py b/agrirouter/onboarding/request_body.py index 045c46a8..a730cf6e 100644 --- a/agrirouter/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -1,30 +1,11 @@ import json -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.onboarding.enums import CertificateTypes, GateWays from agrirouter.onboarding.exceptions import WrongCertificationType, WrongGateWay -class BaseOnboardingBody(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_parameters(self, *args, **kwargs) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def json(self, *args, **kwargs): - ... - - -class SoftwareOnboardingBody(BaseOnboardingBody): +class SoftwareOnboardingBody: def __init__(self, *, id_, @@ -86,21 +67,3 @@ def _validate_certificate_type(certificate_type: str) -> None: def _validate_gateway_id(gateway_id: str) -> None: if gateway_id not in GateWays.values_list(): raise WrongGateWay - - -class CUOnboardingBody(BaseOnboardingBody): - - def __init__(self, *args, **kwargs): - ... - - def get_parameters(self, *args, **kwargs) -> dict: - ... - - def _set_params(self, *args, **kwargs): - ... - - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index d62ce45f..367879d6 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -91,11 +91,3 @@ def get_device_alternate_id(self) -> str: def get_capability_alternate_id(self) -> str: return self.capability_alternate_id - - -class CUOnboardingResponse(BaseOnboardingResonse): - """ - Response from onboarding request used for CUs - """ - pass - From 4ce546863d668ec9f9bf37667bb70142fab78359 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 17:57:55 +0300 Subject: [PATCH 51/78] Refactor BaseEnvieonment --- agrirouter/environments/environments.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 503ecd6b..5c688c7a 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -6,7 +6,7 @@ class BaseEnvironment: _MQTT_URL_TEMPLATE = "ssl://{host}:{port}" _SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE = \ "/application/{application_id}/authorize" \ - "?response_type={response_type}&state={state}&redirect_uri={redirect_uri}" + "?response_type={response_type}&state={state}" _ENV_BASE_URL = "" _API_PREFIX = "" @@ -38,13 +38,13 @@ def get_revoke_url(self) -> str: def get_agrirouter_login_url(self) -> str: return self.get_base_url() + self._AGRIROUTER_LOGIN_URL - def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri) -> str: - return self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( - application_id=application_id, - response_type=response_type, - state=state, - redirect_uri=redirect_uri - ) + def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri=None) -> str: + auth_url = self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=response_type, + state=state + ) + return auth_url + f"&redirect_uri={redirect_uri}" if redirect_uri is not None else auth_url def get_mqtt_server_url(self, host, port) -> str: return self._MQTT_URL_TEMPLATE.format(host=host, port=port) From 819c60c01c91bc03310bd7ae8db7d249d21c5d8f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 18:01:31 +0300 Subject: [PATCH 52/78] Refactor Authorization.__init__ --- agrirouter/auth/auth.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index 6cf1902b..f1e2f634 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -12,10 +12,10 @@ class Authorization(EnvironmentalService): TOKEN_KEY = "token" ERROR_KEY = "error" - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(Authorization, self).__init__(*args, **kwargs) + def __init__(self, env, public_key, private_key): + self._public_key = public_key + self._private_key = private_key + super(Authorization, self).__init__(env) def get_auth_request_url(self, parameters: AuthUrlParameter) -> str: auth_parameters = parameters.get_parameters() From 4aac735bb14485e608a536c701397e4333cc3657 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 18:16:25 +0300 Subject: [PATCH 53/78] Refactor auth --- agrirouter/auth/response.py | 50 ++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index 9aac71b3..b5a73d48 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,6 +5,7 @@ from cryptography.exceptions import InvalidSignature +from agrirouter.auth.dto import AuthorizationToken from agrirouter.onboarding.signature import verify_signature @@ -17,11 +18,11 @@ class AuthResponse: CRED_KEY = "credentials" def __init__(self, query_params): - self._state = query_params.get(self.STATE_KEY, None) - self._signature = query_params.get(self.SIGNATURE_KEY, None) - self._token = query_params.get(self.TOKEN_KEY, None) - self._error = query_params.get(self.ERROR_KEY, None) - self.is_successful = not bool(self._error) + self.state = query_params.get(self.STATE_KEY, None) + self.signature = query_params.get(self.SIGNATURE_KEY, None) + self.token = query_params.get(self.TOKEN_KEY, None) + self.error = query_params.get(self.ERROR_KEY, None) + self.is_successful = not bool(self.error) self._was_verified = False self._is_valid = False @@ -43,8 +44,8 @@ def verify(self, public_key) -> None: :return: """ - encoded_data = self._state + self._token - unquoted_signature = unquote(self._signature) + encoded_data = self.state + self.token + unquoted_signature = unquote(self.signature) encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) try: @@ -58,20 +59,41 @@ def verify(self, public_key) -> None: self._is_valid = True @staticmethod - def decode_token(token: Union[str, bytes]) -> dict: + def decode_token(token: Union[str, bytes]) -> AuthorizationToken: if type(token) == str: token = token.encode("utf-8") base_64_decoded_token = base64.b64decode(token) decoded_token = base_64_decoded_token.decode("utf-8") - return json.loads(decoded_token) + + auth_token = AuthorizationToken() + auth_token.json_deserialize(json.loads(decoded_token)) + return auth_token def get_auth_result(self) -> dict: if not self.is_successful: - return {self.ERROR_KEY: self._error} - decoded_token = self.decode_token(self._token) + return {self.ERROR_KEY: self.error} + decoded_token = self.decode_token(self.token) return { - self.SIGNATURE_KEY: self._signature, - self.STATE_KEY: self._state, - self.TOKEN_KEY: self._token, + self.SIGNATURE_KEY: self.signature, + self.STATE_KEY: self.state, + self.TOKEN_KEY: self.token, self.CRED_KEY: decoded_token } + + def get_signature(self): + return self.signature + + def set_signature(self, signature): + self.signature = signature + + def get_state(self): + return self.state + + def set_state(self, state): + self.state = state + + def get_token(self): + return self.token + + def set_token(self, token): + self.token = token From 8005b5063651d620754f32968a4a2063c695fc38 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 19:13:31 +0300 Subject: [PATCH 54/78] Refactor SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 78 ++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 367879d6..1b9c6591 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,7 +1,9 @@ import json +from typing import Union from requests import Response +from agrirouter.messaging.exceptions import WrongFieldError from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication @@ -26,9 +28,14 @@ class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): Response from verify request used for Farming Software or Telemetry Platform before onboarding """ - def __init__(self, http_response: Response): - super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.account_id = response_body.get("accountId", None) @@ -42,14 +49,29 @@ def __init__(self, http_response: Response): def get_account_id(self) -> str: return self.account_id + def set_account_id(self, account_id: str): + self.account_id = account_id + class SoftwareOnboardingResponse(BaseOnboardingResonse): """ Response from onboarding request used for Farming Software or Telemetry Platform """ - def __init__(self, http_response: Response): - super(SoftwareOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + + DEVICE_ALTERNATE_ID = "deviceAlternateId" + CAPABILITY_ALTERNATE_ID = "capabilityAlternateId" + SENSOR_ALTERNATE_ID = "sensorAlternateId" + CONNECTION_CRITERIA = "connectionCriteria" + AUTHENTICATION = "authentication" + + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.connection_criteria = ConnectionCriteria( gateway_id=response_body.get("connectionCriteria").get("gatewayId"), @@ -80,14 +102,58 @@ def __init__(self, http_response: Response): def get_connection_criteria(self) -> ConnectionCriteria: return self.connection_criteria + def set_connection_criteria(self, connection_criteria: ConnectionCriteria): + self.connection_criteria = connection_criteria + def get_authentication(self) -> Authentication: return self.authentication + def set_authentication(self, authentication: Authentication): + self.authentication = authentication + def get_sensor_alternate_id(self) -> str: return self.sensor_alternate_id + def set_sensor_alternate_id(self, sensor_alternate_id: str): + self.sensor_alternate_id = sensor_alternate_id + def get_device_alternate_id(self) -> str: return self.device_alternate_id + def set_device_alternate_id(self, device_alternate_id: str): + self.device_alternate_id = device_alternate_id + def get_capability_alternate_id(self) -> str: return self.capability_alternate_id + + def set_capability_alternate_id(self, capability_alternate_id: str): + self.capability_alternate_id = capability_alternate_id + + def json_serialize(self): + return { + self.DEVICE_ALTERNATE_ID: self.device_alternate_id, + self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, + self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, + self.CONNECTION_CRITERIA: self.connection_criteria, + self.AUTHENTICATION: self.authentication + } + + def json_deserialize(self, data: Union[dict, str]): + data_dict = data if type(data) == dict else json.loads(data) + for (key, value) in data_dict.items(): + if key == self.DEVICE_ALTERNATE_ID: + self.device_alternate_id = value + if key == self.CAPABILITY_ALTERNATE_ID: + self.capability_alternate_id = value + if key == self.SENSOR_ALTERNATE_ID: + self.sensor_alternate_id = value + if key == self.CONNECTION_CRITERIA: + connection_criteria = ConnectionCriteria() + connection_criteria.json_deserialize(value) + self.connection_criteria = connection_criteria + if key == self.AUTHENTICATION: + authentication = Authentication() + authentication.json_deserialize(value) + self.authentication = authentication + else: + raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") From 55ecf8fbf1cc0df9cdda8b182714e07ef56df4c4 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 19:46:22 +0300 Subject: [PATCH 55/78] Refactor SoftwareOnboardingParameter --- agrirouter/onboarding/parameters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index ba5e01e3..bb62ae2e 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -2,6 +2,7 @@ from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes +from agrirouter.utils.utc_time_util import now_as_utc_str class SoftwareOnboardingParameter: @@ -24,8 +25,7 @@ def __init__(self, self.certification_version_id = certification_version_id self.gateway_id = str(gateway_id) self.certificate_type = certificate_type - self.utc_timestamp = str(utc_timestamp) if utc_timestamp \ - else datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.utc_timestamp = str(utc_timestamp) if utc_timestamp else now_as_utc_str() self.time_zone = str(time_zone) self.reg_code = reg_code From 3658d118da691d8a0c51faa7aaa9606823c9b7d3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 20:14:45 +0300 Subject: [PATCH 56/78] Implement tests for --- .../messaging_test/test_messaging_services.py | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 tests/messaging_test/test_messaging_services.py diff --git a/tests/messaging_test/test_messaging_services.py b/tests/messaging_test/test_messaging_services.py new file mode 100644 index 00000000..3ca06ea7 --- /dev/null +++ b/tests/messaging_test/test_messaging_services.py @@ -0,0 +1,237 @@ +import pytest +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter import CapabilityParameters, CapabilityService, QueryHeaderService, QueryHeaderParameters +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod + +from agrirouter.messaging.builders import CapabilityBuilder +from agrirouter.messaging.decode import decode_response, decode_details +from agrirouter.messaging.services.commons import HttpMessagingService +from agrirouter.messaging.services.http.outbox import OutboxService +from agrirouter.onboarding.response import SoftwareOnboardingResponse +from agrirouter.utils.uuid_util import new_uuid +from tests.sleeper import let_agrirouter_process_the_message + +onboarding_response = SoftwareOnboardingResponse() +onboarding_response.json_deserialize('{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}') +account_id = "fb2921de-592a-49ba-be5e-94044430bc96" +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" + + +def test_given_task_data_capabilities_capability_service_http(): + messaging_service = HttpMessagingService() + capability_parameters = CapabilityParameters( + application_id=application_id, + certification_version_id=certification_version_id, + enable_push_notification=1, + capability_parameters=CapabilityBuilder().with_task_data(2).build(), + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + capability_service = CapabilityService(messaging_service) + messaging_result = capability_service.send(capability_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + +# def test_list_endpoint_service_http(): +# messaging_service = HttpMessagingService() +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_list_endpoint_service_mqtt(): +# client = MqttClient(on_message_callback=foo, client_id="fb2921de-592a-49ba-be5e-94044430bc96") +# messaging_service = MqttMessagingService(onboarding_response, client) +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_valid_subscription_service_http(): +# messaging_service = HttpMessagingService() +# subscription_service = SubscriptionService(messaging_service) +# items = [] +# for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: +# subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +# items.append(subscription_item) +# subscription_parameters = SubscriptionParameters( +# subscription_items=items, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# messaging_result = subscription_service.send(subscription_parameters) +# return messaging_result + + +def test_query_header_message_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + return messaging_result + + +def test_given_validity_and_missing_messages_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_sender_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_message_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_missing_filter_criteria_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 400 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + assert 1 == len(decoded_details.messages) + + for message in decoded_details.messages: + assert "VAL_000017" == message.message_code + assert message.message == "Query does not contain any filtering criteria: messageIds, senders or validityPeriod. Information required to process message is missing or malformed." From a11821b9666bb6f5d00b05a5465d8dd9494b928c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 29 Oct 2021 00:03:24 +0300 Subject: [PATCH 57/78] Refactor messaging test --- tests/messaging_test/test_messaging_services.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/messaging_test/test_messaging_services.py b/tests/messaging_test/test_messaging_services.py index 3ca06ea7..4d5a5e15 100644 --- a/tests/messaging_test/test_messaging_services.py +++ b/tests/messaging_test/test_messaging_services.py @@ -4,6 +4,7 @@ from agrirouter import CapabilityParameters, CapabilityService, QueryHeaderService, QueryHeaderParameters from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification from agrirouter.messaging.builders import CapabilityBuilder from agrirouter.messaging.decode import decode_response, decode_details from agrirouter.messaging.services.commons import HttpMessagingService @@ -25,7 +26,9 @@ def test_given_task_data_capabilities_capability_service_http(): application_id=application_id, certification_version_id=certification_version_id, enable_push_notification=1, - capability_parameters=CapabilityBuilder().with_task_data(2).build(), + capability_parameters=CapabilityBuilder().with_task_data( + CapabilitySpecification.Direction.Value("SEND_RECEIVE") + ).build(), onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, From b6152c54735ccd7308f3c530906353da448f847f Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 2 Nov 2021 12:43:38 +0300 Subject: [PATCH 58/78] Refactor project (#16) --- agrirouter/auth/auth.py | 8 +- agrirouter/auth/dto.py | 110 ++++++++ agrirouter/auth/response.py | 50 +++- agrirouter/environments/environments.py | 16 +- agrirouter/messaging/builders.py | 201 +++++++++++++++ agrirouter/messaging/clients/http.py | 62 +++-- agrirouter/messaging/clients/mqtt.py | 9 +- agrirouter/messaging/decode.py | 33 ++- agrirouter/messaging/encode.py | 13 +- agrirouter/messaging/enums.py | 15 ++ agrirouter/messaging/exceptions.py | 8 + agrirouter/messaging/messages.py | 22 +- agrirouter/messaging/parameters/dto.py | 14 +- agrirouter/messaging/parameters/service.py | 147 ++++++++--- agrirouter/messaging/request.py | 2 +- agrirouter/messaging/result.py | 8 +- agrirouter/messaging/services/cloud.py | 4 +- agrirouter/messaging/services/commons.py | 40 ++- agrirouter/messaging/services/http/outbox.py | 23 +- agrirouter/messaging/services/messaging.py | 16 +- agrirouter/onboarding/dto.py | 106 -------- agrirouter/onboarding/exceptions.py | 4 + agrirouter/onboarding/headers.py | 25 +- agrirouter/onboarding/onboarding.py | 49 +--- agrirouter/onboarding/parameters.py | 56 +--- agrirouter/onboarding/request.py | 22 +- agrirouter/onboarding/request_body.py | 39 +-- agrirouter/onboarding/response.py | 88 ++++++- agrirouter/revoking/parameters.py | 6 +- agrirouter/revoking/request.py | 6 +- agrirouter/revoking/request_body.py | 11 +- agrirouter/revoking/revoking.py | 6 +- agrirouter/utils/type_url.py | 48 ++-- agrirouter/utils/utc_time_util.py | 4 + agrirouter/utils/uuid_util.py | 2 +- example_script.py | 130 ++++++++++ examples.txt | 40 +++ .../messaging_test/test_messaging_services.py | 240 ++++++++++++++++++ tests/sleeper.py | 5 + tests/test_revoking/test_parameters.py | 8 +- 40 files changed, 1202 insertions(+), 494 deletions(-) create mode 100644 agrirouter/auth/dto.py create mode 100644 agrirouter/messaging/builders.py create mode 100644 example_script.py create mode 100644 tests/messaging_test/test_messaging_services.py create mode 100644 tests/sleeper.py diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index 6cf1902b..f1e2f634 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -12,10 +12,10 @@ class Authorization(EnvironmentalService): TOKEN_KEY = "token" ERROR_KEY = "error" - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(Authorization, self).__init__(*args, **kwargs) + def __init__(self, env, public_key, private_key): + self._public_key = public_key + self._private_key = private_key + super(Authorization, self).__init__(env) def get_auth_request_url(self, parameters: AuthUrlParameter) -> str: auth_parameters = parameters.get_parameters() diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py new file mode 100644 index 00000000..fec51c0a --- /dev/null +++ b/agrirouter/auth/dto.py @@ -0,0 +1,110 @@ +import json +from typing import Union + +from agrirouter.messaging.exceptions import WrongFieldError + + +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + +class AuthorizationToken: + ACCOUNT = 'account' + REGISTRATION_CODE = 'regcode' + EXPIRES = 'expires' + + def __init__(self, + *, + account: str = None, + regcode: str = None, + expires: str = None + ): + self.account = account + self.regcode = regcode + self.expires = expires + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.ACCOUNT: + self.account = value + elif key == self.REGISTRATION_CODE: + self.regcode = value + elif key == self.EXPIRES: + self.expires = value + else: + raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") + + def get_account(self) -> str: + return self.account + + def set_account(self, account: str) -> None: + self.account = account + + def get_regcode(self) -> str: + return self.regcode + + def set_regcode(self, regcode: str) -> None: + self.regcode = regcode + + def get_expires(self) -> str: + return self.expires + + def set_expires(self, expires: str) -> None: + self.expires = expires + + +class AuthorizationResult: + def __init__(self, + *, + authorization_url: str = None, + state: str = None, + ): + self.authorization_url = authorization_url + self.state = state + + def get_authorization_url(self) -> str: + return self.authorization_url + + def set_authorization_url(self, authorization_url: str) -> None: + self.authorization_url = authorization_url + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state \ No newline at end of file diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index 9aac71b3..b5a73d48 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,6 +5,7 @@ from cryptography.exceptions import InvalidSignature +from agrirouter.auth.dto import AuthorizationToken from agrirouter.onboarding.signature import verify_signature @@ -17,11 +18,11 @@ class AuthResponse: CRED_KEY = "credentials" def __init__(self, query_params): - self._state = query_params.get(self.STATE_KEY, None) - self._signature = query_params.get(self.SIGNATURE_KEY, None) - self._token = query_params.get(self.TOKEN_KEY, None) - self._error = query_params.get(self.ERROR_KEY, None) - self.is_successful = not bool(self._error) + self.state = query_params.get(self.STATE_KEY, None) + self.signature = query_params.get(self.SIGNATURE_KEY, None) + self.token = query_params.get(self.TOKEN_KEY, None) + self.error = query_params.get(self.ERROR_KEY, None) + self.is_successful = not bool(self.error) self._was_verified = False self._is_valid = False @@ -43,8 +44,8 @@ def verify(self, public_key) -> None: :return: """ - encoded_data = self._state + self._token - unquoted_signature = unquote(self._signature) + encoded_data = self.state + self.token + unquoted_signature = unquote(self.signature) encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) try: @@ -58,20 +59,41 @@ def verify(self, public_key) -> None: self._is_valid = True @staticmethod - def decode_token(token: Union[str, bytes]) -> dict: + def decode_token(token: Union[str, bytes]) -> AuthorizationToken: if type(token) == str: token = token.encode("utf-8") base_64_decoded_token = base64.b64decode(token) decoded_token = base_64_decoded_token.decode("utf-8") - return json.loads(decoded_token) + + auth_token = AuthorizationToken() + auth_token.json_deserialize(json.loads(decoded_token)) + return auth_token def get_auth_result(self) -> dict: if not self.is_successful: - return {self.ERROR_KEY: self._error} - decoded_token = self.decode_token(self._token) + return {self.ERROR_KEY: self.error} + decoded_token = self.decode_token(self.token) return { - self.SIGNATURE_KEY: self._signature, - self.STATE_KEY: self._state, - self.TOKEN_KEY: self._token, + self.SIGNATURE_KEY: self.signature, + self.STATE_KEY: self.state, + self.TOKEN_KEY: self.token, self.CRED_KEY: decoded_token } + + def get_signature(self): + return self.signature + + def set_signature(self, signature): + self.signature = signature + + def get_state(self): + return self.state + + def set_state(self, state): + self.state = state + + def get_token(self): + return self.token + + def set_token(self, token): + self.token = token diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 503ecd6b..5c688c7a 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -6,7 +6,7 @@ class BaseEnvironment: _MQTT_URL_TEMPLATE = "ssl://{host}:{port}" _SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE = \ "/application/{application_id}/authorize" \ - "?response_type={response_type}&state={state}&redirect_uri={redirect_uri}" + "?response_type={response_type}&state={state}" _ENV_BASE_URL = "" _API_PREFIX = "" @@ -38,13 +38,13 @@ def get_revoke_url(self) -> str: def get_agrirouter_login_url(self) -> str: return self.get_base_url() + self._AGRIROUTER_LOGIN_URL - def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri) -> str: - return self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( - application_id=application_id, - response_type=response_type, - state=state, - redirect_uri=redirect_uri - ) + def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri=None) -> str: + auth_url = self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=response_type, + state=state + ) + return auth_url + f"&redirect_uri={redirect_uri}" if redirect_uri is not None else auth_url def get_mqtt_server_url(self, host, port) -> str: return self._MQTT_URL_TEMPLATE.format(host=host, port=port) diff --git a/agrirouter/messaging/builders.py b/agrirouter/messaging/builders.py new file mode 100644 index 00000000..94e56ce8 --- /dev/null +++ b/agrirouter/messaging/builders.py @@ -0,0 +1,201 @@ +from typing import List + +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.enums import CapabilityType + + +class SubscriptionItemBuilder: + + def __init__(self): + self._subscription_items = [] + + def build(self): + return self._subscription_items + + def clear(self): + self._subscription_items = [] + + def with_task_data(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_device_description(self, ddis: List[int]=None, position: bool=None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_time_log(self, ddis: List[int] = None, position: bool = None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_bmp(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_BMP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_jpg(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_JPEG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_png(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_PNG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_shape(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.SHP_SHAPE_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_pdf(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.DOC_PDF.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_avi(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_AVI.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_mp4(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_MP4.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_wmv(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_WMV.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_gps_info(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.GPS_INFO.value + ) + self._subscription_items.append(subscription_item) + return self + + +class CapabilityBuilder: + + def __init__(self): + self._capabilities = [] + + def build(self) -> list: + return self._capabilities + + def clear(self): + self._capabilities = [] + + def with_task_data(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TASKDATA_ZIP.value + self._capabilities.append(capability) + return self + + def with_device_description(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_time_log(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_bmp(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_BMP.value + self._capabilities.append(capability) + return self + + def with_jpg(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_JPEG.value + self._capabilities.append(capability) + return self + + def with_png(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_PNG.value + self._capabilities.append(capability) + return self + + def with_shape(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.SHP_SHAPE_ZIP.value + self._capabilities.append(capability) + return self + + def with_pdf(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.DOC_PDF.value + self._capabilities.append(capability) + return self + + def with_avi(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_AVI.value + self._capabilities.append(capability) + return self + + def with_mp4(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_MP4.value + self._capabilities.append(capability) + return self + + def with_wmv(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_WMV.value + self._capabilities.append(capability) + return self + + def with_gps_info(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.GPS_INFO.value + self._capabilities.append(capability) + return self diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index fa3110e9..429be1e7 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -2,6 +2,7 @@ import json import os import ssl +from urllib.parse import urlparse from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.onboarding.dto import ConnectionCriteria @@ -10,18 +11,12 @@ class HttpClient: - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } - def __init__( - self, - on_message_callback: callable, - timeout=20 - ): - self.on_message_callback = on_message_callback - self.timeout = timeout - - @staticmethod - def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + def make_connection(self, certificate_file_path: str, uri: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( certfile=certificate_file_path, @@ -29,27 +24,43 @@ def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboar password=onboard_response.get_authentication().get_secret(), ) connection = http.client.HTTPSConnection( - host=onboard_response.connection_criteria.get_host(), - port=onboard_response.connection_criteria.get_port(), + host=self.get_host(uri), + port=self.get_port(uri), context=context ) return connection - def send(self, method: str, onboard_response: SoftwareOnboardingResponse, request_body=None): + def send_measure(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="POST", + uri=onboard_response.get_connection_criteria().get_measures(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send_command(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="GET", + uri=onboard_response.get_connection_criteria().get_commands(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send(self, method: str, uri: str, onboard_response: SoftwareOnboardingResponse, request_body=None): certificate_file_path = create_certificate_file_from_pen(onboard_response) try: - connection = self.make_connection(certificate_file_path, onboard_response) + connection = self.make_connection(certificate_file_path, uri, onboard_response) if request_body is not None: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(uri), headers=self.headers, - body=json.dumps(request_body) + body=json.dumps(request_body.json_serialize()) ) else: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(uri), headers=self.headers, ) response = connection.getresponse() @@ -58,11 +69,14 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques return response - def subscribe(self): - pass + @staticmethod + def get_host(uri): + return urlparse(uri).netloc - def unsubscribe(self): - pass + @staticmethod + def get_port(uri): + return urlparse(uri).port if urlparse(uri).port else None - def _start_loop(self): - pass + @staticmethod + def get_path(uri): + return urlparse(uri).path diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 4e8dd555..72ff399d 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -7,7 +7,7 @@ class MqttClient: def __init__(self, - client_id: str = "", + client_id, on_message_callback: callable = None, userdata: Any = None, clean_session: bool = True @@ -88,6 +88,13 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback() -> callable: def on_connect(client, userdata, flags, rc, properties=None): + print("Connection started") + with open("connection.txt", "w") as file: + file.write("Connection started") + if rc == 0: + file.write("Connected!!") + else: + file.write("Do not Connected!!") if rc == 0: print("Connected to MQTT Broker!") else: diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index 839a0e48..02387153 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -9,6 +9,7 @@ from agrirouter.generated.messaging.response.payload.feed.feed_response_pb2 import HeaderQueryResponse, \ MessageQueryResponse from agrirouter.generated.messaging.response.response_pb2 import ResponseEnvelope, ResponsePayloadWrapper +from agrirouter.messaging.exceptions import DecodeMessageException from agrirouter.messaging.messages import DecodedMessage from agrirouter.utils.type_url import TypeUrl @@ -31,8 +32,10 @@ def decode_response(message: bytes) -> DecodedMessage: input_stream = base64.b64decode(message) response_envelope_buffer, response_payload_buffer = read_properties_buffers_from_input_stream(input_stream) - envelope = ResponseEnvelope().MergeFromString(response_envelope_buffer) - payload = ResponsePayloadWrapper().MergeFromString(response_payload_buffer) + envelope = ResponseEnvelope() + envelope.ParseFromString(response_envelope_buffer) + payload = ResponsePayloadWrapper() + payload.ParseFromString(response_payload_buffer) message = DecodedMessage(envelope, payload) @@ -40,11 +43,21 @@ def decode_response(message: bytes) -> DecodedMessage: def decode_details(details: Any): - if details.type_url == TypeUrl.get_type_url(Messages.__name__): - return Messages().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse.__name__): - return ListEndpointsResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse.__name__): - return HeaderQueryResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse.__name__): - return MessageQueryResponse().MergeFromString(details.value) + if details.type_url == TypeUrl.get_type_url(Messages): + messages = Messages() + messages.MergeFromString(details.value) + return messages + elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse): + list_endpoints_response = ListEndpointsResponse() + list_endpoints_response.MergeFromString(details.value) + return list_endpoints_response + elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse): + header_query_response = HeaderQueryResponse() + header_query_response.MergeFromString(details.value) + return header_query_response + elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse): + message_query_response = MessageQueryResponse() + message_query_response.MergeFromString(details.value) + return message_query_response + else: + raise DecodeMessageException(f"Could not handle type {details.type_url} while decoding details.") diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py index fe74c229..f1f40513 100644 --- a/agrirouter/messaging/encode.py +++ b/agrirouter/messaging/encode.py @@ -18,23 +18,26 @@ def write_proto_parts_to_buffer(parts: list, buffer: bytes = b""): return buffer -def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> bytes: +def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> str: request_envelope = encode_header(header_parameters) request_payload = encode_payload(payload_parameters) raw_data = write_proto_parts_to_buffer([request_envelope, request_payload]) - return base64.b64encode(raw_data) + return base64.b64encode(raw_data).decode() def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope: request_envelope = RequestEnvelope() - request_envelope.application_id = header_parameters.get_application_message_id() \ + request_envelope.application_message_id = header_parameters.get_application_message_id() \ if header_parameters.get_application_message_id() else new_uuid() request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() request_envelope.technical_message_type = header_parameters.get_technical_message_type() request_envelope.mode = header_parameters.get_mode() - request_envelope.timestamp = now_as_utc_timestamp() + if header_parameters.get_team_set_context_id() is not None: + request_envelope.team_set_context_id = header_parameters.get_team_set_context_id() + request_envelope.timestamp.FromDatetime(now_as_utc_timestamp()) + return request_envelope @@ -42,5 +45,5 @@ def encode_payload(payload_parameters: MessagePayloadParameters) -> RequestPaylo any_proto_wrapper = Any() any_proto_wrapper.type_url = payload_parameters.get_type_url() any_proto_wrapper.value = payload_parameters.get_value() - request_payload = RequestPayloadWrapper(any_proto_wrapper) + request_payload = RequestPayloadWrapper(details=any_proto_wrapper) return request_payload diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py index 1247118f..53265ec5 100644 --- a/agrirouter/messaging/enums.py +++ b/agrirouter/messaging/enums.py @@ -13,3 +13,18 @@ class TechnicalMessageType(BaseEnum): FEED_MESSAGE_QUERY = "dke:feed_message_query" CLOUD_ONBOARD_ENDPOINTS = "dke:cloud_onboard_endpoints" CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" + + +class CapabilityType(BaseEnum): + ISO_11783_TASKDATA_ZIP = "iso:11783:-10:taskdata:zip" + ISO_11783_DEVICE_DESCRIPTION_PROTOBUF = "iso:11783:-10:device_description:protobuf" + ISO_11783_TIMELOG_PROTOBUF = "iso:11783:-10:time_log:protobuf" + IMG_BMP = "img:bmp" + IMG_JPEG = "img:jpeg" + IMG_PNG = "img:png" + SHP_SHAPE_ZIP = "shp:shape:zip" + DOC_PDF = "doc:pdf" + VID_AVI = "vid:avi" + VID_MP4 = "vid:mp4" + VID_WMV = "vid:wmv" + GPS_INFO = "gps:info" diff --git a/agrirouter/messaging/exceptions.py b/agrirouter/messaging/exceptions.py index 3df29d4c..2bc8b9a7 100644 --- a/agrirouter/messaging/exceptions.py +++ b/agrirouter/messaging/exceptions.py @@ -7,3 +7,11 @@ class TypeUrlNotFoundError(AgriRouuterBaseException): class WrongFieldError(AgriRouuterBaseException): _message = "Unknown field" + + +class DecodeMessageException(AgriRouuterBaseException): + _message = "Can't decode message" + + +class OutboxException(AgriRouuterBaseException): + _message = "Can't fetch outbox message" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 1e5228fd..da475631 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -3,6 +3,7 @@ from typing import Union, List, Dict from agrirouter.messaging.exceptions import WrongFieldError +from agrirouter.utils.utc_time_util import now_as_utc_str class EncodedMessage: @@ -36,12 +37,12 @@ class Message: def __init__(self, content): self.content = content - self.timestamp = datetime.utcnow() + self.timestamp = now_as_utc_str() def json_serialize(self) -> dict: return { self.MESSAGE: self.content, - self.TIMESTAMP: self.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.TIMESTAMP: self.timestamp } @@ -52,8 +53,8 @@ def __init__(self, message: str = None): self.message = message def json_deserialize(self, data: Union[Dict[str, str], str]): - messages = data if type(data) == list else json.loads(data) - for key, value in messages.keys(): + messages = data if type(data) == dict else json.loads(data) + for key, value in messages.items(): if key == self.MESSAGE: self.message = value else: @@ -81,15 +82,17 @@ def __init__(self, self.sensor_alternate_id = sensor_alternate_id self.command = command - def json_deserialize(self, data: Union[list, str]): - data = data if type(data) == list else json.loads(data) - for key, value in data.keys(): + def json_deserialize(self, data: Union[dict, str]): + data = data if type(data) == dict else json.loads(data) + for (key, value) in data.items(): if key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value elif key == self.COMMAND: - self.command = Command.json_deserialize(value) + command = Command() + command.json_deserialize(value) + self.command = command else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") @@ -110,6 +113,3 @@ def get_command(self) -> Command: def set_command(self, command: Command) -> None: self.command = command - - def json_deserialize(self): - pass diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py index 3c1404de..17998bf0 100644 --- a/agrirouter/messaging/parameters/dto.py +++ b/agrirouter/messaging/parameters/dto.py @@ -8,8 +8,8 @@ class Parameters: def __init__(self, *, - application_message_seq_no: str, - application_message_id: int = None, + application_message_seq_no: int, + application_message_id: str = None, team_set_context_id: str ): self.application_message_seq_no = application_message_seq_no @@ -41,9 +41,9 @@ def validate(self): class MessageParameters(Parameters): def __init__(self, *, - application_message_seq_no: str, - application_message_id: int, - team_set_context_id: str, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse ): super(MessageParameters, self).__init__( @@ -54,7 +54,7 @@ def __init__(self, self.onboarding_response = onboarding_response - def get_onboarding_response(self): + def get_onboarding_response(self) -> BaseOnboardingResonse: return self.onboarding_response @@ -63,7 +63,7 @@ class MessagingParameters(MessageParameters): def __init__(self, *, application_message_seq_no: str = None, - application_message_id: int = None, + application_message_id: str = None, team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse, encoded_messages=None diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 0769fa08..631188ae 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -3,9 +3,11 @@ from typing import List from agrirouter.generated.commons.chunk_pb2 import ChunkComponent +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.messaging.parameters.dto import MessageParameters, Parameters +from agrirouter.onboarding.response import BaseOnboardingResonse class MessageHeaderParameters(Parameters): @@ -15,10 +17,10 @@ def __init__(self, technical_message_type: str = None, mode: str = None, team_set_context_id: str = None, - application_message_seq_no: str = None, + application_message_seq_no: int = None, recipients: list = None, chunk_component: ChunkComponent = None, - application_message_id: int = None, + application_message_id: str = None, ): super(MessageHeaderParameters, self).__init__( application_message_seq_no=application_message_seq_no, @@ -65,13 +67,20 @@ def get_value(self) -> str: class CloudOnboardParameters(MessageParameters): def __init__(self, - # List[EndpointRegistrationDetails] - must be defined in generated by by proto schemes, - # but they are not + *, onboarding_requests: list = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.onboarding_requests = onboarding_requests if onboarding_requests else [] - super(CloudOnboardParameters, self).__init__(**kwargs) + super(CloudOnboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_onboarding_requests(self) -> list: return self.onboarding_requests @@ -89,11 +98,20 @@ def extend_onboarding_requests(self, onboarding_requests: list) -> None: class CloudOffboardParameters(MessageParameters): def __init__(self, + *, endpoints: List[str] = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.endpoints = endpoints if endpoints else [] - super(CloudOffboardParameters, self).__init__(**kwargs) + super(CloudOffboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_endpoints(self) -> List[str]: return self.endpoints @@ -111,17 +129,26 @@ def extend_endpoints(self, endpoints: List[str]) -> None: class CapabilityParameters(MessageParameters): def __init__(self, - application_id, - certification_version_id, - enable_push_notification, - capability_parameters: list = None, - **kwargs + *, + application_id: str, + certification_version_id: str, + enable_push_notification: int = CapabilitySpecification.PushNotification.Value("DISABLED"), + capability_parameters: List[CapabilitySpecification.Capability] = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.application_id = application_id self.certification_version_id = certification_version_id self.enable_push_notification = enable_push_notification self.capability_parameters = capability_parameters if capability_parameters else [] - super(CapabilityParameters, self).__init__(**kwargs) + super(CapabilityParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_application_id(self): return self.application_id @@ -155,9 +182,21 @@ def extend_capability_parameters(self, capability_parameters: list): class FeedConfirmParameters(MessageParameters): - def __init__(self, message_ids: list = None, **kwargs): + def __init__(self, + *, + message_ids: list = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse + ): self.message_ids = message_ids if message_ids else [] - super(FeedConfirmParameters, self).__init__(**kwargs) + super(FeedConfirmParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -174,14 +213,24 @@ def extend_message_ids(self, message_ids): class FeedDeleteParameters(MessageParameters): def __init__(self, + *, message_ids: list = None, receivers: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.message_ids = message_ids if message_ids else [] self.receivers = receivers if receivers else [] self.validity_period = validity_period - super(FeedDeleteParameters, self).__init__(**kwargs) + super(FeedDeleteParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -216,14 +265,24 @@ def set_validity_period(self, validity_period: ValidityPeriod): class ListEndpointsParameters(MessageParameters): def __init__(self, + *, technical_message_type: str = None, - direction: str = None, + direction: int = None, filtered: bool = False, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.technical_message_type = technical_message_type self.direction = direction self.filtered = filtered - super(ListEndpointsParameters, self).__init__(**kwargs) + super(ListEndpointsParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_technical_message_type(self) -> str: return self.technical_message_type @@ -231,10 +290,10 @@ def get_technical_message_type(self) -> str: def set_technical_message_type(self, technical_message_type: str): self.technical_message_type = technical_message_type - def get_direction(self) -> str: + def get_direction(self) -> int: return self.direction - def set_direction(self, direction: str): + def set_direction(self, direction: int): self.direction = direction def is_filtered(self): @@ -246,14 +305,24 @@ def set_filtered(self, filtered: bool): class QueryMessageParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryMessageParameters, self).__init__(**kwargs) + super(QueryMessageParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders @@ -288,14 +357,24 @@ def set_validity_period(self, validity_period: list) -> None: class QueryHeaderParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryHeaderParameters, self).__init__(**kwargs) + super(QueryHeaderParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders @@ -330,10 +409,20 @@ def set_validity_period(self, validity_period: list) -> None: class SubscriptionParameters(MessageParameters): def __init__(self, + *, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, subscription_items: List[Subscription.MessageTypeSubscriptionItem] = None, - **kwargs): + ): self.subscription_items = subscription_items if subscription_items else [] - super(SubscriptionParameters, self).__init__(**kwargs) + super(SubscriptionParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_subscription_items(self) -> List[Subscription.MessageTypeSubscriptionItem]: return self.subscription_items diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py index 4de0a69e..9cc9507e 100644 --- a/agrirouter/messaging/request.py +++ b/agrirouter/messaging/request.py @@ -11,7 +11,7 @@ class MessageRequest: def __init__(self, sensor_alternate_id: str, capability_alternate_id: str, - messages: List[Message] + messages: List[dict] ): self.sensor_alternate_id = sensor_alternate_id self.capability_alternate_id = capability_alternate_id diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py index ba2df495..d08c7f4b 100644 --- a/agrirouter/messaging/result.py +++ b/agrirouter/messaging/result.py @@ -26,7 +26,13 @@ def __init__(self, def json_deserialize(self, data: Union[list, str]): messages = data if type(data) == list else json.loads(data) - self.set_messages([OutboxMessage.json_deserialize(message) for message in messages]) + outbox_message_list = [] + for message in messages: + outbox_message = OutboxMessage() + outbox_message.json_deserialize(message) + outbox_message_list.append(outbox_message) + + self.set_messages(outbox_message_list) def get_status_code(self) -> int: return self.status_code diff --git a/agrirouter/messaging/services/cloud.py b/agrirouter/messaging/services/cloud.py index cd40a465..217462e2 100644 --- a/agrirouter/messaging/services/cloud.py +++ b/agrirouter/messaging/services/cloud.py @@ -28,7 +28,7 @@ def encode(parameters: CloudOnboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OnboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OnboardingRequest), value=onboarding_request.SerializeToString() ) @@ -58,7 +58,7 @@ def encode(parameters: CloudOffboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OffboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OffboardingRequest), value=offboarding_request.SerializeToString() ) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 939a6fdc..425aa976 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -1,14 +1,16 @@ +import json import os from abc import ABC, abstractmethod import requests -from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult +from agrirouter.onboarding.exceptions import BadMessagingResult class AbstractMessagingClient(ABC): @@ -18,10 +20,10 @@ def create_message_request(parameters) -> MessageRequest: messages = [] for encoded_message in parameters.get_encoded_messages(): message = Message(encoded_message) - messages.append(message) + messages.append(message.json_serialize()) message_request = MessageRequest( - parameters.get_sensor_alternate_id(), - parameters.get_capability_alternate_id(), + parameters.get_onboarding_response().get_sensor_alternate_id(), + parameters.get_onboarding_response().get_capability_alternate_id(), messages ) return message_request @@ -33,36 +35,29 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self): + self.client = HttpClient() def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) - response = self.client.send( - "POST", - parameters.get_onboarding_response(), - request - ) - result = MessagingResult([parameters.get_message_id()]) + response = self.client.send_measure(parameters.get_onboarding_response(), request) + if response.status != 200: + raise BadMessagingResult(f"Messaging Request failed with status code {response.status}") + result = MessagingResult([parameters.get_application_message_id()]) return result - def subscribe(self): - pass - - def unsubscribe(self): - pass - class MqttMessagingService(AbstractMessagingClient): def __init__(self, + client_id, onboarding_response, on_message_callback: callable = None, ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=self.onboarding_response.get_client_id(), + client_id=client_id, on_message_callback=on_message_callback, ) self.client.connect( @@ -71,12 +66,13 @@ def __init__(self, ) def send(self, parameters, qos: int = 0) -> MessagingResult: - mqtt_payload = self.create_message_request(parameters) + message_request = self.create_message_request(parameters) + mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, + self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), qos=qos ) - result = MessagingResult([parameters.get_message_id()]) + result = MessagingResult([parameters.get_application_message_id()]) return result def subscribe(self): diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index 72328072..13db25cf 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -1,8 +1,11 @@ +import json import os import requests from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.exceptions import OutboxException +from agrirouter.messaging.messages import OutboxMessage from agrirouter.messaging.result import OutboxResponse from agrirouter.messaging.certification import create_certificate_file_from_pen @@ -10,18 +13,18 @@ class OutboxService: - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self): + self.client = HttpClient() def fetch(self, onboarding_response) -> OutboxResponse: - response = self.client.send( - "GET", - onboarding_response, - None - ) - - outbox_response = OutboxResponse(status_code=response.status_code) - outbox_response.json_deserialize(response.json()["contents"]) + response = self.client.send_command(onboarding_response, None) + + if response.status == 200: + outbox_response = OutboxResponse(status_code=response.status) + response_body = response.read() + outbox_response.json_deserialize(response_body) + else: + raise OutboxException(f"Could not fetch messages from outbox. Status code was {response.status}") return outbox_response diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index e071b028..685934ba 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -52,10 +52,10 @@ def encode(parameters: CapabilityParameters) -> EncodedMessage: enable_push_notifications=parameters.get_enable_push_notification() ) if parameters.get_capability_parameters(): - capability_specification.capabilities = parameters.get_capability_parameters() + capability_specification.capabilities.extend(parameters.get_capability_parameters()) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(CapabilitySpecification.__name__), + type_url=TypeUrl.get_type_url(CapabilitySpecification), value=capability_specification.SerializeToString() ) @@ -85,7 +85,7 @@ def encode(parameters: FeedConfirmParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -115,7 +115,7 @@ def encode(parameters: FeedDeleteParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -147,7 +147,7 @@ def encode(parameters: ListEndpointsParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(ListEndpointsQuery.__name__), + type_url=TypeUrl.get_type_url(ListEndpointsQuery), value=list_endpoints_query.SerializeToString() ) @@ -179,7 +179,7 @@ def encode(parameters: QueryMessageParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -211,7 +211,7 @@ def encode(parameters: QueryHeaderParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -241,7 +241,7 @@ def encode(parameters: SubscriptionParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(Subscription.__name__), + type_url=TypeUrl.get_type_url(Subscription), value=subscription.SerializeToString() ) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index 233c907e..c4cbc314 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -145,112 +145,6 @@ def set_certificate(self, certificate: str) -> None: self.certificate = certificate -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - -class AuthorizationToken: - ACCOUNT = 'account' - REGISTRATION_CODE = 'regcode' - EXPIRES = 'expires' - - def __init__(self, - *, - account: str = None, - regcode: str = None, - expires: str = None - ): - self.account = account - self.regcode = regcode - self.expires = expires - - def json_deserialize(self, data: Union[str, dict]) -> None: - data = data if type(data) == dict else json.loads(data) - for key, value in data.items(): - if key == self.ACCOUNT: - self.account = value - elif key == self.REGISTRATION_CODE: - self.regcode = value - elif key == self.EXPIRES: - self.expires = value - else: - raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") - - def get_account(self) -> str: - return self.account - - def set_account(self, account: str) -> None: - self.account = account - - def get_regcode(self) -> str: - return self.regcode - - def set_regcode(self, regcode: str) -> None: - self.regcode = regcode - - def get_expires(self) -> str: - return self.expires - - def set_expires(self, expires: str) -> None: - self.expires = expires - - -class AuthorizationResult: - def __init__(self, - *, - authorization_url: str = None, - state: str = None, - ): - self.authorization_url = authorization_url - self.state = state - - def get_authorization_url(self) -> str: - return self.authorization_url - - def set_authorization_url(self, authorization_url: str) -> None: - self.authorization_url = authorization_url - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - class ErrorResponse: def __init__(self, *, diff --git a/agrirouter/onboarding/exceptions.py b/agrirouter/onboarding/exceptions.py index 490e51e3..fb3b024a 100644 --- a/agrirouter/onboarding/exceptions.py +++ b/agrirouter/onboarding/exceptions.py @@ -21,3 +21,7 @@ class RequestNotSigned(AgriRouuterBaseException): Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/ integration/onboarding.html#signing-requests """ + + +class BadMessagingResult(AgriRouuterBaseException): + _message = "Messaging Request failed" diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index a19fe008..fb598310 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,28 +1,9 @@ import base64 -from abc import ABC, abstractmethod from agrirouter.constants.media_types import ContentTypes -class BaseOnboardingHeader(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - self._set_params(*args, **kwargs) - - @abstractmethod - def get_header(self) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def sign(self, *args, **kwargs): - ... - - -class SoftwareOnboardingHeader(BaseOnboardingHeader): +class SoftwareOnboardingHeader: def __init__(self, reg_code, application_id, @@ -46,7 +27,3 @@ def _set_params(self, reg_code: str, application_id: str, signature: str, conten header["X-Agrirouter-Signature"] = signature if signature else "" self.params = header - - -class CUOnboardingHeader(BaseOnboardingHeader): - pass diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 306dba00..74ded694 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -4,12 +4,11 @@ from agrirouter.environments.environmental_services import EnvironmentalService from agrirouter.onboarding.exceptions import RequestNotSigned -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader -from agrirouter.onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter -from agrirouter.onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, \ - CUOnboardingResponse +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter +from agrirouter.onboarding.request import SoftwareOnboardingRequest +from agrirouter.onboarding.request_body import SoftwareOnboardingBody +from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse class SoftwareOnboarding(EnvironmentalService): @@ -19,7 +18,7 @@ def __init__(self, *args, **kwargs): self._private_key = kwargs.pop("private_key") super(SoftwareOnboarding, self).__init__(*args, **kwargs) - def _create_request(self, params: BaseOnboardingParameter, url: str) -> SoftwareOnboardingRequest: + def _create_request(self, params: SoftwareOnboardingParameter, url: str) -> SoftwareOnboardingRequest: body_params = params.get_body_params() request_body = SoftwareOnboardingBody(**body_params) @@ -28,7 +27,7 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software return SoftwareOnboardingRequest(header=request_header, body=request_body, url=url) - def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: + def _perform_request(self, params: SoftwareOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) request.sign(self._private_key, self._public_key) if request.is_signed: @@ -50,37 +49,3 @@ def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResp http_response = self._perform_request(params=params, url=url) return SoftwareOnboardingResponse(http_response) - - -class CUOnboarding(EnvironmentalService): - - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(CUOnboarding, self).__init__(*args, **kwargs) - - def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardingRequest: - body_params = params.get_body_params() - request_body = CUOnboardingBody(**body_params) - - header_params = params.get_header_params() - request_header = CUOnboardingHeader(**header_params) - - return CUOnboardingRequest(header=request_header, body=request_body, url=url) - - def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: - request = self._create_request(params, url) - request.sign(self._private_key, self._public_key) - if request.is_signed: - return requests.post( - url=request.get_url(), - data=request.get_body_content(), - headers=request.get_header() - ) - raise RequestNotSigned - - def onboard(self, params: CUOnboardingParameter) -> CUOnboardingResponse: - url = self._environment.get_onboard_url() - http_response = self._perform_request(params=params, url=url) - - return CUOnboardingResponse(http_response) diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index 1ca75312..bb62ae2e 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,25 +1,11 @@ -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes +from agrirouter.utils.utc_time_util import now_as_utc_str -class BaseOnboardingParameter(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_header_params(self, *args, **kwargs): - ... - - @abstractmethod - def get_body_params(self, *args, **kwargs): - ... - - -class SoftwareOnboardingParameter(BaseOnboardingParameter): +class SoftwareOnboardingParameter: def __init__(self, *, id_, @@ -39,8 +25,7 @@ def __init__(self, self.certification_version_id = certification_version_id self.gateway_id = str(gateway_id) self.certificate_type = certificate_type - self.utc_timestamp = str(utc_timestamp) if utc_timestamp \ - else datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.utc_timestamp = str(utc_timestamp) if utc_timestamp else now_as_utc_str() self.time_zone = str(time_zone) self.reg_code = reg_code @@ -61,38 +46,3 @@ def get_body_params(self): "utc_timestamp": self.utc_timestamp, "time_zone": self.time_zone, } - - -class CUOnboardingParameter(BaseOnboardingParameter): - def __init__(self, - id_, - application_id, - certification_version_id, - gateway_id, - reg_code, - content_type=ContentTypes.APPLICATION_JSON.value, - certificate_type=CertificateTypes.P12.value, - ): - - self.id_ = id_ - self.application_id = application_id - self.content_type = content_type - self.certification_version_id = certification_version_id - self.gateway_id = gateway_id - self.certificate_type = certificate_type - self.reg_code = reg_code - - def get_header_params(self): - return { - "content_type": self.content_type, - "reg_code": self.reg_code, - } - - def get_body_params(self): - return { - "id_": self.id_, - "application_id": self.application_id, - "certification_version_id": self.certification_version_id, - "gateway_id": self.gateway_id, - "certificate_type": self.certificate_type, - } diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index c39d9d7a..0728f149 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -1,10 +1,10 @@ -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.request_body import SoftwareOnboardingBody from agrirouter.onboarding.signature import create_signature, verify_signature -class BaseOnboardingRequest: - def __init__(self, header: BaseOnboardingHeader, body: BaseOnboardingBody, url: str): +class SoftwareOnboardingRequest: + def __init__(self, header: SoftwareOnboardingHeader, body: SoftwareOnboardingBody, url: str): self.header = header self.body = body self.url = url @@ -33,17 +33,3 @@ def is_signed(self): if header_has_signature: return True return False - - -class SoftwareOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard Farming Software or Telemetry Platform - """ - pass - - -class CUOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard CUs - """ - pass diff --git a/agrirouter/onboarding/request_body.py b/agrirouter/onboarding/request_body.py index 045c46a8..a730cf6e 100644 --- a/agrirouter/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -1,30 +1,11 @@ import json -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.onboarding.enums import CertificateTypes, GateWays from agrirouter.onboarding.exceptions import WrongCertificationType, WrongGateWay -class BaseOnboardingBody(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_parameters(self, *args, **kwargs) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def json(self, *args, **kwargs): - ... - - -class SoftwareOnboardingBody(BaseOnboardingBody): +class SoftwareOnboardingBody: def __init__(self, *, id_, @@ -86,21 +67,3 @@ def _validate_certificate_type(certificate_type: str) -> None: def _validate_gateway_id(gateway_id: str) -> None: if gateway_id not in GateWays.values_list(): raise WrongGateWay - - -class CUOnboardingBody(BaseOnboardingBody): - - def __init__(self, *args, **kwargs): - ... - - def get_parameters(self, *args, **kwargs) -> dict: - ... - - def _set_params(self, *args, **kwargs): - ... - - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 50826c4c..1b9c6591 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,5 +1,9 @@ +import json +from typing import Union + from requests import Response +from agrirouter.messaging.exceptions import WrongFieldError from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication @@ -24,9 +28,14 @@ class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): Response from verify request used for Farming Software or Telemetry Platform before onboarding """ - def __init__(self, http_response: Response): - super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.account_id = response_body.get("accountId", None) @@ -40,20 +49,37 @@ def __init__(self, http_response: Response): def get_account_id(self) -> str: return self.account_id + def set_account_id(self, account_id: str): + self.account_id = account_id + class SoftwareOnboardingResponse(BaseOnboardingResonse): """ Response from onboarding request used for Farming Software or Telemetry Platform """ - def __init__(self, http_response: Response): - super(SoftwareOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + + DEVICE_ALTERNATE_ID = "deviceAlternateId" + CAPABILITY_ALTERNATE_ID = "capabilityAlternateId" + SENSOR_ALTERNATE_ID = "sensorAlternateId" + CONNECTION_CRITERIA = "connectionCriteria" + AUTHENTICATION = "authentication" + + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.connection_criteria = ConnectionCriteria( gateway_id=response_body.get("connectionCriteria").get("gatewayId"), measures=response_body.get("connectionCriteria").get("measures"), commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), + port=response_body.get("connectionCriteria").get("port"), + client_id=response_body.get("connectionCriteria").get("client_id") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( @@ -76,22 +102,58 @@ def __init__(self, http_response: Response): def get_connection_criteria(self) -> ConnectionCriteria: return self.connection_criteria + def set_connection_criteria(self, connection_criteria: ConnectionCriteria): + self.connection_criteria = connection_criteria + def get_authentication(self) -> Authentication: return self.authentication + def set_authentication(self, authentication: Authentication): + self.authentication = authentication + def get_sensor_alternate_id(self) -> str: return self.sensor_alternate_id + def set_sensor_alternate_id(self, sensor_alternate_id: str): + self.sensor_alternate_id = sensor_alternate_id + def get_device_alternate_id(self) -> str: return self.device_alternate_id + def set_device_alternate_id(self, device_alternate_id: str): + self.device_alternate_id = device_alternate_id + def get_capability_alternate_id(self) -> str: return self.capability_alternate_id - -class CUOnboardingResponse(BaseOnboardingResonse): - """ - Response from onboarding request used for CUs - """ - pass - + def set_capability_alternate_id(self, capability_alternate_id: str): + self.capability_alternate_id = capability_alternate_id + + def json_serialize(self): + return { + self.DEVICE_ALTERNATE_ID: self.device_alternate_id, + self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, + self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, + self.CONNECTION_CRITERIA: self.connection_criteria, + self.AUTHENTICATION: self.authentication + } + + def json_deserialize(self, data: Union[dict, str]): + data_dict = data if type(data) == dict else json.loads(data) + for (key, value) in data_dict.items(): + if key == self.DEVICE_ALTERNATE_ID: + self.device_alternate_id = value + if key == self.CAPABILITY_ALTERNATE_ID: + self.capability_alternate_id = value + if key == self.SENSOR_ALTERNATE_ID: + self.sensor_alternate_id = value + if key == self.CONNECTION_CRITERIA: + connection_criteria = ConnectionCriteria() + connection_criteria.json_deserialize(value) + self.connection_criteria = connection_criteria + if key == self.AUTHENTICATION: + authentication = Authentication() + authentication.json_deserialize(value) + self.authentication = authentication + else: + raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") diff --git a/agrirouter/revoking/parameters.py b/agrirouter/revoking/parameters.py index 95a73332..cf706b02 100644 --- a/agrirouter/revoking/parameters.py +++ b/agrirouter/revoking/parameters.py @@ -8,7 +8,7 @@ def __init__(self, account_id, endpoint_ids, utc_timestamp, - timestamp, + time_zone, content_type=ContentTypes.APPLICATION_JSON.value ): @@ -17,7 +17,7 @@ def __init__(self, self.account_id = account_id self.endpoint_ids = endpoint_ids self.utc_timestamp = utc_timestamp - self.timestamp = timestamp + self.time_zone = time_zone def get_header_params(self): return { @@ -30,5 +30,5 @@ def get_body_params(self): "account_id": self.account_id, "endpoint_ids": self.endpoint_ids, "utc_timestamp": self.utc_timestamp, - "timestamp": self.timestamp, + "time_zone": self.time_zone, } diff --git a/agrirouter/revoking/request.py b/agrirouter/revoking/request.py index cb8605fa..1fea09e1 100644 --- a/agrirouter/revoking/request.py +++ b/agrirouter/revoking/request.py @@ -18,8 +18,12 @@ def get_data(self): def get_header(self): return self.header.get_header() + def get_body_content(self): + return self.body.json().replace("\n", "") + def sign(self, private_key): - signature = create_signature(self.body.json(new_lines=False), private_key) + body = self.get_body_content() + signature = create_signature(body, private_key) self.header.sign(signature) @property diff --git a/agrirouter/revoking/request_body.py b/agrirouter/revoking/request_body.py index 6ec70b2d..7bdea6e5 100644 --- a/agrirouter/revoking/request_body.py +++ b/agrirouter/revoking/request_body.py @@ -26,14 +26,11 @@ def _set_params(self, ) -> None: self.params = { - "account_id": account_id, - "endpoint_ids": endpoint_ids, + "accountId": account_id, + "endpointIds": endpoint_ids, "UTCTimestamp": utc_timestamp, "timeZone": time_zone, } - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result + def json(self) -> str: + return json.dumps(self.get_parameters(), separators=(',', ':')) diff --git a/agrirouter/revoking/revoking.py b/agrirouter/revoking/revoking.py index aaa8f871..b835bc8f 100644 --- a/agrirouter/revoking/revoking.py +++ b/agrirouter/revoking/revoking.py @@ -29,9 +29,9 @@ def _perform_request(self, params: RevokingParameter, url: str) -> requests.Resp request = self._create_request(params, url) request.sign(self._private_key) if request.is_signed: - return requests.post( + return requests.delete( url=request.get_url(), - data=request.get_data(), + json=request.get_data(), headers=request.get_header() ) raise RequestNotSigned @@ -40,4 +40,4 @@ def revoke(self, params: RevokingParameter) -> RevokingResponse: url = self._environment.get_revoke_url() http_response = self._perform_request(params=params, url=url) - return RevokingResponse(http_response) \ No newline at end of file + return RevokingResponse(http_response) diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py index 71f4c6df..2457b9da 100644 --- a/agrirouter/utils/type_url.py +++ b/agrirouter/utils/type_url.py @@ -17,29 +17,29 @@ class TypeUrl: @classmethod def get_type_url(cls, class_): - if class_.__name__ == Messages.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == HeaderQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageDelete.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageConfirm.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingRequest.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == CapabilitySpecification.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == Subscription.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQuery.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsQuery.__name__: - return cls.prefix + class_.__name__ + if class_ == Messages: + return cls.prefix + Messages.DESCRIPTOR.full_name + elif class_ == ListEndpointsResponse: + return cls.prefix + ListEndpointsResponse.DESCRIPTOR.full_name + elif class_ == HeaderQueryResponse: + return cls.prefix + HeaderQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageQueryResponse: + return cls.prefix + MessageQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageDelete: + return cls.prefix + MessageDelete.DESCRIPTOR.full_name + elif class_ == MessageConfirm: + return cls.prefix + MessageConfirm.DESCRIPTOR.full_name + elif class_ == OnboardingResponse: + return cls.prefix + OnboardingResponse.DESCRIPTOR.full_name + elif class_ == OnboardingRequest: + return cls.prefix + OnboardingRequest.DESCRIPTOR.full_name + elif class_ == CapabilitySpecification: + return cls.prefix + CapabilitySpecification.DESCRIPTOR.full_name + elif class_ == Subscription: + return cls.prefix + Subscription.DESCRIPTOR.full_name + elif class_ == MessageQuery: + return cls.prefix + MessageQuery.DESCRIPTOR.full_name + elif class_ == ListEndpointsQuery: + return cls.prefix + ListEndpointsQuery.DESCRIPTOR.full_name else: raise TypeUrlNotFoundError(f"The {class_} type url not found") diff --git a/agrirouter/utils/utc_time_util.py b/agrirouter/utils/utc_time_util.py index b86f3afb..b87ca808 100644 --- a/agrirouter/utils/utc_time_util.py +++ b/agrirouter/utils/utc_time_util.py @@ -2,5 +2,9 @@ def now_as_utc_timestamp(): + return datetime.utcnow() + + +def now_as_utc_str(): timestamp = datetime.utcnow() return timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") diff --git a/agrirouter/utils/uuid_util.py b/agrirouter/utils/uuid_util.py index 0c9bd872..8e500ead 100644 --- a/agrirouter/utils/uuid_util.py +++ b/agrirouter/utils/uuid_util.py @@ -2,4 +2,4 @@ def new_uuid(): - return uuid.uuid4() + return str(uuid.uuid4()) diff --git a/example_script.py b/example_script.py new file mode 100644 index 00000000..0995c898 --- /dev/null +++ b/example_script.py @@ -0,0 +1,130 @@ +public_key = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN +6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk +F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR +IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE +SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg +uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj +qwIDAQAB +-----END PUBLIC KEY-----""" + +private_key = """-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7 +Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz +9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21 +8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe +rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA +NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5 +jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK +9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD +IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb +XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL +uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn +Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb +sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7 +5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0 +OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9 +Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi +vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz +XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9 +pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/ +NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5 +mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV +Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F +CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR +qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65 +KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr +HjmgzBRxXFy5uph6Ue6dxyszaA== +-----END PRIVATE KEY-----""" + + +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI + + +######################################################## +# Authorization +print("Authorization...\n") + +import agrirouter as ar + +auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") +auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) +auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization +print(f"auth_url={auth_url}") + +auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. +auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process +auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + +print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + +print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + + +# Get dict containing data from auth process you will use for futher communication. +# If auth was rejected, contains {"error"} key. +# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys +# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. +auth_data = auth_response.get_auth_result() +print(f"auth_data: {auth_data}") + +######################################################## + +# Onboarding +print("Onboarding...\n") + + +from agrirouter.onboarding.enums import GateWays + +id_ = "urn:myapp:snr00003234" # just unique +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI +time_zone = "+03:00" + +onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) +onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) +print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") +print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") +onboarding_response = onboarding_client.onboard(onboarding_parameters) +print(f"onboarding_response.status_code: {onboarding_response.status_code}") +print(f"onboarding_response.text: {onboarding_response.text}") + + +########################## +# Messaging + + +from agrirouter.messaging.enums import CapabilityTypeDefinitions +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +from agrirouter.utils.uuid_util import new_uuid + +# List Endpoints + +messaging_service = HttpMessagingService() +list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +list_endpoint_service = ListEndpointsService(messaging_service) +list_endpoint_service.send(list_endpoint_parameters) + +# Subscription + +messaging_service = HttpMessagingService() +subscription_service = SubscriptionService(messaging_service) + +tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +subscription_service.send(subscription_parameters) diff --git a/examples.txt b/examples.txt index e59fe910..fcb13cd4 100644 --- a/examples.txt +++ b/examples.txt @@ -112,3 +112,43 @@ True "deviceAlternateId": "c067272a-d3a7-4dcf-ab58-5c45ba66ad60", "sensorAlternateId": "5564ce96-385f-448a-9502-9ea3c940a259" } + + +>>> ########################## +>>> # Messaging + + +>>> from agrirouter.messaging.enums import CapabilityTypeDefinitions +>>> from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +>>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +>>> from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +>>> from agrirouter.utils.uuid_util import new_uuid + +>>> # List Endpoints + +>>> messaging_service = HttpMessagingService() +>>> list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +>>> list_endpoint_service = ListEndpointsService(messaging_service) +>>> list_endpoint_service.send(list_endpoint_parameters) + +>>> # Subscription + +>>> messaging_service = HttpMessagingService() +>>> subscription_service = SubscriptionService(messaging_service) + +>>> tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +>>> subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +>>> subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +>>> subscription_service.send(subscription_parameters) diff --git a/tests/messaging_test/test_messaging_services.py b/tests/messaging_test/test_messaging_services.py new file mode 100644 index 00000000..4d5a5e15 --- /dev/null +++ b/tests/messaging_test/test_messaging_services.py @@ -0,0 +1,240 @@ +import pytest +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter import CapabilityParameters, CapabilityService, QueryHeaderService, QueryHeaderParameters +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod + +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.messaging.builders import CapabilityBuilder +from agrirouter.messaging.decode import decode_response, decode_details +from agrirouter.messaging.services.commons import HttpMessagingService +from agrirouter.messaging.services.http.outbox import OutboxService +from agrirouter.onboarding.response import SoftwareOnboardingResponse +from agrirouter.utils.uuid_util import new_uuid +from tests.sleeper import let_agrirouter_process_the_message + +onboarding_response = SoftwareOnboardingResponse() +onboarding_response.json_deserialize('{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}') +account_id = "fb2921de-592a-49ba-be5e-94044430bc96" +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" + + +def test_given_task_data_capabilities_capability_service_http(): + messaging_service = HttpMessagingService() + capability_parameters = CapabilityParameters( + application_id=application_id, + certification_version_id=certification_version_id, + enable_push_notification=1, + capability_parameters=CapabilityBuilder().with_task_data( + CapabilitySpecification.Direction.Value("SEND_RECEIVE") + ).build(), + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + capability_service = CapabilityService(messaging_service) + messaging_result = capability_service.send(capability_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + +# def test_list_endpoint_service_http(): +# messaging_service = HttpMessagingService() +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_list_endpoint_service_mqtt(): +# client = MqttClient(on_message_callback=foo, client_id="fb2921de-592a-49ba-be5e-94044430bc96") +# messaging_service = MqttMessagingService(onboarding_response, client) +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_valid_subscription_service_http(): +# messaging_service = HttpMessagingService() +# subscription_service = SubscriptionService(messaging_service) +# items = [] +# for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: +# subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +# items.append(subscription_item) +# subscription_parameters = SubscriptionParameters( +# subscription_items=items, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# messaging_result = subscription_service.send(subscription_parameters) +# return messaging_result + + +def test_query_header_message_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + return messaging_result + + +def test_given_validity_and_missing_messages_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_sender_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_message_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_missing_filter_criteria_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 400 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + assert 1 == len(decoded_details.messages) + + for message in decoded_details.messages: + assert "VAL_000017" == message.message_code + assert message.message == "Query does not contain any filtering criteria: messageIds, senders or validityPeriod. Information required to process message is missing or malformed." diff --git a/tests/sleeper.py b/tests/sleeper.py new file mode 100644 index 00000000..73eb1d28 --- /dev/null +++ b/tests/sleeper.py @@ -0,0 +1,5 @@ +import time + + +def let_agrirouter_process_the_message(seconds: int = 3): + time.sleep(3) diff --git a/tests/test_revoking/test_parameters.py b/tests/test_revoking/test_parameters.py index 6cb97298..2c1c737e 100644 --- a/tests/test_revoking/test_parameters.py +++ b/tests/test_revoking/test_parameters.py @@ -8,15 +8,15 @@ class TestRevokingParameter: content_type = "json" account_id = "111" endpoint_ids = "endpoint_1" - utc_timestamp = "+03:00" - timestamp = "01-01-2021" + time_zone = "+03:00" + utc_timestamp = "01-01-2021" test_object = RevokingParameter( application_id=application_id, content_type=content_type, account_id=account_id, endpoint_ids=endpoint_ids, utc_timestamp=utc_timestamp, - timestamp=timestamp, + time_zone=time_zone ) def test_get_header_params(self): @@ -27,4 +27,4 @@ def test_get_body_params(self): assert self.test_object.get_body_params()["account_id"] == self.account_id assert self.test_object.get_body_params()["endpoint_ids"] == self.endpoint_ids assert self.test_object.get_body_params()["utc_timestamp"] == self.utc_timestamp - assert self.test_object.get_body_params()["timestamp"] == self.timestamp + assert self.test_object.get_body_params()["time_zone"] == self.time_zone From 5707874982885f3954124d5f34ec4fe9030284f0 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 9 Nov 2021 18:33:42 +0300 Subject: [PATCH 59/78] Refactor Dtos --- agrirouter/auth/dto.py | 84 ++++++++++++++++++++----------------- agrirouter/auth/response.py | 20 ++++----- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py index fec51c0a..8e4be05e 100644 --- a/agrirouter/auth/dto.py +++ b/agrirouter/auth/dto.py @@ -4,44 +4,6 @@ from agrirouter.messaging.exceptions import WrongFieldError -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - class AuthorizationToken: ACCOUNT = 'account' REGISTRATION_CODE = 'regcode' @@ -88,6 +50,52 @@ def set_expires(self, expires: str) -> None: self.expires = expires +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + decoded_token: AuthorizationToken = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.decoded_token = decoded_token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + def get_decoded_token(self) -> AuthorizationToken: + return self.decoded_token + + def set_decoded_token(self, decoded_token: AuthorizationToken) -> None: + self.decoded_token = decoded_token + + class AuthorizationResult: def __init__(self, *, diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index b5a73d48..a4b87f7f 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,7 +5,7 @@ from cryptography.exceptions import InvalidSignature -from agrirouter.auth.dto import AuthorizationToken +from agrirouter.auth.dto import AuthorizationToken, AuthorizationResultUrl from agrirouter.onboarding.signature import verify_signature @@ -69,16 +69,16 @@ def decode_token(token: Union[str, bytes]) -> AuthorizationToken: auth_token.json_deserialize(json.loads(decoded_token)) return auth_token - def get_auth_result(self) -> dict: - if not self.is_successful: - return {self.ERROR_KEY: self.error} + def get_auth_result(self) -> AuthorizationResultUrl: decoded_token = self.decode_token(self.token) - return { - self.SIGNATURE_KEY: self.signature, - self.STATE_KEY: self.state, - self.TOKEN_KEY: self.token, - self.CRED_KEY: decoded_token - } + + return AuthorizationResultUrl( + signature=self.signature, + state=self.state, + token=self.token, + decoded_token=decoded_token, + error=self.error + ) def get_signature(self): return self.signature From 9a4edb48cb9126891debf9778567f32693f46aa2 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 9 Nov 2021 18:34:31 +0300 Subject: [PATCH 60/78] [WIP] Fix MqttClient --- agrirouter/messaging/clients/mqtt.py | 64 +++++----- agrirouter/messaging/services/commons.py | 11 +- example_script.py | 142 ++++++++++++++--------- examples.txt | 2 +- 4 files changed, 129 insertions(+), 90 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 72ff399d..cfc4b90c 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -1,13 +1,18 @@ +import ssl from typing import Any, List, Tuple -from paho.mqtt import client as mqtt_client +import paho.mqtt.client as mqtt_client from paho.mqtt.client import MQTTv31, MQTTMessageInfo +from agrirouter.messaging.certification import create_certificate_file_from_pen + class MqttClient: def __init__(self, - client_id, + onboard_response, + + client_id: str, on_message_callback: callable = None, userdata: Any = None, clean_session: bool = True @@ -22,18 +27,29 @@ def __init__(self, protocol=MQTTv31, transport="tcp" ) + self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() + certificate_file_path = create_certificate_file_from_pen(onboard_response) + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.load_cert_chain( + certfile=certificate_file_path, + keyfile=certificate_file_path, + password=onboard_response.get_authentication().get_secret(), + ) + self.mqtt_client.tls_set_context() + def connect(self, host: str, port: str) -> None: - self.mqtt_client.connect_async( + self.mqtt_client.connect( host=host, - port=port + port=int(port) ) - self.mqtt_client.loop_start() + self.mqtt_client.loop_forever() def disconnect(self): self.mqtt_client.loop_stop() @@ -41,7 +57,6 @@ def disconnect(self): def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: """ - :param topic: str representing unique name of the topic that the message should be published on :param payload: The actual message to send :param qos: int representing the quality of service level to use. May be [0, 1, 2] @@ -88,19 +103,9 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback() -> callable: def on_connect(client, userdata, flags, rc, properties=None): - print("Connection started") - with open("connection.txt", "w") as file: - file.write("Connection started") - if rc == 0: - file.write("Connected!!") - else: - file.write("Do not Connected!!") - if rc == 0: - print("Connected to MQTT Broker!") - else: - print(f"Failed to connect, return code: {rc}") - - return client, userdata, flags, rc, properties + print("On_connect func start...") + print(f"Connection with response code: {rc}, flags: {flags}") + print("-"*50) return on_connect @@ -108,7 +113,7 @@ def on_connect(client, userdata, flags, rc, properties=None): def _get_on_message_callback() -> callable: def on_message(client, userdata, msg): - # print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") + print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") return client, userdata, msg @@ -117,29 +122,30 @@ def on_message(client, userdata, msg): @staticmethod def _get_on_subscribe_callback() -> callable: - def on_subscribe(client, userdata, mid, granted_qos, properties=None): - # print(f"Subscribed {userdata} to `{properties}`") + # def on_subscribe(client, userdata, mid, granted_qos, properties=None): + def on_subscribe(*args, **kwargs): + print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, mid, granted_qos, properties + return args, kwargs return on_subscribe @staticmethod def _get_on_disconnect_callback() -> callable: - def on_disconnect(client, userdata, rc): - # print(f"Disconnected from from `{properties}`") + def on_disconnect(*args, **kwargs): + print(f"Disconnected. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, rc + return args, kwargs return on_disconnect @staticmethod def _get_on_unsubscribe_callback() -> callable: - def on_unsubscribe(client, userdata, mid): - # print(f"Unsubscribed `{userdata}` from `{properties}`") + def on_unsubscribe(*args, **kwargs): + print(f"Unsubscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, mid + return args, kwargs return on_unsubscribe diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 425aa976..dd41491d 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -11,6 +11,7 @@ from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult from agrirouter.onboarding.exceptions import BadMessagingResult +from agrirouter.onboarding.response import SoftwareOnboardingResponse class AbstractMessagingClient(ABC): @@ -50,14 +51,15 @@ def send(self, parameters) -> MessagingResult: class MqttMessagingService(AbstractMessagingClient): def __init__(self, - client_id, - onboarding_response, + onboarding_response: SoftwareOnboardingResponse, on_message_callback: callable = None, ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=client_id, + onboard_response=onboarding_response, + + client_id=onboarding_response.get_connection_criteria().get_client_id(), on_message_callback=on_message_callback, ) self.client.connect( @@ -69,7 +71,8 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: message_request = self.create_message_request(parameters) mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), + topic=self.onboarding_response.get_connection_criteria().get_measures(), + payload=json.dumps(mqtt_payload), qos=qos ) result = MessagingResult([parameters.get_application_message_id()]) diff --git a/example_script.py b/example_script.py index 0995c898..b6c05d67 100644 --- a/example_script.py +++ b/example_script.py @@ -1,3 +1,5 @@ +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery + public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN 6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk @@ -38,93 +40,121 @@ -----END PRIVATE KEY-----""" -application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI +import agrirouter as ar +from agrirouter.onboarding.enums import GateWays +from agrirouter.messaging.enums import CapabilityType +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +from agrirouter.utils.uuid_util import new_uuid -######################################################## -# Authorization -print("Authorization...\n") +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI -import agrirouter as ar -auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") -auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) -auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization -print(f"auth_url={auth_url}") +def test_auth(): + print("Authorization...\n") -auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. -auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process -auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") + auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) + auth_url = auth_client.get_auth_request_url( + auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization + print(f"auth_url={auth_url}") -print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + auth_result_url = input( + "Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. + auth_response = auth_client.extract_auth_response( + auth_result_url) # auth_response contains the results of the auth process + auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR -print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + print( + f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + print( + f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying -# Get dict containing data from auth process you will use for futher communication. -# If auth was rejected, contains {"error"} key. -# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys -# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. -auth_data = auth_response.get_auth_result() -print(f"auth_data: {auth_data}") + # Get dict containing data from auth process you will use for futher communication. + # If auth was rejected, contains {"error"} key. + # If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys + # Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. + auth_data = auth_response.get_auth_result() + print(f"auth_data: {auth_data}") -######################################################## + return auth_data -# Onboarding -print("Onboarding...\n") +def test_onboarding(gateway_id): -from agrirouter.onboarding.enums import GateWays + auth_data = test_auth() -id_ = "urn:myapp:snr00003234" # just unique -certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI -time_zone = "+03:00" + print("Onboarding...\n") -onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) -onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) -onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) -print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") -print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") -onboarding_response = onboarding_client.onboard(onboarding_parameters) -print(f"onboarding_response.status_code: {onboarding_response.status_code}") -print(f"onboarding_response.text: {onboarding_response.text}") + id_ = "urn:myapp:snr00003234" # just unique + certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI + time_zone = "+03:00" + onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) + onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, + certification_version_id=certification_version_id, + gateway_id=gateway_id, time_zone=time_zone, + reg_code=auth_data.get_decoded_token().regcode) + onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) + print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") + print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") + onboarding_response = onboarding_client.onboard(onboarding_parameters) + print(f"onboarding_response.status_code: {onboarding_response.status_code}") + print(f"onboarding_response.text: {onboarding_response.text}") -########################## -# Messaging + return onboarding_response -from agrirouter.messaging.enums import CapabilityTypeDefinitions -from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription -from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters -from agrirouter.utils.uuid_util import new_uuid +def test_list_endpoints_mqtt(onboarding_response): + messaging_service = MqttMessagingService( + onboarding_response=onboarding_response + ) + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result -# List Endpoints -messaging_service = HttpMessagingService() -list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, +def test_list_endpoint_http(onboarding_response): + messaging_service = HttpMessagingService() + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, direction=2, filtered=False, onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, ) -list_endpoint_service = ListEndpointsService(messaging_service) -list_endpoint_service.send(list_endpoint_parameters) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result -# Subscription -messaging_service = HttpMessagingService() -subscription_service = SubscriptionService(messaging_service) +def test_subscription_http(onboarding_response): + messaging_service = HttpMessagingService() + subscription_service = SubscriptionService(messaging_service) -tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value -subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) -subscription_parameters = SubscriptionParameters( + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( subscription_items=[subscription_item], onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, -) -subscription_service.send(subscription_parameters) + ) + messaging_result = subscription_service.send(subscription_parameters) + return messaging_result + + +if __name__ == "__main__": + test_list_endpoints_mqtt(test_onboarding(GateWays.MQTT.value)) diff --git a/examples.txt b/examples.txt index fcb13cd4..3ca0f065 100644 --- a/examples.txt +++ b/examples.txt @@ -89,7 +89,7 @@ True >>> time_zone = "+03:00" >>> onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) ->>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +>>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data.get_decoded_token().regcode) >>> onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) >>> onboarding_verifying_response.status_code >>> onboarding_verifying_response.text From 344f7358332bf5af89538488b12bf5d6b97480ef Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 13:25:09 +0300 Subject: [PATCH 61/78] Fix SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 1b9c6591..e42e14b2 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -79,7 +79,7 @@ def __init__(self, http_response: Response = None): commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), port=response_body.get("connectionCriteria").get("port"), - client_id=response_body.get("connectionCriteria").get("client_id") + client_id=response_body.get("connectionCriteria").get("clientId") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( From dbdeb6fe00d477390fb3e92227ed4b122230b621 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 14:21:08 +0300 Subject: [PATCH 62/78] Fix MqttClient.__init__ method --- agrirouter/messaging/clients/mqtt.py | 2 +- connection.txt | 0 my_test.py | 265 +++++++++++++++++++++++++++ tests/messaging_test/test_decode.py | 21 +++ 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 connection.txt create mode 100644 my_test.py create mode 100644 tests/messaging_test/test_decode.py diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index cfc4b90c..c2de3c4e 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -42,7 +42,7 @@ def __init__(self, keyfile=certificate_file_path, password=onboard_response.get_authentication().get_secret(), ) - self.mqtt_client.tls_set_context() + self.mqtt_client.tls_set_context(context) def connect(self, host: str, port: str) -> None: self.mqtt_client.connect( diff --git a/connection.txt b/connection.txt new file mode 100644 index 00000000..e69de29b diff --git a/my_test.py b/my_test.py new file mode 100644 index 00000000..27e9f4a2 --- /dev/null +++ b/my_test.py @@ -0,0 +1,265 @@ +import os +import uuid +from datetime import datetime + +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionParameters, SubscriptionService, \ + RevokingParameter, Revoking, CapabilityService, CapabilityParameters, QueryHeaderService, QueryHeaderParameters +from agrirouter.auth.auth import Authorization +from agrirouter.auth.parameters import AuthUrlParameter +from agrirouter.constants.media_types import ContentTypes +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.messaging.builders import CapabilityBuilder +from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.clients.mqtt import MqttClient +from agrirouter.messaging.decode import decode_response, decode_details +from agrirouter.messaging.enums import CapabilityType +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter.messaging.services.http.outbox import OutboxService +from agrirouter.onboarding.onboarding import SoftwareOnboarding +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter +from agrirouter.utils.utc_time_util import now_as_utc_str +from agrirouter.utils.uuid_util import new_uuid + +application_id = os.getenv("APPLICATION_ID", "8c947a45-c57d-42d2-affc-206e21d63a50") +ENV = os.getenv("ENVIRONMENT", "QA") +auth_result_url = "http://fuf.me/?state=99298901-2cbc-4f51-a0c2-3905e277e921&token=eyJhY2NvdW50IjoiZmIyOTIxZGUtNTkyYS00OWJhLWJlNWUtOTQwNDQ0MzBiYzk2IiwicmVnY29kZSI6IjBkMDE3NmU4NmYiLCJleHBpcmVzIjoiMjAyMS0xMC0xMlQxMzo0MjozNy40ODZaIn0%3D&signature=qeHHDV9sE6DxqcwoXm453Hbq7pJ92pWGUXwp3A13xtaNftjLX%2Ffcu8iiGV6%2Fu22g856Bw21Bxlrtyj2wCIm%2FJSmztYeitsfd99o5oSzPQ3zqm4tdDLH8qvnONRJqck7OWChVU4mArk14uVQv2ofxANGogspp1T1k51WLtPtoHBxFu6XAS3Cbm%2FpkakqalR%2FnWjAsCIlf9vGvX6oQnSX2lUDYmMCNNvzLwHYPCQgwF1vusbXE%2BAxwpBAFnZHFHPmjbTVUPXF6Hxb%2B3plwwm4tfXK9%2B7dwkZvUbKgIkcq2AYKFSTKwAns7hBjFSt6uhWK17%2Brzc%2FsarW30CZ%2BIGuuQeQ%3D%3D" +private_key = os.getenv("PRIVATE_KEY", + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7\n" + "Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz\n" + "9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21\n" + "8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe\n" + "rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA\n" + "NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5\n" + "jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK\n" + "9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD\n" + "IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb\n" + "XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL\n" + "uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn\n" + "Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb\n" + "sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7\n" + "5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0\n" + "OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9\n" + "Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi\n" + "vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz\n" + "XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9\n" + "pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/\n" + "NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5\n" + "mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV\n" + "Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F\n" + "CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR\n" + "qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65\n" + "KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr\n" + "HjmgzBRxXFy5uph6Ue6dxyszaA==\n" + "-----END PRIVATE KEY-----" + ) + +public_key = "-----BEGIN PUBLIC KEY-----\n" \ + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN\n" \ + "6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk\n" \ + "F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR\n" \ + "IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE\n" \ + "SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg\n" \ + "uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj\n" \ + "qwIDAQAB\n" \ + "-----END PUBLIC KEY-----" + +AR_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n3HvTfUVcismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4u2FVJphC1m2BfLxelGLjzx\nVAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n1/7MLD6IE2+HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\nY8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wNvxXWtkng3yh+7WiZ/Y\nqwIDAQAB\n-----END PUBLIC KEY-----" + +account_id = "fb2921de-592a-49ba-be5e-94044430bc96" +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" + + +verifying_response = '{"accountId":"fb2921de-592a-49ba-be5e-94044430bc96"}' +onboarding_response = '{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}' + + +message_result = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' + + + +def test_onboarding(): + from agrirouter.onboarding.enums import GateWays + import agrirouter as ar + auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") + auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) + auth_url = auth_client.get_auth_request_url(auth_params) + + print(auth_url) + + auth_result_url = input("Entrer url: ") # the url the user was redirected after his authorization. + + auth_response = auth_client.extract_auth_response(auth_result_url) + auth_client.verify_auth_response(auth_response) + auth_response.is_successful + auth_response.is_valid + auth_data = auth_response.get_auth_result() + + id_ = "urn:myapp:snr00003234sdf" + certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" + time_zone = "+03:00" + onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) + onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, + certification_version_id=certification_version_id, + gateway_id=GateWays.MQTT.value, + time_zone=time_zone, + reg_code=auth_data.decoded_token.regcode, + utc_timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") + onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) + onboarding_verifying_response.status_code + onboarding_verifying_response.text + print(onboarding_verifying_response.text) + onboarding_response = onboarding_client.onboard(onboarding_parameters) + print(onboarding_response.text) + + return onboarding_response + + +def test_capability_service_http(onboarding_response): + messaging_service = HttpMessagingService() + capability_parameters = CapabilityParameters( + application_id=application_id, + certification_version_id=certification_version_id, + enable_push_notification=1, + capability_parameters=CapabilityBuilder().with_task_data(2).build(), + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + capability_service = CapabilityService(messaging_service) + messaging_result = capability_service.send(capability_parameters) + return messaging_result + + +def test_list_endpoint_service_http(onboarding_response): + messaging_service = HttpMessagingService() + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result + + +def foo(client, userdata, message): + print(client, userdata, message) + return client, userdata, message + + +def test_list_endpoint_service_mqtt(onboarding_response): + messaging_service = MqttMessagingService( + on_message_callback=foo, + onboarding_response=onboarding_response + ) + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result + + +def test_valid_subscription_service_http(onboarding_response): + messaging_service = HttpMessagingService() + subscription_service = SubscriptionService(messaging_service) + items = [] + for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + items.append(subscription_item) + subscription_parameters = SubscriptionParameters( + subscription_items=items, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = subscription_service.send(subscription_parameters) + return messaging_result + + +def test_query_header_message_http(onboarding_response): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + return messaging_result + + +def get_outbox(onboarding_response): + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + # assert 200 == outbox_response.status_code + + messages = outbox_response.messages + # assert len(messages) == 1 + # assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + # assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + # assert decoded_details + + # query_metrics = decoded_details.query_metrics + # assert 0 == query_metrics.total_messages_in_query + + return messages, decoded_message, decoded_details + + +def test_outbox_service_http(onboarding_response): + outbox_service = OutboxService() + result = outbox_service.fetch(onboarding_response) + return result + + +def test_revoke(): + params = RevokingParameter( + application_id=application_id, + account_id=account_id, + endpoint_ids=[ + "849ff5b9-6b3a-418e-9394-931e19acb8ec", + "b2157913-013f-486c-bf25-a591ffca451b" + ], + utc_timestamp=now_as_utc_str(), + time_zone="+03:00", + content_type=ContentTypes.APPLICATION_JSON.value + ) + service = Revoking("QA", public_key=public_key, private_key=private_key) + return service.revoke(params) + + +def test(): + onboarding_response = test_onboarding() + # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) + # print(test_valid_subscription_service_http(onboarding_response).get_messages_ids()) + # print(test_capability_service_http(onboarding_response).get_messages_ids()) + # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) + messaging_result = test_list_endpoint_service_mqtt(onboarding_response) + return messaging_result + + +if __name__ == "__main__": + test() diff --git a/tests/messaging_test/test_decode.py b/tests/messaging_test/test_decode.py new file mode 100644 index 00000000..77d62143 --- /dev/null +++ b/tests/messaging_test/test_decode.py @@ -0,0 +1,21 @@ +import json + +import pytest + +from agrirouter.messaging.decode import decode_response +from agrirouter.messaging.decode import decode_details + + +MESSAGING_RESULT = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' + + +def test_decode_response(): + pass + + +def test_decode_details(): + json_response = json.loads(MESSAGING_RESULT) + message = decode_response(json_response[0]["command"]["message"].encode()) + decoded_details = decode_details(message.response_payload.details) + print(decoded_details) + assert False From 8558c45b96fa1a73b732b67b98cda4e65b6b3108 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 15:33:42 +0300 Subject: [PATCH 63/78] Remove redundant files --- agrirouter/messaging/clients/mqtt.py | 1 - connection.txt | 0 my_test.py | 265 --------------------------- 3 files changed, 266 deletions(-) delete mode 100644 connection.txt delete mode 100644 my_test.py diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 1b5183f8..8c0ccaf9 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -121,7 +121,6 @@ def on_message(client, userdata, msg): @staticmethod def _get_on_subscribe_callback() -> callable: - # def on_subscribe(client, userdata, mid, granted_qos, properties=None): def on_subscribe(*args, **kwargs): print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") diff --git a/connection.txt b/connection.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/my_test.py b/my_test.py deleted file mode 100644 index 27e9f4a2..00000000 --- a/my_test.py +++ /dev/null @@ -1,265 +0,0 @@ -import os -import uuid -from datetime import datetime - -from google.protobuf.timestamp_pb2 import Timestamp - -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionParameters, SubscriptionService, \ - RevokingParameter, Revoking, CapabilityService, CapabilityParameters, QueryHeaderService, QueryHeaderParameters -from agrirouter.auth.auth import Authorization -from agrirouter.auth.parameters import AuthUrlParameter -from agrirouter.constants.media_types import ContentTypes -from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery -from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription -from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod -from agrirouter.messaging.builders import CapabilityBuilder -from agrirouter.messaging.clients.http import HttpClient -from agrirouter.messaging.clients.mqtt import MqttClient -from agrirouter.messaging.decode import decode_response, decode_details -from agrirouter.messaging.enums import CapabilityType -from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter.messaging.services.http.outbox import OutboxService -from agrirouter.onboarding.onboarding import SoftwareOnboarding -from agrirouter.onboarding.parameters import SoftwareOnboardingParameter -from agrirouter.utils.utc_time_util import now_as_utc_str -from agrirouter.utils.uuid_util import new_uuid - -application_id = os.getenv("APPLICATION_ID", "8c947a45-c57d-42d2-affc-206e21d63a50") -ENV = os.getenv("ENVIRONMENT", "QA") -auth_result_url = "http://fuf.me/?state=99298901-2cbc-4f51-a0c2-3905e277e921&token=eyJhY2NvdW50IjoiZmIyOTIxZGUtNTkyYS00OWJhLWJlNWUtOTQwNDQ0MzBiYzk2IiwicmVnY29kZSI6IjBkMDE3NmU4NmYiLCJleHBpcmVzIjoiMjAyMS0xMC0xMlQxMzo0MjozNy40ODZaIn0%3D&signature=qeHHDV9sE6DxqcwoXm453Hbq7pJ92pWGUXwp3A13xtaNftjLX%2Ffcu8iiGV6%2Fu22g856Bw21Bxlrtyj2wCIm%2FJSmztYeitsfd99o5oSzPQ3zqm4tdDLH8qvnONRJqck7OWChVU4mArk14uVQv2ofxANGogspp1T1k51WLtPtoHBxFu6XAS3Cbm%2FpkakqalR%2FnWjAsCIlf9vGvX6oQnSX2lUDYmMCNNvzLwHYPCQgwF1vusbXE%2BAxwpBAFnZHFHPmjbTVUPXF6Hxb%2B3plwwm4tfXK9%2B7dwkZvUbKgIkcq2AYKFSTKwAns7hBjFSt6uhWK17%2Brzc%2FsarW30CZ%2BIGuuQeQ%3D%3D" -private_key = os.getenv("PRIVATE_KEY", - "-----BEGIN PRIVATE KEY-----\n" - "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7\n" - "Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz\n" - "9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21\n" - "8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe\n" - "rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA\n" - "NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5\n" - "jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK\n" - "9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD\n" - "IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb\n" - "XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL\n" - "uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn\n" - "Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb\n" - "sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7\n" - "5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0\n" - "OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9\n" - "Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi\n" - "vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz\n" - "XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9\n" - "pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/\n" - "NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5\n" - "mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV\n" - "Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F\n" - "CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR\n" - "qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65\n" - "KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr\n" - "HjmgzBRxXFy5uph6Ue6dxyszaA==\n" - "-----END PRIVATE KEY-----" - ) - -public_key = "-----BEGIN PUBLIC KEY-----\n" \ - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN\n" \ - "6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk\n" \ - "F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR\n" \ - "IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE\n" \ - "SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg\n" \ - "uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj\n" \ - "qwIDAQAB\n" \ - "-----END PUBLIC KEY-----" - -AR_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n3HvTfUVcismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4u2FVJphC1m2BfLxelGLjzx\nVAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n1/7MLD6IE2+HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\nY8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wNvxXWtkng3yh+7WiZ/Y\nqwIDAQAB\n-----END PUBLIC KEY-----" - -account_id = "fb2921de-592a-49ba-be5e-94044430bc96" -certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" - - -verifying_response = '{"accountId":"fb2921de-592a-49ba-be5e-94044430bc96"}' -onboarding_response = '{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}' - - -message_result = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' - - - -def test_onboarding(): - from agrirouter.onboarding.enums import GateWays - import agrirouter as ar - auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") - auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) - auth_url = auth_client.get_auth_request_url(auth_params) - - print(auth_url) - - auth_result_url = input("Entrer url: ") # the url the user was redirected after his authorization. - - auth_response = auth_client.extract_auth_response(auth_result_url) - auth_client.verify_auth_response(auth_response) - auth_response.is_successful - auth_response.is_valid - auth_data = auth_response.get_auth_result() - - id_ = "urn:myapp:snr00003234sdf" - certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" - time_zone = "+03:00" - onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) - onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, - certification_version_id=certification_version_id, - gateway_id=GateWays.MQTT.value, - time_zone=time_zone, - reg_code=auth_data.decoded_token.regcode, - utc_timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") - onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) - onboarding_verifying_response.status_code - onboarding_verifying_response.text - print(onboarding_verifying_response.text) - onboarding_response = onboarding_client.onboard(onboarding_parameters) - print(onboarding_response.text) - - return onboarding_response - - -def test_capability_service_http(onboarding_response): - messaging_service = HttpMessagingService() - capability_parameters = CapabilityParameters( - application_id=application_id, - certification_version_id=certification_version_id, - enable_push_notification=1, - capability_parameters=CapabilityBuilder().with_task_data(2).build(), - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - capability_service = CapabilityService(messaging_service) - messaging_result = capability_service.send(capability_parameters) - return messaging_result - - -def test_list_endpoint_service_http(onboarding_response): - messaging_service = HttpMessagingService() - list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, - direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), - filtered=False, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - list_endpoint_service = ListEndpointsService(messaging_service) - messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result - - -def foo(client, userdata, message): - print(client, userdata, message) - return client, userdata, message - - -def test_list_endpoint_service_mqtt(onboarding_response): - messaging_service = MqttMessagingService( - on_message_callback=foo, - onboarding_response=onboarding_response - ) - list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, - direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), - filtered=False, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - list_endpoint_service = ListEndpointsService(messaging_service) - messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result - - -def test_valid_subscription_service_http(onboarding_response): - messaging_service = HttpMessagingService() - subscription_service = SubscriptionService(messaging_service) - items = [] - for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: - subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) - items.append(subscription_item) - subscription_parameters = SubscriptionParameters( - subscription_items=items, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - messaging_result = subscription_service.send(subscription_parameters) - return messaging_result - - -def test_query_header_message_http(onboarding_response): - messaging_service = HttpMessagingService() - query_header_service = QueryHeaderService(messaging_service) - sent_from = Timestamp() - sent_to = Timestamp() - validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) - query_header_parameters = QueryHeaderParameters( - message_ids=[new_uuid(), new_uuid()], - senders=[new_uuid(), new_uuid()], - validity_period=validity_period, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - messaging_result = query_header_service.send(query_header_parameters) - return messaging_result - - -def get_outbox(onboarding_response): - outbox_service = OutboxService() - outbox_response = outbox_service.fetch(onboarding_response) - # assert 200 == outbox_response.status_code - - messages = outbox_response.messages - # assert len(messages) == 1 - # assert messages[0].command.message - - decoded_message = decode_response(outbox_response.messages[0].command.message) - # assert 204 == decoded_message.response_envelope.response_code - - decoded_details = decode_details(decoded_message.response_payload.details) - # assert decoded_details - - # query_metrics = decoded_details.query_metrics - # assert 0 == query_metrics.total_messages_in_query - - return messages, decoded_message, decoded_details - - -def test_outbox_service_http(onboarding_response): - outbox_service = OutboxService() - result = outbox_service.fetch(onboarding_response) - return result - - -def test_revoke(): - params = RevokingParameter( - application_id=application_id, - account_id=account_id, - endpoint_ids=[ - "849ff5b9-6b3a-418e-9394-931e19acb8ec", - "b2157913-013f-486c-bf25-a591ffca451b" - ], - utc_timestamp=now_as_utc_str(), - time_zone="+03:00", - content_type=ContentTypes.APPLICATION_JSON.value - ) - service = Revoking("QA", public_key=public_key, private_key=private_key) - return service.revoke(params) - - -def test(): - onboarding_response = test_onboarding() - # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) - # print(test_valid_subscription_service_http(onboarding_response).get_messages_ids()) - # print(test_capability_service_http(onboarding_response).get_messages_ids()) - # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) - messaging_result = test_list_endpoint_service_mqtt(onboarding_response) - return messaging_result - - -if __name__ == "__main__": - test() From 0c6da941308b81321d6bf978ced5860504cc6443 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 01:14:38 +0300 Subject: [PATCH 64/78] Fix SoftwareOnboardingResponse.json_deserialize method --- agrirouter/onboarding/response.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index e42e14b2..5d2382c2 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -143,15 +143,15 @@ def json_deserialize(self, data: Union[dict, str]): for (key, value) in data_dict.items(): if key == self.DEVICE_ALTERNATE_ID: self.device_alternate_id = value - if key == self.CAPABILITY_ALTERNATE_ID: + elif key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value - if key == self.SENSOR_ALTERNATE_ID: + elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value - if key == self.CONNECTION_CRITERIA: + elif key == self.CONNECTION_CRITERIA: connection_criteria = ConnectionCriteria() connection_criteria.json_deserialize(value) self.connection_criteria = connection_criteria - if key == self.AUTHENTICATION: + elif key == self.AUTHENTICATION: authentication = Authentication() authentication.json_deserialize(value) self.authentication = authentication From 869b77499551da7cacb8a0e60d99e5c7d29625a1 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 12:24:31 +0300 Subject: [PATCH 65/78] Fix let_agrirouter_process_the_message method --- tests/sleeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sleeper.py b/tests/sleeper.py index 73eb1d28..9d4179ac 100644 --- a/tests/sleeper.py +++ b/tests/sleeper.py @@ -2,4 +2,4 @@ def let_agrirouter_process_the_message(seconds: int = 3): - time.sleep(3) + time.sleep(seconds) From 9915d36615ce4df44cc834d698f4d9ea22830fa0 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:07:58 +0300 Subject: [PATCH 66/78] Refactor MqttMessagingService --- agrirouter/messaging/services/commons.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index ea3b2e3e..f610bca3 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -53,6 +53,7 @@ class MqttMessagingService(AbstractMessagingClient): def __init__(self, onboarding_response: SoftwareOnboardingResponse, on_message_callback: callable = None, + client_async: bool = True ): self.onboarding_response = onboarding_response @@ -61,10 +62,16 @@ def __init__(self, client_id=onboarding_response.get_connection_criteria().get_client_id(), on_message_callback=on_message_callback, ) - self.client.connect( - self.onboarding_response.get_connection_criteria().get_host(), - self.onboarding_response.get_connection_criteria().get_port() - ) + if client_async: + self.client.connect_async( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) + else: + self.client.connect( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) def send(self, parameters, qos: int = 0) -> MessagingResult: message_request = self.create_message_request(parameters) @@ -76,9 +83,3 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: ) result = MessagingResult([parameters.get_application_message_id()]) return result - - def subscribe(self): - pass - - def unsubscribe(self): - pass From 972ccdb7afecee136846e2cbddbca6bf3ed5fe76 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:08:49 +0300 Subject: [PATCH 67/78] Implement __str__ and __repr__ magic methods for dtos --- agrirouter/onboarding/dto.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index c4cbc314..89d84806 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -91,6 +91,12 @@ def get_client_id(self) -> str: def set_client_id(self, client_id: str) -> None: self.client_id = client_id + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class Authentication: TYPE = 'type' @@ -144,6 +150,12 @@ def get_certificate(self) -> str: def set_certificate(self, certificate: str) -> None: self.certificate = certificate + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class ErrorResponse: def __init__(self, From 9067872c3d43c66e259490a391a92b1fbd1765fa Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:12:48 +0300 Subject: [PATCH 68/78] Refactor MqttClient --- agrirouter/messaging/clients/constants.py | 2 + agrirouter/messaging/clients/mqtt.py | 46 ++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 agrirouter/messaging/clients/constants.py diff --git a/agrirouter/messaging/clients/constants.py b/agrirouter/messaging/clients/constants.py new file mode 100644 index 00000000..78de8628 --- /dev/null +++ b/agrirouter/messaging/clients/constants.py @@ -0,0 +1,2 @@ +ASYNC = "ASYNC" +SYNC = "SYNC" diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 8c0ccaf9..e5ce0da8 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -1,3 +1,4 @@ +import time import ssl from typing import Any, List, Tuple @@ -5,6 +6,7 @@ from paho.mqtt.client import MQTTv31, MQTTMessageInfo from agrirouter.messaging.certification import create_certificate_file_from_pen +from agrirouter.messaging.clients.constants import SYNC, ASYNC class MqttClient: @@ -14,7 +16,7 @@ def __init__(self, client_id: str, on_message_callback: callable = None, userdata: Any = None, - clean_session: bool = True + clean_session: bool = False ): # TODO: Implement on_message_callback parameter validation: # must take params as described at https://pypi.org/project/paho-mqtt/#callbacks @@ -28,9 +30,8 @@ def __init__(self, ) self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() - self.mqtt_client.on_connect = self._get_on_connect_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback(onboard_response) self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() - self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() @@ -43,18 +44,37 @@ def __init__(self, ) self.mqtt_client.tls_set_context(context) + self._mode = None + def connect(self, host: str, port: str) -> None: self.mqtt_client.connect( host=host, port=int(port) ) - self.mqtt_client.loop_forever() + self.mqtt_client.loop() + + self._mode = SYNC + + def connect_async(self, host: str, port: str): + self.mqtt_client.connect_async( + host=host, + port=int(port) + ) + self.mqtt_client.loop_start() + + self._mode = ASYNC + + while self.mqtt_client._state == 0: + time.sleep(1) def disconnect(self): self.mqtt_client.loop_stop() self.mqtt_client.disconnect() - def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: + def receive_outbox_messages(self): + self.mqtt_client.loop() + + def publish(self, topic, payload, qos=2) -> MQTTMessageInfo: """ :param topic: str representing unique name of the topic that the message should be published on :param payload: The actual message to send @@ -66,6 +86,10 @@ def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: payload=payload, qos=qos ) + if self._mode == SYNC: + self.mqtt_client.loop() + time.sleep(3) + self.mqtt_client.loop() return message_info def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: @@ -81,7 +105,7 @@ def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: :return: tuple """ - result, mid = self.mqtt_client.subscribe(topics) + result, mid = self.mqtt_client.subscribe(topics, qos=2) return result, mid def unsubscribe(self, topics: List[str]) -> tuple: @@ -99,12 +123,14 @@ def unsubscribe(self, topics: List[str]) -> tuple: return result, mid @staticmethod - def _get_on_connect_callback() -> callable: + def _get_on_connect_callback(onboard_response) -> callable: - def on_connect(client, userdata, flags, rc, properties=None): - print("On_connect func start...") + def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None): print(f"Connection with response code: {rc}, flags: {flags}") - print("-"*50) + if rc == 0: + print(f"Subscribing to {onboard_response.connection_criteria.commands}") + client.subscribe(topic=onboard_response.connection_criteria.commands) + time.sleep(3) return on_connect From 2d06436fcc3c6afd4a78277c27fb70be8b4f6f04 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:17:18 +0300 Subject: [PATCH 69/78] Remove redundant prints --- agrirouter/messaging/clients/mqtt.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index e5ce0da8..423897d0 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -126,9 +126,7 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback(onboard_response) -> callable: def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None): - print(f"Connection with response code: {rc}, flags: {flags}") if rc == 0: - print(f"Subscribing to {onboard_response.connection_criteria.commands}") client.subscribe(topic=onboard_response.connection_criteria.commands) time.sleep(3) @@ -138,8 +136,6 @@ def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None) def _get_on_message_callback() -> callable: def on_message(client, userdata, msg): - print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") - return client, userdata, msg return on_message @@ -148,8 +144,6 @@ def on_message(client, userdata, msg): def _get_on_subscribe_callback() -> callable: def on_subscribe(*args, **kwargs): - print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_subscribe @@ -158,8 +152,6 @@ def on_subscribe(*args, **kwargs): def _get_on_disconnect_callback() -> callable: def on_disconnect(*args, **kwargs): - print(f"Disconnected. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_disconnect @@ -168,8 +160,6 @@ def on_disconnect(*args, **kwargs): def _get_on_unsubscribe_callback() -> callable: def on_unsubscribe(*args, **kwargs): - print(f"Unsubscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_unsubscribe From 8de531a29540fb76a93f8bd48bed04175b13b6db Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:14:37 +0300 Subject: [PATCH 70/78] Refactor SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 5d2382c2..99723f96 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -134,8 +134,8 @@ def json_serialize(self): self.DEVICE_ALTERNATE_ID: self.device_alternate_id, self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, - self.CONNECTION_CRITERIA: self.connection_criteria, - self.AUTHENTICATION: self.authentication + self.CONNECTION_CRITERIA: self.connection_criteria.json_serialize(), + self.AUTHENTICATION: self.authentication.json_serialize() } def json_deserialize(self, data: Union[dict, str]): From 8e1d24e9ff2abe34cab1bf65e5e9b9bbbd016176 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:15:33 +0300 Subject: [PATCH 71/78] Update examp,es script --- example_script.py | 118 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) diff --git a/example_script.py b/example_script.py index b6c05d67..2c8b667c 100644 --- a/example_script.py +++ b/example_script.py @@ -1,4 +1,8 @@ +from pprint import pprint + from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.onboarding.response import SoftwareOnboardingResponse +import time public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN @@ -40,6 +44,28 @@ -----END PRIVATE KEY-----""" +onboarding_response_mqtt = { + "deviceAlternateId": "2145df0e-3451-46cb-bf23-23191af66fce", + "capabilityAlternateId": "523e4623-68d2-43d4-a0cc-e2ada2f68b5e", + "sensorAlternateId": "1489638c-7bed-4205-ad77-8d11efdc779f", + "connectionCriteria": { + "gatewayId": "2", + "host": "dke-qa.eu10.cp.iot.sap", + "port": 8883, + "clientId": "2145df0e-3451-46cb-bf23-23191af66fce", + "measures": "measures/2145df0e-3451-46cb-bf23-23191af66fce", + "commands": "commands/2145df0e-3451-46cb-bf23-23191af66fce" + }, + "authentication": { + "type": "PEM", + "secret": "JNKdNg8R0lwmFgvrUfOCc7inebr0h?!7Z9wL", + "certificate": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECMkL85F+LbPbAgMCAAAEggTI1CmRlnDUStBv\nTycvaRVFMCk1OuynhiOYRF6HBFFXBCxWKZa3WqTShLdf9iCel/NgtdZIiQsoD1LL\nMxVyh8pWAfLQ+pDJLvM6suQjHALt8dW5iTeCZ7R1gzFvPJ+xnDGFFytN7HmGSvHM\nQbcCOuEeIu8U6ENa6/+WmUwK9/ZMkLNqDHVKEGpI+lSJs8JWEE+S3Klmsxuq0dvz\nh6o3V7RKFwMfUZOQLHezGBDjLfEBdP+d2G87CY+LSzinL8pFhLwyrXFKfYWYoT0m\n5PkDdjfiVq3SJIUoQWnGrjaVVw4TV3WSxmhQnWbDwOQydr8DAiBxDMYoeK3rePpC\nwh6KATnBrovq1icqjonYDE0T+3Rs2SUbG+3+m9Zj4j46L2Sh9bUB6qxdw74Ck2/z\nAzJ1N+tB+RL7UvOpMOhmndMBl5qpx9dFFy8Z/N7w4YTQLZLN7chD8ApeFhCgvppt\nAGh8/VeWO54OC9ZOSHpxEl7sJz97jaHYNbw/lGbDk7cOZezwpA0NCWZ/Bb1vRDzy\n8EDX9s1hOA3jiy2T1RSyk2Rj/12pWdKtdSO8lMhMKC0B32Zr1F8rBJKDVzqFWuTt\nn+pXOKedyOA/ggyvYJdsltP8O4XB2oBN3WBdFK7Y1FG/tN30LsaqcnFTxab5v1Pp\ngq2dHu6Xy0TCMAw/DH3RmGXlGnDDWu86Zad7TjjrEZvpSIv4TTSCqqTvc4IN0xFX\nbKZCrY6JSkJWWnDMKrsRYOijUDvpAbYwZuTV9PAljYbt5YX778qxV9O0fNBQdaww\nNlfxU93jgr4g3E9nIzRxLu9S98hPbxKUnVYiQmYvP7vJUcUSo5F0LmUU/nvHY1pi\nr4tZDp8Xu1aZy7cOd3sTbf/68IjiZMZlF5/PVlOFOo40yGqW600j/qEqXoY/492h\nONXUCpHKaG/Pkjtg9THuYoaw1773gxYYsYLt+c6NkQCCsydOr2BMZQ4Qy4bZV67D\n2RNDeZzSBY6jEX6dnfY0FJqIsSiw28Ek5NXx0HTEGN8txPkx/1dfu3RfZnzUqT/0\nmS9xcWVYRmlip3vm48fMecqP/DNIHyjVLC39SsFdeXa+De76z/S3+or0t7HGlUim\nNVkIcWqm/sD2ia8hYberaRRTbUQ1iObNToIg8dA/xna6D61sYK8jkf1GVPpKsCTA\nOVW5u9XrE1f5YQEovE9kFgvtzs0u6jSeI9edqVadH1u6hX4QWQSTrcTb3raqAKpK\nl67cQ96eXI1WQPSdPhQPTjqzOPZDbot3qMkGFijHar7FdQjDx/cNhqhvxv0LWsvl\njgep1czUFoo1BS3wTUiO0qyloNGOQdgmlTOHbMFk1wgoNyAohfZtfn6LH/zlJnE3\nQ0YkUKgAG+1N/PmkQFO0k5qAflUV7h+HAzT1ZAZcscjHNbQFDc0Zjq9nE9sfhxE8\nOFpnF9Jp3fQVekyyC/dsCxtJdYfhxqYe+BzZu0SlsLCmc1JoK5lkiXQwv6+cFpKW\nwfHMTTrCoOetJyiF7oJX+t4adzmLmnujiw5izxObWQJ7avHC1oYNHfRejrOtlu34\n0nDPRFiSDyEbDCBXPe9dIafqjJVLQGFOeXC8/VN9cGSZp2JV8rqumWOr9E+Wd5zU\n8MRZpevo0i3rPgdyFRpw\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaDCCA1CgAwIBAgIPANHZYxYlOc+wEAEDDWDpMA0GCSqGSIb3DQEBCwUAMFYx\nCzAJBgNVBAYTAkRFMSMwIQYDVQQKExpTQVAgSW9UIFRydXN0IENvbW11bml0eSBJ\nSTEiMCAGA1UEAxMZU0FQIEludGVybmV0IG9mIFRoaW5ncyBDQTAeFw0yMTExMTIw\nNzMyMjNaFw0yMjExMTIwNzMyMjNaMIG1MQswCQYDVQQGEwJERTEcMBoGA1UEChMT\nU0FQIFRydXN0IENvbW11bml0eTEVMBMGA1UECxMMSW9UIFNlcnZpY2VzMXEwbwYD\nVQQDFGhkZXZpY2VBbHRlcm5hdGVJZDoyMTQ1ZGYwZS0zNDUxLTQ2Y2ItYmYyMy0y\nMzE5MWFmNjZmY2V8Z2F0ZXdheUlkOjJ8dGVuYW50SWQ6MTExNjkwMzQ5MHxpbnN0\nYW5jZUlkOmRrZS1xYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeF\naxjV7Xk1R2dFjadN6WsUkrmcVu44vZRCJEbR7Chkg1xcXT6cgIlokO/V4lTgaD6i\neCMKMFegjXzEJQy0dyIWncozcmt6HJFxpdjVQtdCtDtCWykGscNDgvv5ukykOOKI\nMzWJ4d2cJRlostpNe4FYZoPp6cArSHTl9DvfYqjZ/ykeTa1w157dgVxPxezHrJMl\n+z2XgO37mq6CJLw8J6W8RBHbCADgB8c6qGHgJnBURyxnoHHi/yqdIKC6cOs8NAnc\nyVmnvLDu8RUWu9pWkqFHhMvSqdkUCTYORZ9mUTm/Kmv6ss2NaYT4uUBZTskwnAa9\nFLdj+DV2NG0OQl3NYr8CAwEAAaOB0jCBzzBIBgNVHR8EQTA/MD2gO6A5hjdodHRw\nczovL3Rjcy5teXNhcC5jb20vY3JsL1RydXN0Q29tbXVuaXR5SUkvU0FQSW9UQ0Eu\nY3JsMAwGA1UdEwEB/wQCMAAwJQYDVR0SBB4wHIYaaHR0cDovL3NlcnZpY2Uuc2Fw\nLmNvbS9UQ1MwDgYDVR0PAQH/BAQDAgbAMB0GA1UdDgQWBBSRf8DUjowgQ+6amVIs\njd7zM7VWqjAfBgNVHSMEGDAWgBSVt7P1WN7VtLNYRuDypsl4Tr0tdTANBgkqhkiG\n9w0BAQsFAAOCAQEARzSc9GLpSU3pRJPIfgadHrZ+2KQsPsQ1/fLlASlt4V1Rlxn7\n/tn0gk3sP0X5/TrkO+N0kx1qrLarxWSDiVfaXoPa6Lit30SBPnPLUPPPZeTJOz5r\nTW9PkPPuC39GlM1biVoil2cLZrTr9DMSUoBvR4IVKQoJveQsLwn7Ea+SDPE0uvZV\nbDN6UPGZ2yIiCXO1MODJ6r3A4EDD2MArGgfhGdbvJNAY36ShFJhzfzi0t8linEAA\nxh0vcaEEIkVeEiwiguyGWB69X88cjZ0Q5cCf0r6iu3oQnB57uM5TW12OwXQN1NpQ\neK3EMFSoM6BYJu/3B8TXhNmpNBvD7KYozw9XaA==\n-----END CERTIFICATE-----\n" + } +} + + + + import agrirouter as ar from agrirouter.onboarding.enums import GateWays from agrirouter.messaging.enums import CapabilityType @@ -52,7 +78,7 @@ application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI -def test_auth(): +def example_auth(): print("Authorization...\n") auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") @@ -83,9 +109,9 @@ def test_auth(): return auth_data -def test_onboarding(gateway_id): +def example_onboarding(gateway_id): - auth_data = test_auth() + auth_data = example_auth() print("Onboarding...\n") @@ -108,9 +134,14 @@ def test_onboarding(gateway_id): return onboarding_response -def test_list_endpoints_mqtt(onboarding_response): +def example_list_endpoints_mqtt(onboarding_response_data, foo): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = MqttMessagingService( - onboarding_response=onboarding_response + onboarding_response=onboarding_response, + on_message_callback=foo + ) list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, @@ -121,11 +152,19 @@ def test_list_endpoints_mqtt(onboarding_response): application_message_seq_no=1, ) list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) -def test_list_endpoint_http(onboarding_response): +def example_list_endpoints_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, @@ -136,14 +175,19 @@ def test_list_endpoint_http(onboarding_response): application_message_seq_no=1, ) list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + print("Sent message: ", messaging_result) + return messaging_result -def test_subscription_http(onboarding_response): +def example_subscription_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = HttpMessagingService() subscription_service = SubscriptionService(messaging_service) - tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) subscription_parameters = SubscriptionParameters( @@ -152,9 +196,63 @@ def test_subscription_http(onboarding_response): application_message_id=new_uuid(), application_message_seq_no=1, ) + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + return messaging_result +def example_subscription_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + subscription_service = SubscriptionService(messaging_service) + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) + + +def on_message_callback(client, userdata, msg): + + # Define here the way receiving messages will be processed + + from agrirouter.messaging.decode import decode_response + from agrirouter.messaging.decode import decode_details + from agrirouter.messaging.messages import OutboxMessage + + outbox_message = OutboxMessage() + outbox_message.json_deserialize(msg.payload.decode().replace("'", '"')) + + print(outbox_message.command.message) + + decoded_message = decode_response(outbox_message.command.message) + print(decoded_message.response_envelope) + + try: + decoded_details = decode_details(decoded_message.response_payload.details) + print(decoded_details) + except: + pass + + if __name__ == "__main__": - test_list_endpoints_mqtt(test_onboarding(GateWays.MQTT.value)) + onboarding_response_mqtt = example_onboarding(GateWays.MQTT.value) + example_list_endpoints_mqtt(onboarding_response_mqtt.json_serialize(), on_message_callback) + + # of for http + onboarding_response_mqtt = example_onboarding(GateWays.REST.value) + example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) From 65a8c8cc0124c9524412dd051bef88c58e605dba Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:32:35 +0300 Subject: [PATCH 72/78] Add example functions for QueryHeaderService to example_script --- example_script.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/example_script.py b/example_script.py index 2c8b667c..a4752230 100644 --- a/example_script.py +++ b/example_script.py @@ -1,6 +1,9 @@ from pprint import pprint +from google.protobuf.timestamp_pb2 import Timestamp + from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.onboarding.response import SoftwareOnboardingResponse import time @@ -71,7 +74,8 @@ from agrirouter.messaging.enums import CapabilityType from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters, \ + QueryHeaderService, QueryHeaderParameters from agrirouter.utils.uuid_util import new_uuid @@ -226,6 +230,52 @@ def example_subscription_mqtt(onboarding_response_data, on_msg_callback): time.sleep(1) +def example_query_header_message_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + +def example_query_header_message_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + def on_message_callback(client, userdata, msg): # Define here the way receiving messages will be processed From 368689d1c6da3c99d9a8d8f9b5bcb8643e3cfef3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:33:18 +0300 Subject: [PATCH 73/78] Fix example_script --- example_script.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example_script.py b/example_script.py index a4752230..e6b33a91 100644 --- a/example_script.py +++ b/example_script.py @@ -273,7 +273,9 @@ def example_query_header_message_mqtt(onboarding_response_data, on_msg_callback) messaging_result = query_header_service.send(query_header_parameters) print("Sent message: ", messaging_result) - return messaging_result + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) def on_message_callback(client, userdata, msg): From 88d78ba26af0d65e48843313f2d93cac5f775db8 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:29:13 +0300 Subject: [PATCH 74/78] Refactor example_script --- example_script.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example_script.py b/example_script.py index e6b33a91..50e4ae3f 100644 --- a/example_script.py +++ b/example_script.py @@ -47,7 +47,7 @@ -----END PRIVATE KEY-----""" -onboarding_response_mqtt = { +onboarding_response_mqtt_data = { "deviceAlternateId": "2145df0e-3451-46cb-bf23-23191af66fce", "capabilityAlternateId": "523e4623-68d2-43d4-a0cc-e2ada2f68b5e", "sensorAlternateId": "1489638c-7bed-4205-ad77-8d11efdc779f", @@ -297,14 +297,14 @@ def on_message_callback(client, userdata, msg): try: decoded_details = decode_details(decoded_message.response_payload.details) print(decoded_details) - except: - pass + except Exception as exc: + print("Error in decoding details: ", exc) if __name__ == "__main__": onboarding_response_mqtt = example_onboarding(GateWays.MQTT.value) example_list_endpoints_mqtt(onboarding_response_mqtt.json_serialize(), on_message_callback) - # of for http - onboarding_response_mqtt = example_onboarding(GateWays.REST.value) - example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) + # or for http + # onboarding_response_mqtt = example_onboarding(GateWays.REST.value) + # example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) From 1181acb9c4b433ec57fd2b7a6776bacf7ee6fb42 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:30:15 +0300 Subject: [PATCH 75/78] Fix ConnectionCriteria dto --- agrirouter/onboarding/dto.py | 1 + 1 file changed, 1 insertion(+) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index 89d84806..d2b34bed 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -33,6 +33,7 @@ def json_serialize(self) -> dict: self.GATEWAY_ID: self.gateway_id, self.MEASURES: self.measures, self.COMMANDS: self.commands, + self.HOST: self.host, self.PORT: self.port, self.CLIENT_ID: self.client_id } From ab4f189b2e64da8f8476c1f53dd3de9340aa5fd2 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:30:57 +0300 Subject: [PATCH 76/78] Add __str__ and __repr__ methods for SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 99723f96..44a4f5b5 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -157,3 +157,9 @@ def json_deserialize(self, data: Union[dict, str]): self.authentication = authentication else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") + + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) From f51039db7160ca258686579339a8402fdd4ad072 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 15 Nov 2021 15:10:09 +0300 Subject: [PATCH 77/78] Fix mqtt client (#17) --- agrirouter/auth/dto.py | 84 ++++--- agrirouter/auth/response.py | 20 +- agrirouter/messaging/clients/constants.py | 2 + agrirouter/messaging/clients/mqtt.py | 94 ++++--- agrirouter/messaging/services/commons.py | 31 +-- agrirouter/onboarding/dto.py | 13 + agrirouter/onboarding/response.py | 20 +- example_script.py | 290 ++++++++++++++++++---- examples.txt | 2 +- tests/messaging_test/test_decode.py | 21 ++ tests/sleeper.py | 2 +- 11 files changed, 416 insertions(+), 163 deletions(-) create mode 100644 agrirouter/messaging/clients/constants.py create mode 100644 tests/messaging_test/test_decode.py diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py index fec51c0a..8e4be05e 100644 --- a/agrirouter/auth/dto.py +++ b/agrirouter/auth/dto.py @@ -4,44 +4,6 @@ from agrirouter.messaging.exceptions import WrongFieldError -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - class AuthorizationToken: ACCOUNT = 'account' REGISTRATION_CODE = 'regcode' @@ -88,6 +50,52 @@ def set_expires(self, expires: str) -> None: self.expires = expires +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + decoded_token: AuthorizationToken = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.decoded_token = decoded_token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + def get_decoded_token(self) -> AuthorizationToken: + return self.decoded_token + + def set_decoded_token(self, decoded_token: AuthorizationToken) -> None: + self.decoded_token = decoded_token + + class AuthorizationResult: def __init__(self, *, diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index b5a73d48..a4b87f7f 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,7 +5,7 @@ from cryptography.exceptions import InvalidSignature -from agrirouter.auth.dto import AuthorizationToken +from agrirouter.auth.dto import AuthorizationToken, AuthorizationResultUrl from agrirouter.onboarding.signature import verify_signature @@ -69,16 +69,16 @@ def decode_token(token: Union[str, bytes]) -> AuthorizationToken: auth_token.json_deserialize(json.loads(decoded_token)) return auth_token - def get_auth_result(self) -> dict: - if not self.is_successful: - return {self.ERROR_KEY: self.error} + def get_auth_result(self) -> AuthorizationResultUrl: decoded_token = self.decode_token(self.token) - return { - self.SIGNATURE_KEY: self.signature, - self.STATE_KEY: self.state, - self.TOKEN_KEY: self.token, - self.CRED_KEY: decoded_token - } + + return AuthorizationResultUrl( + signature=self.signature, + state=self.state, + token=self.token, + decoded_token=decoded_token, + error=self.error + ) def get_signature(self): return self.signature diff --git a/agrirouter/messaging/clients/constants.py b/agrirouter/messaging/clients/constants.py new file mode 100644 index 00000000..78de8628 --- /dev/null +++ b/agrirouter/messaging/clients/constants.py @@ -0,0 +1,2 @@ +ASYNC = "ASYNC" +SYNC = "SYNC" diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 72ff399d..423897d0 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -1,16 +1,22 @@ +import time +import ssl from typing import Any, List, Tuple -from paho.mqtt import client as mqtt_client +import paho.mqtt.client as mqtt_client from paho.mqtt.client import MQTTv31, MQTTMessageInfo +from agrirouter.messaging.certification import create_certificate_file_from_pen +from agrirouter.messaging.clients.constants import SYNC, ASYNC + class MqttClient: def __init__(self, - client_id, + onboard_response, + client_id: str, on_message_callback: callable = None, userdata: Any = None, - clean_session: bool = True + clean_session: bool = False ): # TODO: Implement on_message_callback parameter validation: # must take params as described at https://pypi.org/project/paho-mqtt/#callbacks @@ -22,26 +28,54 @@ def __init__(self, protocol=MQTTv31, transport="tcp" ) + self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() - self.mqtt_client.on_connect = self._get_on_connect_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback(onboard_response) self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() + certificate_file_path = create_certificate_file_from_pen(onboard_response) + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.load_cert_chain( + certfile=certificate_file_path, + keyfile=certificate_file_path, + password=onboard_response.get_authentication().get_secret(), + ) + self.mqtt_client.tls_set_context(context) + + self._mode = None + def connect(self, host: str, port: str) -> None: + self.mqtt_client.connect( + host=host, + port=int(port) + ) + self.mqtt_client.loop() + + self._mode = SYNC + + def connect_async(self, host: str, port: str): self.mqtt_client.connect_async( host=host, - port=port + port=int(port) ) self.mqtt_client.loop_start() + self._mode = ASYNC + + while self.mqtt_client._state == 0: + time.sleep(1) + def disconnect(self): self.mqtt_client.loop_stop() self.mqtt_client.disconnect() - def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: - """ + def receive_outbox_messages(self): + self.mqtt_client.loop() + def publish(self, topic, payload, qos=2) -> MQTTMessageInfo: + """ :param topic: str representing unique name of the topic that the message should be published on :param payload: The actual message to send :param qos: int representing the quality of service level to use. May be [0, 1, 2] @@ -52,6 +86,10 @@ def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: payload=payload, qos=qos ) + if self._mode == SYNC: + self.mqtt_client.loop() + time.sleep(3) + self.mqtt_client.loop() return message_info def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: @@ -67,7 +105,7 @@ def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: :return: tuple """ - result, mid = self.mqtt_client.subscribe(topics) + result, mid = self.mqtt_client.subscribe(topics, qos=2) return result, mid def unsubscribe(self, topics: List[str]) -> tuple: @@ -85,22 +123,12 @@ def unsubscribe(self, topics: List[str]) -> tuple: return result, mid @staticmethod - def _get_on_connect_callback() -> callable: - - def on_connect(client, userdata, flags, rc, properties=None): - print("Connection started") - with open("connection.txt", "w") as file: - file.write("Connection started") - if rc == 0: - file.write("Connected!!") - else: - file.write("Do not Connected!!") - if rc == 0: - print("Connected to MQTT Broker!") - else: - print(f"Failed to connect, return code: {rc}") + def _get_on_connect_callback(onboard_response) -> callable: - return client, userdata, flags, rc, properties + def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None): + if rc == 0: + client.subscribe(topic=onboard_response.connection_criteria.commands) + time.sleep(3) return on_connect @@ -108,8 +136,6 @@ def on_connect(client, userdata, flags, rc, properties=None): def _get_on_message_callback() -> callable: def on_message(client, userdata, msg): - # print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") - return client, userdata, msg return on_message @@ -117,29 +143,23 @@ def on_message(client, userdata, msg): @staticmethod def _get_on_subscribe_callback() -> callable: - def on_subscribe(client, userdata, mid, granted_qos, properties=None): - # print(f"Subscribed {userdata} to `{properties}`") - - return client, userdata, mid, granted_qos, properties + def on_subscribe(*args, **kwargs): + return args, kwargs return on_subscribe @staticmethod def _get_on_disconnect_callback() -> callable: - def on_disconnect(client, userdata, rc): - # print(f"Disconnected from from `{properties}`") - - return client, userdata, rc + def on_disconnect(*args, **kwargs): + return args, kwargs return on_disconnect @staticmethod def _get_on_unsubscribe_callback() -> callable: - def on_unsubscribe(client, userdata, mid): - # print(f"Unsubscribed `{userdata}` from `{properties}`") - - return client, userdata, mid + def on_unsubscribe(*args, **kwargs): + return args, kwargs return on_unsubscribe diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 425aa976..f610bca3 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -11,6 +11,7 @@ from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult from agrirouter.onboarding.exceptions import BadMessagingResult +from agrirouter.onboarding.response import SoftwareOnboardingResponse class AbstractMessagingClient(ABC): @@ -50,33 +51,35 @@ def send(self, parameters) -> MessagingResult: class MqttMessagingService(AbstractMessagingClient): def __init__(self, - client_id, - onboarding_response, + onboarding_response: SoftwareOnboardingResponse, on_message_callback: callable = None, + client_async: bool = True ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=client_id, + onboard_response=onboarding_response, + client_id=onboarding_response.get_connection_criteria().get_client_id(), on_message_callback=on_message_callback, ) - self.client.connect( - self.onboarding_response.get_connection_criteria().get_host(), - self.onboarding_response.get_connection_criteria().get_port() - ) + if client_async: + self.client.connect_async( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) + else: + self.client.connect( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) def send(self, parameters, qos: int = 0) -> MessagingResult: message_request = self.create_message_request(parameters) mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), + topic=self.onboarding_response.get_connection_criteria().get_measures(), + payload=json.dumps(mqtt_payload), qos=qos ) result = MessagingResult([parameters.get_application_message_id()]) return result - - def subscribe(self): - pass - - def unsubscribe(self): - pass diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index c4cbc314..d2b34bed 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -33,6 +33,7 @@ def json_serialize(self) -> dict: self.GATEWAY_ID: self.gateway_id, self.MEASURES: self.measures, self.COMMANDS: self.commands, + self.HOST: self.host, self.PORT: self.port, self.CLIENT_ID: self.client_id } @@ -91,6 +92,12 @@ def get_client_id(self) -> str: def set_client_id(self, client_id: str) -> None: self.client_id = client_id + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class Authentication: TYPE = 'type' @@ -144,6 +151,12 @@ def get_certificate(self) -> str: def set_certificate(self, certificate: str) -> None: self.certificate = certificate + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class ErrorResponse: def __init__(self, diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 1b9c6591..44a4f5b5 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -79,7 +79,7 @@ def __init__(self, http_response: Response = None): commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), port=response_body.get("connectionCriteria").get("port"), - client_id=response_body.get("connectionCriteria").get("client_id") + client_id=response_body.get("connectionCriteria").get("clientId") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( @@ -134,8 +134,8 @@ def json_serialize(self): self.DEVICE_ALTERNATE_ID: self.device_alternate_id, self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, - self.CONNECTION_CRITERIA: self.connection_criteria, - self.AUTHENTICATION: self.authentication + self.CONNECTION_CRITERIA: self.connection_criteria.json_serialize(), + self.AUTHENTICATION: self.authentication.json_serialize() } def json_deserialize(self, data: Union[dict, str]): @@ -143,17 +143,23 @@ def json_deserialize(self, data: Union[dict, str]): for (key, value) in data_dict.items(): if key == self.DEVICE_ALTERNATE_ID: self.device_alternate_id = value - if key == self.CAPABILITY_ALTERNATE_ID: + elif key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value - if key == self.SENSOR_ALTERNATE_ID: + elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value - if key == self.CONNECTION_CRITERIA: + elif key == self.CONNECTION_CRITERIA: connection_criteria = ConnectionCriteria() connection_criteria.json_deserialize(value) self.connection_criteria = connection_criteria - if key == self.AUTHENTICATION: + elif key == self.AUTHENTICATION: authentication = Authentication() authentication.json_deserialize(value) self.authentication = authentication else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") + + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) diff --git a/example_script.py b/example_script.py index 0995c898..50e4ae3f 100644 --- a/example_script.py +++ b/example_script.py @@ -1,3 +1,12 @@ +from pprint import pprint + +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.onboarding.response import SoftwareOnboardingResponse +import time + public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN 6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk @@ -38,93 +47,264 @@ -----END PRIVATE KEY-----""" -application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI +onboarding_response_mqtt_data = { + "deviceAlternateId": "2145df0e-3451-46cb-bf23-23191af66fce", + "capabilityAlternateId": "523e4623-68d2-43d4-a0cc-e2ada2f68b5e", + "sensorAlternateId": "1489638c-7bed-4205-ad77-8d11efdc779f", + "connectionCriteria": { + "gatewayId": "2", + "host": "dke-qa.eu10.cp.iot.sap", + "port": 8883, + "clientId": "2145df0e-3451-46cb-bf23-23191af66fce", + "measures": "measures/2145df0e-3451-46cb-bf23-23191af66fce", + "commands": "commands/2145df0e-3451-46cb-bf23-23191af66fce" + }, + "authentication": { + "type": "PEM", + "secret": "JNKdNg8R0lwmFgvrUfOCc7inebr0h?!7Z9wL", + "certificate": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECMkL85F+LbPbAgMCAAAEggTI1CmRlnDUStBv\nTycvaRVFMCk1OuynhiOYRF6HBFFXBCxWKZa3WqTShLdf9iCel/NgtdZIiQsoD1LL\nMxVyh8pWAfLQ+pDJLvM6suQjHALt8dW5iTeCZ7R1gzFvPJ+xnDGFFytN7HmGSvHM\nQbcCOuEeIu8U6ENa6/+WmUwK9/ZMkLNqDHVKEGpI+lSJs8JWEE+S3Klmsxuq0dvz\nh6o3V7RKFwMfUZOQLHezGBDjLfEBdP+d2G87CY+LSzinL8pFhLwyrXFKfYWYoT0m\n5PkDdjfiVq3SJIUoQWnGrjaVVw4TV3WSxmhQnWbDwOQydr8DAiBxDMYoeK3rePpC\nwh6KATnBrovq1icqjonYDE0T+3Rs2SUbG+3+m9Zj4j46L2Sh9bUB6qxdw74Ck2/z\nAzJ1N+tB+RL7UvOpMOhmndMBl5qpx9dFFy8Z/N7w4YTQLZLN7chD8ApeFhCgvppt\nAGh8/VeWO54OC9ZOSHpxEl7sJz97jaHYNbw/lGbDk7cOZezwpA0NCWZ/Bb1vRDzy\n8EDX9s1hOA3jiy2T1RSyk2Rj/12pWdKtdSO8lMhMKC0B32Zr1F8rBJKDVzqFWuTt\nn+pXOKedyOA/ggyvYJdsltP8O4XB2oBN3WBdFK7Y1FG/tN30LsaqcnFTxab5v1Pp\ngq2dHu6Xy0TCMAw/DH3RmGXlGnDDWu86Zad7TjjrEZvpSIv4TTSCqqTvc4IN0xFX\nbKZCrY6JSkJWWnDMKrsRYOijUDvpAbYwZuTV9PAljYbt5YX778qxV9O0fNBQdaww\nNlfxU93jgr4g3E9nIzRxLu9S98hPbxKUnVYiQmYvP7vJUcUSo5F0LmUU/nvHY1pi\nr4tZDp8Xu1aZy7cOd3sTbf/68IjiZMZlF5/PVlOFOo40yGqW600j/qEqXoY/492h\nONXUCpHKaG/Pkjtg9THuYoaw1773gxYYsYLt+c6NkQCCsydOr2BMZQ4Qy4bZV67D\n2RNDeZzSBY6jEX6dnfY0FJqIsSiw28Ek5NXx0HTEGN8txPkx/1dfu3RfZnzUqT/0\nmS9xcWVYRmlip3vm48fMecqP/DNIHyjVLC39SsFdeXa+De76z/S3+or0t7HGlUim\nNVkIcWqm/sD2ia8hYberaRRTbUQ1iObNToIg8dA/xna6D61sYK8jkf1GVPpKsCTA\nOVW5u9XrE1f5YQEovE9kFgvtzs0u6jSeI9edqVadH1u6hX4QWQSTrcTb3raqAKpK\nl67cQ96eXI1WQPSdPhQPTjqzOPZDbot3qMkGFijHar7FdQjDx/cNhqhvxv0LWsvl\njgep1czUFoo1BS3wTUiO0qyloNGOQdgmlTOHbMFk1wgoNyAohfZtfn6LH/zlJnE3\nQ0YkUKgAG+1N/PmkQFO0k5qAflUV7h+HAzT1ZAZcscjHNbQFDc0Zjq9nE9sfhxE8\nOFpnF9Jp3fQVekyyC/dsCxtJdYfhxqYe+BzZu0SlsLCmc1JoK5lkiXQwv6+cFpKW\nwfHMTTrCoOetJyiF7oJX+t4adzmLmnujiw5izxObWQJ7avHC1oYNHfRejrOtlu34\n0nDPRFiSDyEbDCBXPe9dIafqjJVLQGFOeXC8/VN9cGSZp2JV8rqumWOr9E+Wd5zU\n8MRZpevo0i3rPgdyFRpw\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaDCCA1CgAwIBAgIPANHZYxYlOc+wEAEDDWDpMA0GCSqGSIb3DQEBCwUAMFYx\nCzAJBgNVBAYTAkRFMSMwIQYDVQQKExpTQVAgSW9UIFRydXN0IENvbW11bml0eSBJ\nSTEiMCAGA1UEAxMZU0FQIEludGVybmV0IG9mIFRoaW5ncyBDQTAeFw0yMTExMTIw\nNzMyMjNaFw0yMjExMTIwNzMyMjNaMIG1MQswCQYDVQQGEwJERTEcMBoGA1UEChMT\nU0FQIFRydXN0IENvbW11bml0eTEVMBMGA1UECxMMSW9UIFNlcnZpY2VzMXEwbwYD\nVQQDFGhkZXZpY2VBbHRlcm5hdGVJZDoyMTQ1ZGYwZS0zNDUxLTQ2Y2ItYmYyMy0y\nMzE5MWFmNjZmY2V8Z2F0ZXdheUlkOjJ8dGVuYW50SWQ6MTExNjkwMzQ5MHxpbnN0\nYW5jZUlkOmRrZS1xYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeF\naxjV7Xk1R2dFjadN6WsUkrmcVu44vZRCJEbR7Chkg1xcXT6cgIlokO/V4lTgaD6i\neCMKMFegjXzEJQy0dyIWncozcmt6HJFxpdjVQtdCtDtCWykGscNDgvv5ukykOOKI\nMzWJ4d2cJRlostpNe4FYZoPp6cArSHTl9DvfYqjZ/ykeTa1w157dgVxPxezHrJMl\n+z2XgO37mq6CJLw8J6W8RBHbCADgB8c6qGHgJnBURyxnoHHi/yqdIKC6cOs8NAnc\nyVmnvLDu8RUWu9pWkqFHhMvSqdkUCTYORZ9mUTm/Kmv6ss2NaYT4uUBZTskwnAa9\nFLdj+DV2NG0OQl3NYr8CAwEAAaOB0jCBzzBIBgNVHR8EQTA/MD2gO6A5hjdodHRw\nczovL3Rjcy5teXNhcC5jb20vY3JsL1RydXN0Q29tbXVuaXR5SUkvU0FQSW9UQ0Eu\nY3JsMAwGA1UdEwEB/wQCMAAwJQYDVR0SBB4wHIYaaHR0cDovL3NlcnZpY2Uuc2Fw\nLmNvbS9UQ1MwDgYDVR0PAQH/BAQDAgbAMB0GA1UdDgQWBBSRf8DUjowgQ+6amVIs\njd7zM7VWqjAfBgNVHSMEGDAWgBSVt7P1WN7VtLNYRuDypsl4Tr0tdTANBgkqhkiG\n9w0BAQsFAAOCAQEARzSc9GLpSU3pRJPIfgadHrZ+2KQsPsQ1/fLlASlt4V1Rlxn7\n/tn0gk3sP0X5/TrkO+N0kx1qrLarxWSDiVfaXoPa6Lit30SBPnPLUPPPZeTJOz5r\nTW9PkPPuC39GlM1biVoil2cLZrTr9DMSUoBvR4IVKQoJveQsLwn7Ea+SDPE0uvZV\nbDN6UPGZ2yIiCXO1MODJ6r3A4EDD2MArGgfhGdbvJNAY36ShFJhzfzi0t8linEAA\nxh0vcaEEIkVeEiwiguyGWB69X88cjZ0Q5cCf0r6iu3oQnB57uM5TW12OwXQN1NpQ\neK3EMFSoM6BYJu/3B8TXhNmpNBvD7KYozw9XaA==\n-----END CERTIFICATE-----\n" + } +} + -######################################################## -# Authorization -print("Authorization...\n") import agrirouter as ar +from agrirouter.onboarding.enums import GateWays +from agrirouter.messaging.enums import CapabilityType +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters, \ + QueryHeaderService, QueryHeaderParameters +from agrirouter.utils.uuid_util import new_uuid -auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") -auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) -auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization -print(f"auth_url={auth_url}") -auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. -auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process -auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI -print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected -print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying +def example_auth(): + print("Authorization...\n") + auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") + auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) + auth_url = auth_client.get_auth_request_url( + auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization + print(f"auth_url={auth_url}") -# Get dict containing data from auth process you will use for futher communication. -# If auth was rejected, contains {"error"} key. -# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys -# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. -auth_data = auth_response.get_auth_result() -print(f"auth_data: {auth_data}") + auth_result_url = input( + "Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. + auth_response = auth_client.extract_auth_response( + auth_result_url) # auth_response contains the results of the auth process + auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR -######################################################## + print( + f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected -# Onboarding -print("Onboarding...\n") + print( + f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + # Get dict containing data from auth process you will use for futher communication. + # If auth was rejected, contains {"error"} key. + # If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys + # Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. + auth_data = auth_response.get_auth_result() + print(f"auth_data: {auth_data}") -from agrirouter.onboarding.enums import GateWays + return auth_data -id_ = "urn:myapp:snr00003234" # just unique -certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI -time_zone = "+03:00" -onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) -onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) -onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) -print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") -print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") -onboarding_response = onboarding_client.onboard(onboarding_parameters) -print(f"onboarding_response.status_code: {onboarding_response.status_code}") -print(f"onboarding_response.text: {onboarding_response.text}") +def example_onboarding(gateway_id): + auth_data = example_auth() -########################## -# Messaging + print("Onboarding...\n") + id_ = "urn:myapp:snr00003234" # just unique + certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI + time_zone = "+03:00" -from agrirouter.messaging.enums import CapabilityTypeDefinitions -from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription -from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters -from agrirouter.utils.uuid_util import new_uuid + onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) + onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, + certification_version_id=certification_version_id, + gateway_id=gateway_id, time_zone=time_zone, + reg_code=auth_data.get_decoded_token().regcode) + onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) + print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") + print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") + onboarding_response = onboarding_client.onboard(onboarding_parameters) + print(f"onboarding_response.status_code: {onboarding_response.status_code}") + print(f"onboarding_response.text: {onboarding_response.text}") + + return onboarding_response + + +def example_list_endpoints_mqtt(onboarding_response_data, foo): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService( + onboarding_response=onboarding_response, + on_message_callback=foo + + ) + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + print("Sent message: ", messaging_result) -# List Endpoints + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) -messaging_service = HttpMessagingService() -list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + +def example_list_endpoints_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = HttpMessagingService() + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, direction=2, filtered=False, onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, ) -list_endpoint_service = ListEndpointsService(messaging_service) -list_endpoint_service.send(list_endpoint_parameters) + list_endpoint_service = ListEndpointsService(messaging_service) + + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + print("Sent message: ", messaging_result) -# Subscription + return messaging_result -messaging_service = HttpMessagingService() -subscription_service = SubscriptionService(messaging_service) -tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value -subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) -subscription_parameters = SubscriptionParameters( +def example_subscription_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = HttpMessagingService() + subscription_service = SubscriptionService(messaging_service) + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( subscription_items=[subscription_item], onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, -) -subscription_service.send(subscription_parameters) + ) + + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + +def example_subscription_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + subscription_service = SubscriptionService(messaging_service) + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) + + +def example_query_header_message_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + +def example_query_header_message_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) + + +def on_message_callback(client, userdata, msg): + + # Define here the way receiving messages will be processed + + from agrirouter.messaging.decode import decode_response + from agrirouter.messaging.decode import decode_details + from agrirouter.messaging.messages import OutboxMessage + + outbox_message = OutboxMessage() + outbox_message.json_deserialize(msg.payload.decode().replace("'", '"')) + + print(outbox_message.command.message) + + decoded_message = decode_response(outbox_message.command.message) + print(decoded_message.response_envelope) + + try: + decoded_details = decode_details(decoded_message.response_payload.details) + print(decoded_details) + except Exception as exc: + print("Error in decoding details: ", exc) + + +if __name__ == "__main__": + onboarding_response_mqtt = example_onboarding(GateWays.MQTT.value) + example_list_endpoints_mqtt(onboarding_response_mqtt.json_serialize(), on_message_callback) + + # or for http + # onboarding_response_mqtt = example_onboarding(GateWays.REST.value) + # example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) diff --git a/examples.txt b/examples.txt index fcb13cd4..3ca0f065 100644 --- a/examples.txt +++ b/examples.txt @@ -89,7 +89,7 @@ True >>> time_zone = "+03:00" >>> onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) ->>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +>>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data.get_decoded_token().regcode) >>> onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) >>> onboarding_verifying_response.status_code >>> onboarding_verifying_response.text diff --git a/tests/messaging_test/test_decode.py b/tests/messaging_test/test_decode.py new file mode 100644 index 00000000..77d62143 --- /dev/null +++ b/tests/messaging_test/test_decode.py @@ -0,0 +1,21 @@ +import json + +import pytest + +from agrirouter.messaging.decode import decode_response +from agrirouter.messaging.decode import decode_details + + +MESSAGING_RESULT = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' + + +def test_decode_response(): + pass + + +def test_decode_details(): + json_response = json.loads(MESSAGING_RESULT) + message = decode_response(json_response[0]["command"]["message"].encode()) + decoded_details = decode_details(message.response_payload.details) + print(decoded_details) + assert False diff --git a/tests/sleeper.py b/tests/sleeper.py index 73eb1d28..9d4179ac 100644 --- a/tests/sleeper.py +++ b/tests/sleeper.py @@ -2,4 +2,4 @@ def let_agrirouter_process_the_message(seconds: int = 3): - time.sleep(3) + time.sleep(seconds) From e6f35c3391780c64b92ec153be9533ac1e009b97 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 17:12:32 +0300 Subject: [PATCH 78/78] Fix flake8 warnings --- agrirouter/auth/auth.py | 1 - agrirouter/auth/dto.py | 2 +- agrirouter/environments/environments.py | 8 ++++---- agrirouter/messaging/builders.py | 2 +- agrirouter/messaging/certification.py | 3 --- agrirouter/messaging/clients/http.py | 1 - agrirouter/messaging/decode.py | 1 - agrirouter/messaging/messages.py | 3 +-- agrirouter/messaging/parameters/service.py | 1 - agrirouter/messaging/request.py | 2 -- agrirouter/messaging/services/commons.py | 4 ---- agrirouter/messaging/services/http/outbox.py | 9 --------- agrirouter/messaging/services/messaging.py | 2 +- agrirouter/onboarding/headers.py | 2 -- agrirouter/onboarding/onboarding.py | 2 -- agrirouter/onboarding/parameters.py | 2 -- agrirouter/revoking/headers.py | 2 +- 17 files changed, 9 insertions(+), 38 deletions(-) diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index f1e2f634..b33527c0 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -34,4 +34,3 @@ def verify_auth_response(self, response, public_key=None): def _extract_query_params(query_params: str) -> dict: qp_pairs = parse_qs(query_params) return {k: v[0] for k, v in qp_pairs.items()} - diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py index 8e4be05e..55d1bbbb 100644 --- a/agrirouter/auth/dto.py +++ b/agrirouter/auth/dto.py @@ -115,4 +115,4 @@ def get_state(self) -> str: return self.state def set_state(self, state: str) -> None: - self.state = state \ No newline at end of file + self.state = state diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 5c688c7a..6b05b99e 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -40,10 +40,10 @@ def get_agrirouter_login_url(self) -> str: def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri=None) -> str: auth_url = self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( - application_id=application_id, - response_type=response_type, - state=state - ) + application_id=application_id, + response_type=response_type, + state=state + ) return auth_url + f"&redirect_uri={redirect_uri}" if redirect_uri is not None else auth_url def get_mqtt_server_url(self, host, port) -> str: diff --git a/agrirouter/messaging/builders.py b/agrirouter/messaging/builders.py index 94e56ce8..d7815042 100644 --- a/agrirouter/messaging/builders.py +++ b/agrirouter/messaging/builders.py @@ -23,7 +23,7 @@ def with_task_data(self): self._subscription_items.append(subscription_item) return self - def with_device_description(self, ddis: List[int]=None, position: bool=None): + def with_device_description(self, ddis: List[int] = None, position: bool = None): subscription_item = Subscription.MessageTypeSubscriptionItem( technical_message_type=CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value, ddis=ddis, diff --git a/agrirouter/messaging/certification.py b/agrirouter/messaging/certification.py index 50de6778..c87afc83 100644 --- a/agrirouter/messaging/certification.py +++ b/agrirouter/messaging/certification.py @@ -1,7 +1,4 @@ -import json import os -import pathlib -from pathlib import Path import tempfile diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 429be1e7..cf1e400f 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -5,7 +5,6 @@ from urllib.parse import urlparse from agrirouter.messaging.certification import create_certificate_file_from_pen -from agrirouter.onboarding.dto import ConnectionCriteria from agrirouter.onboarding.response import SoftwareOnboardingResponse diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index 02387153..0d1a4e12 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -1,5 +1,4 @@ import base64 -from ctypes import Union from google.protobuf.any_pb2 import Any from google.protobuf.internal.decoder import _DecodeVarint diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index da475631..7db4e461 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -1,6 +1,5 @@ import json -from datetime import datetime, timezone -from typing import Union, List, Dict +from typing import Union, Dict from agrirouter.messaging.exceptions import WrongFieldError from agrirouter.utils.utc_time_util import now_as_utc_str diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 631188ae..758df834 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod from copy import deepcopy from typing import List diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py index 9cc9507e..14766318 100644 --- a/agrirouter/messaging/request.py +++ b/agrirouter/messaging/request.py @@ -1,7 +1,5 @@ from typing import List -from agrirouter.messaging.messages import Message - class MessageRequest: SENSOR_ALTERNATE_ID = "sensorAlternateId" diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index f610bca3..8f4f6db5 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -1,10 +1,6 @@ import json -import os from abc import ABC, abstractmethod -import requests - -from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index 13db25cf..7985f914 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -1,15 +1,7 @@ -import json -import os - -import requests - from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.exceptions import OutboxException -from agrirouter.messaging.messages import OutboxMessage from agrirouter.messaging.result import OutboxResponse -from agrirouter.messaging.certification import create_certificate_file_from_pen - class OutboxService: @@ -27,4 +19,3 @@ def fetch(self, onboarding_response) -> OutboxResponse: raise OutboxException(f"Could not fetch messages from outbox. Status code was {response.status}") return outbox_response - diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index 685934ba..81f60d6f 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -6,7 +6,7 @@ from agrirouter.messaging.encode import encode_message from agrirouter.messaging.enums import TechnicalMessageType from agrirouter.messaging.messages import EncodedMessage -from agrirouter.messaging.parameters.dto import MessageParameters, MessagingParameters +from agrirouter.messaging.parameters.dto import MessagingParameters from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters, \ CapabilityParameters, FeedConfirmParameters, FeedDeleteParameters, ListEndpointsParameters, \ SubscriptionParameters, QueryHeaderParameters, QueryMessageParameters diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index fb598310..7843e2ad 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,5 +1,3 @@ -import base64 - from agrirouter.constants.media_types import ContentTypes diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 74ded694..3119e181 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -1,5 +1,3 @@ -import json - import requests from agrirouter.environments.environmental_services import EnvironmentalService diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index bb62ae2e..1895c9fe 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,5 +1,3 @@ -from datetime import datetime - from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes from agrirouter.utils.utc_time_util import now_as_utc_str diff --git a/agrirouter/revoking/headers.py b/agrirouter/revoking/headers.py index 0c1770f2..50652d5a 100644 --- a/agrirouter/revoking/headers.py +++ b/agrirouter/revoking/headers.py @@ -23,4 +23,4 @@ def _set_params(self, application_id: str, signature: str, content_type: str): if signature: header["X-Agrirouter-Signature"] = signature - self.params = header \ No newline at end of file + self.params = header