diff --git a/tests/net/lib/lwm2m/interop/README.md b/tests/net/lib/lwm2m/interop/README.md index 19883ac1821..72eb430f8e4 100644 --- a/tests/net/lib/lwm2m/interop/README.md +++ b/tests/net/lib/lwm2m/interop/README.md @@ -15,7 +15,39 @@ running Zephyr is using address `192.0.2.1`. Follow [Networking with the host system](https://docs.zephyrproject.org/latest/connectivity/networking/networking_with_host.html#networking-with-the-host-system) from Zephyr's documentation how to set it up, or follow [Create NAT and routing for Zephyr native network on Linux](https://github.com/zephyrproject-rtos/net-tools/blob/master/README%20NAT.md). -### Leshan server setup + +### Run Lehan server from net-tools Docker container + +Zephyr's net-tools Docker container already contains Leshan, so if you don't want to set the environment up manually, +use the pre-made docker. + +First, build the docker container. You only need to do this one, or when you update the container. +``` +cd tools/net-tools/docker +docker build -t net-tools . +``` + +Start the docker networking +``` +cd tools/net-tools/ +sudo ./net-setup.sh --config docker.conf start +``` + +Start the docker container and run leshan +``` +docker run --hostname=net-tools --name=net-tools --ip=192.0.2.2 --ip6=2001:db8::2 -p 8080:8080 -p 8081:8081 -p 5683:5683/udp --rm -dit --network=net-tools0 net-tools +docker container exec net-tools /net-tools/start-leshan.sh +``` + +### Stop Leshan, docker and networking + +``` +cd tools/net-tools/ +docker kill net-tools +sudo ./net-setup.sh --config docker.conf stop +docker network rm net-tools0 +``` +### Leshan server setup (manual) * Leshan server must be reachable from the device using IP address `192.0.2.2`. Configure the port forwarding, if you use Docker to run Leshan. @@ -87,23 +119,59 @@ Or use the Docker based testing Tests are written from test spec; [OMA Enabler Test Specification (Interoperability) for Lightweight M2M](https://www.openmobilealliance.org/release/LightweightM2M/ETS/OMA-ETS-LightweightM2M-V1_1-20190912-D.pdf) -Following tests are implemented: -* LightweightM2M-1.1-int-0 – Client Initiated Bootstrap -* LightweightM2M-1.1-int-1 – Client Initiated Bootstrap Full (PSK) -* LightweightM2M-1.1-int-101 – Initial Registration -* LightweightM2M-1.1-int-102 – Registration Update -* LightweightM2M-1.1-int-104 – Registration Update Trigge -* LightweightM2M-1.1-int-105 - Discarded Register Update -* LightweightM2M-1.1-int-107 – Extending the lifetime of a registration -* LightweightM2M-1.1-int-108 – Turn on Queue Mode -* LightweightM2M-1.1-int-109 – Behavior in Queue Mode -* LightweightM2M-1.1-int-201 – Querying basic information in Plain Text -* LightweightM2M-1.1-int-203 – Querying basic information in TLV format -* LightweightM2M-1.1-int-204 – Querying basic information in JSON format -* LightweightM2M-1.1-int-205 – Setting basic information in Plain Text -* LightweightM2M-1.1-int-211 – Querying basic information in CBOR format -* LightweightM2M-1.1-int-212 – Setting basic information in CBOR format -* LightweightM2M-1.1-int-215 – Setting basic information in TLV format -* LightweightM2M-1.1-int-220 – Setting basic information in JSON format -* LightweightM2M-1.1-int-221 – Attempt to perform operations on Security -* LightweightM2M-1.1-int-401 – UDP Channel Security – PSK Mode +## Current status + +|Test case|Status|Notes| +|---------|------|-----| +|LightweightM2M-1.1-int-0 - Client Initiated Bootstrap |:white_check_mark:| | +|LightweightM2M-1.1-int-1 - Client Initiated Bootstrap Full (PSK) |:white_check_mark:| | +|LightweightM2M-1.1-int-101 - Initial Registration |:white_check_mark:| | +|LightweightM2M-1.1-int-102 - Registration Update |:white_check_mark:| | +|LightweightM2M-1.1-int-103 - Deregistration |:large_orange_diamond:|We don't have "disabled" functionality in server object| +|LightweightM2M-1.1-int-104 - Registration Update Trigge |:white_check_mark:| | +|LightweightM2M-1.1-int-105 - Discarded Register Update |:white_check_mark:| | +|LightweightM2M-1.1-int-107 - Extending the lifetime of a registration |:white_check_mark:| | +|LightweightM2M-1.1-int-108 - Turn on Queue Mode |:white_check_mark:| | +|LightweightM2M-1.1-int-109 - Behavior in Queue Mode |:white_check_mark:| | +|LightweightM2M-1.1-int-201 - Querying basic information in Plain Text |:white_check_mark:| | +|LightweightM2M-1.1-int-203 - Querying basic information in TLV format |:white_check_mark:| | +|LightweightM2M-1.1-int-204 - Querying basic information in JSON format |:white_check_mark:| | +|LightweightM2M-1.1-int-205 - Setting basic information in Plain Text |:white_check_mark:| | +|LightweightM2M-1.1-int-211 - Querying basic information in CBOR format |:white_check_mark:| | +|LightweightM2M-1.1-int-212 - Setting basic information in CBOR format |:white_check_mark:| | +|LightweightM2M-1.1-int-215 - Setting basic information in TLV format |:white_check_mark:| | +|LightweightM2M-1.1-int-220 - Setting basic information in JSON format |:white_check_mark:| | +|LightweightM2M-1.1-int-221 - Attempt to perform operations on Security |:white_check_mark:| | +|LightweightM2M-1.1-int-222 - Read on Object |:white_check_mark:| | +|LightweightM2M-1.1-int-223 - Read on Object Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-224 - Read on Resource |:white_check_mark:| | +|LightweightM2M-1.1-int-225 - Read on Resource Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-226 - Write (Partial Update) on Object Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-222 - Read on Object |:white_check_mark:| | +|LightweightM2M-1.1-int-223 - Read on Object Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-224 - Read on Resource |:white_check_mark:| | +|LightweightM2M-1.1-int-225 - Read on Resource Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-226 - Write (Partial Update) on Object Instance |:white_check_mark:| | +|LightweightM2M-1.1-int-227 - Write (replace) on Resource |:white_check_mark:| | +|LightweightM2M-1.1-int-228 - Write on Resource Instance |:white_check_mark:|[~~#64011~~](https://github.com/zephyrproject-rtos/zephyr/issues/64011) | +|LightweightM2M-1.1-int-229 - Read-Composite Operation|:white_check_mark:|[~~#64012~~](https://github.com/zephyrproject-rtos/zephyr/issues/64012) [~~#64189~~](https://github.com/zephyrproject-rtos/zephyr/issues/64189) | +|LightweightM2M-1.1-int-230 - Write-Composite Operation|:white_check_mark:| | +|LightweightM2M-1.1-int-231 - Querying basic information in SenML JSON format|:white_check_mark:| | +|LightweightM2M-1.1-int-232 - Querying basic information in SenML CBOR format|:white_check_mark:| | +|LightweightM2M-1.1-int-233 - Setting basic information in SenML CBOR format|:white_check_mark:| | +|LightweightM2M-1.1-int-234 - Setting basic information in SenML JSON format|:white_check_mark:| | +|LightweightM2M-1.1-int-235 - Read-Composite Operation on root path|:large_orange_diamond:|Root Path is not yet supported by Leshan.| +|LightweightM2M-1.1-int-236 - Read-Composite - Partial Presence|:white_check_mark:| | +|LightweightM2M-1.1-int-237 - Read on Object without specifying Content-Type|:white_check_mark:| | +|LightweightM2M-1.1-int-241 - Executable Resource: Rebooting the device|:white_check_mark:| | +|LightweightM2M-1.1-int-256 - Write Operation Failure|:white_check_mark:| | +|LightweightM2M-1.1-int-257 - Write-Composite Operation|:white_check_mark:| | +|LightweightM2M-1.1-int-260 - Discover Command|:white_check_mark:| | +|LightweightM2M-1.1-int-261 - Write-Attribute Operation on a multiple resource|:large_orange_diamond:|Leshan don't allow writing attributes to resource instance| +|LightweightM2M-1.1-int-280 - Successful Read-Composite Operation|:white_check_mark:| | +|LightweightM2M-1.1-int-281 - Partially Successful Read-Composite Operation|:white_check_mark:| | +|LightweightM2M-1.1-int-401 - UDP Channel Security - PSK Mode |:white_check_mark:| | + +* :white_check_mark: Working OK. +* :large_orange_diamond: Feature or operation not implemented. +* :red_circle: Broken diff --git a/tests/net/lib/lwm2m/interop/pytest/leshan.py b/tests/net/lib/lwm2m/interop/pytest/leshan.py index 4d69a3a977f..ecd14b2481c 100644 --- a/tests/net/lib/lwm2m/interop/pytest/leshan.py +++ b/tests/net/lib/lwm2m/interop/pytest/leshan.py @@ -5,15 +5,16 @@ from __future__ import annotations import json -import requests import binascii +import requests +from datetime import datetime class Leshan: def __init__(self, url: str): self.api_url = url self.timeout = 10 - self.format = 'TLV' - # self.format = "SENML_CBOR" + #self.format = 'TLV' + self.format = "SENML_CBOR" try: resp = self.get('/security/clients') if not isinstance(resp, list): @@ -23,7 +24,18 @@ class Leshan: @staticmethod def handle_response(resp: requests.models.Response): - """Generic response handler for all queries""" + """ + Handle the response received from the server. + + Parameters: + - response: The response object received from the server. + + Returns: + - dict: The parsed JSON response as a dictionary. + + Raises: + - Exception: If the response indicates an error condition. + """ if resp.status_code >= 300 or resp.status_code < 200: raise RuntimeError(f'Error {resp.status_code}: {resp.text}') if len(resp.text): @@ -34,11 +46,14 @@ class Leshan: def get(self, path: str): """Send HTTP GET query""" - resp = requests.get(f'{self.api_url}{path}?timeout={self.timeout}&format={self.format}') + params = {'timeout': self.timeout} + if self.format is not None: + params['format'] = self.format + resp = requests.get(f'{self.api_url}{path}', params=params, timeout=self.timeout) return Leshan.handle_response(resp) def put_raw(self, path: str, data: str | dict | None = None, headers: dict | None = None): - resp = requests.put(f'{self.api_url}{path}', data=data, headers=headers) + resp = requests.put(f'{self.api_url}{path}', data=data, headers=headers, timeout=self.timeout) return Leshan.handle_response(resp) def put(self, path: str, data: str | dict, uri_options: str = ''): @@ -47,42 +62,249 @@ class Leshan: return self.put_raw(f'{path}?timeout={self.timeout}&format={self.format}' + uri_options, data=data, headers={'content-type': 'application/json'}) def post(self, path: str, data: str | dict | None = None): - resp = requests.post(f'{self.api_url}{path}', data=data, headers={'content-type': 'application/json'}) + if isinstance(data, dict): + data = json.dumps(data) + if data is not None: + headers={'content-type': 'application/json'} + uri_options = f'?timeout={self.timeout}&format={self.format}' + else: + headers=None + uri_options = '' + resp = requests.post(f'{self.api_url}{path}' + uri_options, data=data, headers=headers, timeout=self.timeout) return Leshan.handle_response(resp) def delete(self, path: str): - resp = requests.delete(f'{self.api_url}{path}') + resp = requests.delete(f'{self.api_url}{path}', timeout=self.timeout) return Leshan.handle_response(resp) def execute(self, endpoint: str, path: str): return self.post(f'/clients/{endpoint}/{path}') def write(self, endpoint: str, path: str, value: bool | int | str): + if len(path.split('/')) == 3: + kind = 'singleResource' + else: + kind = 'resourceInstance' + rid = path.split('/')[-1] + return self.put(f'/clients/{endpoint}/{path}', self._define_resource(rid, value, kind)) + + def update_obj_instance(self, endpoint: str, path: str, resources: dict): + data = self._define_obj_inst(path, resources) + return self.put(f'/clients/{endpoint}/{path}', data, uri_options='&replace=false') + + def replace_obj_instance(self, endpoint: str, path: str, resources: dict): + data = self._define_obj_inst(path, resources) + return self.put(f'/clients/{endpoint}/{path}', data, uri_options='&replace=true') + + def create_obj_instance(self, endpoint: str, path: str, resources: dict): + data = self._define_obj_inst(path, resources) + path = '/'.join(path.split('/')[:-1]) # Create call should not have instance ID in path + return self.post(f'/clients/{endpoint}/{path}', data) + + @classmethod + def _type_to_string(cls, value): + """ + Convert a Python value to its corresponding Leshan representation. + + Parameters: + - value: The value to be converted. + + Returns: + - str: The string representation of the value. + """ if isinstance(value, bool): - type = 'boolean' - value = "true" if value else "false" - elif isinstance(value, int): - type = 'integer' - value = str(value) - elif isinstance(value, str): - type = 'string' - value = '"' + value + '"' - id = path.split('/')[2] - return self.put(f'/clients/{endpoint}/{path}', f'{{"id":{id},"kind":"singleResource","value":{value},"type":"{type}"}}') + return 'boolean' + if isinstance(value, int): + return 'integer' + if isinstance(value, datetime): + return 'time' + return 'string' + + @classmethod + def _convert_type(cls, value): + if isinstance(value, datetime): + return int(value.timestamp()) + else: + return value + + def _define_obj_inst(self, path: str, resources: dict): + data = { + "kind": "instance", + "id": int(path.split('/')[-1]), # ID is last element of path + "resources": [] + } + for key, value in resources.items(): + if isinstance(value, dict): + kind = 'multiResource' + else: + kind = 'singleResource' + data['resources'].append(self._define_resource(key, value, kind)) + return data + + def _define_resource(self, rid, value, kind='singleResource'): + if kind in ('singleResource', 'resourceInstance'): + return { + "id": rid, + "kind": kind, + "value": self._convert_type(value), + "type": self._type_to_string(value) + } + if kind == 'multiResource': + return { + "id": rid, + "kind": kind, + "values": value, + "type": self._type_to_string(list(value.values())[0]) + } + raise RuntimeError(f'Unhandled type {kind}') + + def _decode_value(self, type, value): + """ + Decode the Leshan representation of a value back to a Python value. + """ + if type == 'BOOLEAN': + return bool(value) + if type == 'INTEGER': + return int(value) + return value + + def _decode_resource(self, content): + """ + Decode the Leshan representation of a resource back to a Python dictionary. + """ + if content['kind'] == 'singleResource' or content['kind'] == 'resourceInstance': + return {content['id']: self._decode_value(content['type'], content['value'])} + elif content['kind'] == 'multiResource': + values = {} + for riid, value in content['values'].items(): + values.update({int(riid): self._decode_value(content['type'], value)}) + return {content['id']: values} + raise RuntimeError(f'Unhandled type {content["kind"]}') + + def _decode_obj_inst(self, content): + """ + Decode the Leshan representation of an object instance back to a Python dictionary. + """ + resources = {} + for resource in content['resources']: + resources.update(self._decode_resource(resource)) + return {content['id']: resources} + + def _decode_obj(self, content): + """ + Decode the Leshan representation of an object back to a Python dictionary. + """ + instances = {} + for instance in content['instances']: + instances.update(self._decode_obj_inst(instance)) + return {content['id']: instances} def read(self, endpoint: str, path: str): resp = self.get(f'/clients/{endpoint}/{path}') if not resp['success']: return resp content = resp['content'] - if content['kind'] == 'instance': - return content['resources'] - elif content['kind'] == 'singleResource': - return content['value'] + if content['kind'] == 'obj': + return self._decode_obj(content) + elif content['kind'] == 'instance': + return self._decode_obj_inst(content) + elif content['kind'] == 'singleResource' or content['kind'] == 'resourceInstance': + return self._decode_value(content['type'], content['value']) elif content['kind'] == 'multiResource': - return content['values'] + return self._decode_resource(content) raise RuntimeError(f'Unhandled type {content["kind"]}') + def composite_read(self, endpoint: str, paths: list[str]): + paths = [path if path.startswith('/') else '/' + path for path in paths] + parameters = { + 'pathformat': self.format, + 'nodeformat': self.format, + 'timeout': self.timeout, + 'paths': ','.join(paths) + } + resp = requests.get(f'{self.api_url}/clients/{endpoint}/composite', params=parameters, timeout=self.timeout) + payload = Leshan.handle_response(resp) + if not payload['status'] == 'CONTENT(205)': + raise RuntimeError(f'No content received') + data = {} + for path, content in payload['content'].items(): + keys = [int(key) for key in path.lstrip("/").split('/')] + if len(keys) == 1: + data.update(self._decode_obj(content)) + elif len(keys) == 2: + if keys[0] not in data: + data[keys[0]] = {} + data[keys[0]].update(self._decode_obj_inst(content)) + elif len(keys) == 3: + if keys[0] not in data: + data[keys[0]] = {} + if keys[1] not in data[keys[0]]: + data[keys[0]][keys[1]] = {} + data[keys[0]][keys[1]].update(self._decode_resource(content)) + elif len(keys) == 4: + if keys[0] not in data: + data[keys[0]] = {} + if keys[1] not in data[keys[0]]: + data[keys[0]][keys[1]] = {} + if keys[2] not in data[keys[0]][keys[1]]: + data[keys[0]][keys[1]][keys[2]] = {} + data[keys[0]][keys[1]][keys[2]].update(self._decode_resource(content)) + else: + raise RuntimeError(f'Unhandled path {path}') + print(f'Requested paths: {paths}') + print(data) + return data + + + def composite_write(self, endpoint: str, resources: dict): + """ + Do LwM2m composite write operation. + + Targeted resources are defined as a dictionary with the following structure: + { + "/1/0/1": 60, + "/1/0/6": True, + "/16/0/0": { + "0": "aa", + "1": "bb", + "2": "cc", + "3": "dd" + } + } + + Objects or object instances cannot be targeted. + """ + data = { } + parameters = { + 'pathformat': self.format, + 'nodeformat': self.format, + 'timeout': self.timeout + } + for path, value in resources.items(): + path = path if path.startswith('/') else '/' + path + level = len(path.split('/')) - 1 + rid = int(path.split('/')[-1]) + if level == 3: + if isinstance(value, dict): + value = self._define_resource(rid, value, kind='multiResource') + else: + value = self._define_resource(rid, value) + elif level == 4: + value = self._define_resource(rid, value, kind='resourceInstance') + else: + raise RuntimeError(f'Unhandled path {path}') + data[path] = value + + resp = requests.put(f'{self.api_url}/clients/{endpoint}/composite', params=parameters, json=data, timeout=self.timeout) + return Leshan.handle_response(resp) + + def discover(self, endpoint: str, path: str): + resp = self.handle_response(requests.get(f'{self.api_url}/clients/{endpoint}/{path}/discover', timeout=self.timeout)) + data = {} + for obj in resp['objectLinks']: + data[obj['url']] = obj['attributes'] + return data + def create_psk_device(self, endpoint: str, passwd: str): psk = binascii.b2a_hex(passwd.encode()).decode() self.put('/security/clients/', f'{{"endpoint":"{endpoint}","tls":{{"mode":"psk","details":{{"identity":"{endpoint}","key":"{psk}"}} }} }}') @@ -90,13 +312,13 @@ class Leshan: def delete_device(self, endpoint: str): self.delete(f'/security/clients/{endpoint}') - def create_bs_device(self, endpoint: str, server_uri: str, passwd: str): - psk = binascii.b2a_hex(passwd.encode()).decode() + def create_bs_device(self, endpoint: str, server_uri: str, bs_passwd: str, passwd: str): + psk = binascii.b2a_hex(bs_passwd.encode()).decode() data = f'{{"tls":{{"mode":"psk","details":{{"identity":"{endpoint}","key":"{psk}"}}}},"endpoint":"{endpoint}"}}' self.put('/security/clients/', data) - id = str([ord(n) for n in endpoint]) + ep = str([ord(n) for n in endpoint]) key = str([ord(n) for n in passwd]) - content = '{"servers":{"0":{"binding":"U","defaultMinPeriod":1,"lifetime":86400,"notifIfDisabled":false,"shortId":1}},"security":{"1":{"bootstrapServer":false,"clientOldOffTime":1,"publicKeyOrId":' + id + ',"secretKey":' + key + ',"securityMode":"PSK","serverId":1,"serverSmsNumber":"","smsBindingKeyParam":[],"smsBindingKeySecret":[],"smsSecurityMode":"NO_SEC","uri":"'+server_uri+'"}},"oscore":{},"toDelete":["/0","/1"]}' + content = '{"servers":{"0":{"binding":"U","defaultMinPeriod":1,"lifetime":86400,"notifIfDisabled":false,"shortId":1}},"security":{"1":{"bootstrapServer":false,"clientOldOffTime":1,"publicKeyOrId":' + ep + ',"secretKey":' + key + ',"securityMode":"PSK","serverId":1,"serverSmsNumber":"","smsBindingKeyParam":[],"smsBindingKeySecret":[],"smsSecurityMode":"NO_SEC","uri":"'+server_uri+'"}},"oscore":{},"toDelete":["/0","/1"]}' self.post(f'/bootstrap/{endpoint}', content) def delete_bs_device(self, endpoint: str): diff --git a/tests/net/lib/lwm2m/interop/pytest/test_lwm2m.py b/tests/net/lib/lwm2m/interop/pytest/test_lwm2m.py index 53a205fadb9..384e3cadda5 100644 --- a/tests/net/lib/lwm2m/interop/pytest/test_lwm2m.py +++ b/tests/net/lib/lwm2m/interop/pytest/test_lwm2m.py @@ -12,6 +12,7 @@ import random import string from twister_harness import Shell +from datetime import datetime LESHAN_IP: str = '192.0.2.2' COAP_PORT: int = 5683 @@ -67,8 +68,8 @@ def verify_LightweightM2M_1_1_int_101(shell: Shell, leshan: Leshan, endpoint: st def verify_LightweightM2M_1_1_int_102(shell: Shell, leshan: Leshan, endpoint: str): logger.info("LightweightM2M-1.1-int-102 - Registration Update") lines = shell.get_filtered_output(shell.exec_command('lwm2m read 1/0/1 -u32')) - litetime = int(lines[0]) - lifetime = litetime + 10 + lifetime = int(lines[0]) + lifetime = lifetime + 10 start_time = time.time() * 1000 leshan.write(endpoint, '1/0/1', lifetime) shell._device.readlines_until(regex='.*net_lwm2m_rd_client: Update Done', timeout=5.0) @@ -95,10 +96,10 @@ def verify_LightweightM2M_1_1_int_105(shell: Shell, leshan: Leshan, endpoint: st if status["secure"]: logger.debug("Skip, requires non-secure connection") return - id = status["registrationId"] - assert id + regid = status["registrationId"] + assert regid # Fake unregister message - helperclient.delete(f'rd/{id}', timeout=0.1) + helperclient.delete(f'rd/{regid}', timeout=0.1) helperclient.stop() time.sleep(1) shell.exec_command('lwm2m update') @@ -122,7 +123,6 @@ def verify_LightweightM2M_1_1_int_108(leshan, endpoint): def verify_LightweightM2M_1_1_int_109(shell: Shell, leshan: Leshan, endpoint: str): logger.info("LightweightM2M-1.1-int-109 - Behavior in Queue Mode") - verify_LightweightM2M_1_1_int_107(shell, leshan, endpoint) logger.debug('Wait for Queue RX OFF') shell._device.readlines_until(regex='.*Queue mode RX window closed', timeout=120) # Restore previous value @@ -130,74 +130,39 @@ def verify_LightweightM2M_1_1_int_109(shell: Shell, leshan: Leshan, endpoint: st shell._device.readlines_until(regex='.*Registration update complete', timeout=10) def verify_LightweightM2M_1_1_int_201(shell: Shell, leshan: Leshan, endpoint: str): - logger.info("LightweightM2M-1.1-int-201 - Querying basic information in Plain Text format") fmt = leshan.format leshan.format = 'TEXT' - assert leshan.get(f'/clients/{endpoint}/3/0/0')['content']['value'] == 'Zephyr' - assert leshan.get(f'/clients/{endpoint}/3/0/1')['content']['value'] == 'client-1' - assert leshan.get(f'/clients/{endpoint}/3/0/2')['content']['value'] == 'serial-1' + assert leshan.read(endpoint, '3/0/0') == 'Zephyr' + assert leshan.read(endpoint, '3/0/1') == 'client-1' + assert leshan.read(endpoint, '3/0/2') == 'serial-1' leshan.format = fmt def verify_device_object(resp): ''' Verify that Device object match Configuration 3 ''' - assert resp['valid'] is True - found = 0 - for res in resp['content']['resources']: - if res['id'] == 0: - assert res['value'] == 'Zephyr' - found += 1 - elif res['id'] == 1: - assert res['value'] == 'client-1' - found += 1 - elif res['id'] == 2: - assert res['value'] == 'serial-1' - found += 1 - elif res['id'] == 3: - assert res['value'] == '1.2.3' - found += 1 - elif res['id'] == 11: - assert res['kind'] == 'multiResource' - assert res['values']['0'] == '0' - found += 1 - elif res['id'] == 16: - assert res['value'] == 'U' - found += 1 - assert found == 6 + assert resp[0][0] == 'Zephyr' + assert resp[0][1] == 'client-1' + assert resp[0][2] == 'serial-1' + assert resp[0][3] == '1.2.3' + assert resp[0][11][0] == 0 + assert resp[0][16] == 'U' def verify_server_object(obj): ''' Verify that server object match Configuration 3 ''' - found = 0 - for res in obj['resources']: - if res['id'] == 0: - assert res['value'] == '1' - found += 1 - elif res['id'] == 1: - assert res['value'] == '86400' - found += 1 - elif res['id'] == 2: - assert res['value'] == '1' - found += 1 - elif res['id'] == 3: - assert res['value'] == '10' - found += 1 - elif res['id'] == 5: - assert res['value'] == '86400' - found += 1 - elif res['id'] == 6: - assert res['value'] is False - found += 1 - elif res['id'] == 7: - assert res['value'] == 'U' - found += 1 - assert found == 7 + assert obj[0][0] == 1 + assert obj[0][1] == 86400 + assert obj[0][2] == 1 + assert obj[0][3] == 10 + assert obj[0][5] == 86400 + assert obj[0][6] is False + assert obj[0][7] == 'U' def verify_LightweightM2M_1_1_int_203(shell: Shell, leshan: Leshan, endpoint: str): shell.exec_command('lwm2m update') logger.info('LightweightM2M-1.1-int-203 - Querying basic information in TLV format') fmt = leshan.format leshan.format = 'TLV' - resp = leshan.get(f'/clients/{endpoint}/3/0') + resp = leshan.read(endpoint,'3/0') verify_device_object(resp) leshan.format = fmt @@ -206,7 +171,7 @@ def verify_LightweightM2M_1_1_int_204(shell: Shell, leshan: Leshan, endpoint: st logger.info('LightweightM2M-1.1-int-204 - Querying basic information in JSON format') fmt = leshan.format leshan.format = 'JSON' - resp = leshan.get(f'/clients/{endpoint}/3/0') + resp = leshan.read(endpoint, '3/0') verify_device_object(resp) leshan.format = fmt @@ -217,15 +182,15 @@ def verify_LightweightM2M_1_1_int_205(shell: Shell, leshan: Leshan, endpoint: st leshan.write(endpoint, '1/0/2', 101) leshan.write(endpoint, '1/0/3', 1010) leshan.write(endpoint, '1/0/5', 2000) - assert leshan.read(endpoint, '1/0/2') == '101' - assert leshan.read(endpoint, '1/0/3') == '1010' - assert leshan.read(endpoint, '1/0/5') == '2000' + assert leshan.read(endpoint, '1/0/2') == 101 + assert leshan.read(endpoint, '1/0/3') == 1010 + assert leshan.read(endpoint, '1/0/5') == 2000 leshan.write(endpoint, '1/0/2', 1) leshan.write(endpoint, '1/0/3', 10) leshan.write(endpoint, '1/0/5', 86400) - assert leshan.read(endpoint, '1/0/2') == '1' - assert leshan.read(endpoint, '1/0/3') == '10' - assert leshan.read(endpoint, '1/0/5') == '86400' + assert leshan.read(endpoint, '1/0/2') == 1 + assert leshan.read(endpoint, '1/0/3') == 10 + assert leshan.read(endpoint, '1/0/5') == 86400 leshan.format = fmt def verify_LightweightM2M_1_1_int_211(shell: Shell, leshan: Leshan, endpoint: str): @@ -233,8 +198,8 @@ def verify_LightweightM2M_1_1_int_211(shell: Shell, leshan: Leshan, endpoint: st fmt = leshan.format leshan.format = 'CBOR' lines = shell.get_filtered_output(shell.exec_command('lwm2m read 1/0/0 -u16')) - id = lines[0] - assert leshan.read(endpoint, '1/0/0') == id + short_id = int(lines[0]) + assert leshan.read(endpoint, '1/0/0') == short_id assert leshan.read(endpoint, '1/0/6') is False assert leshan.read(endpoint, '1/0/7') == 'U' leshan.format = fmt @@ -246,8 +211,8 @@ def verify_LightweightM2M_1_1_int_212(shell: Shell, leshan: Leshan, endpoint: st leshan.write(endpoint, '1/0/2', 101) leshan.write(endpoint, '1/0/3', 1010) leshan.write(endpoint, '1/0/6', True) - assert leshan.read(endpoint, '1/0/2') == '101' - assert leshan.read(endpoint, '1/0/3') == '1010' + assert leshan.read(endpoint, '1/0/2') == 101 + assert leshan.read(endpoint, '1/0/3') == 1010 assert leshan.read(endpoint, '1/0/6') is True leshan.write(endpoint, '1/0/2', 1) leshan.write(endpoint, '1/0/3', 10) @@ -257,71 +222,26 @@ def verify_LightweightM2M_1_1_int_212(shell: Shell, leshan: Leshan, endpoint: st def verify_setting_basic_in_format(shell, leshan, endpoint, format): fmt = leshan.format leshan.format = format - server_obj = leshan.get(f'/clients/{endpoint}/1/0')['content'] + server_obj = leshan.read(endpoint, '1/0') verify_server_object(server_obj) # Remove Read-Only resources, so we don't end up writing those - for res in server_obj['resources']: - if res['id'] in (0, 11, 12): - server_obj['resources'].remove(res) - data = '''{ - "kind": "instance", - "id": 0, - "resources": [ - { - "id": 2, - "kind": "singleResource", - "value": "101", - "type": "integer" - }, - { - "id": 3, - "kind": "singleResource", - "value": "1010", - "type": "integer" - }, - { - "id": 5, - "kind": "singleResource", - "value": "2000", - "type": "integer" - }, - { - "id": 6, - "kind": "singleResource", - "value": true, - "type": "boolean" - }, - { - "id": 7, - "kind": "singleResource", - "value": "U", - "type": "string" - } - ] - }''' - assert leshan.put(f'/clients/{endpoint}/1/0', data, uri_options = '&replace=false')['status'] == 'CHANGED(204)' - resp = leshan.get(f'/clients/{endpoint}/1/0') - assert resp['valid'] is True - found = 0 - for res in resp['content']['resources']: - if res['id'] == 2: - assert res['value'] == '101' - found += 1 - elif res['id'] == 3: - assert res['value'] == '1010' - found += 1 - elif res['id'] == 5: - assert res['value'] == '2000' - found += 1 - elif res['id'] == 6: - assert res['value'] is True - found += 1 - elif res['id'] == 7: - assert res['value'] == 'U' - found += 1 - assert found == 5 - assert leshan.put(f'/clients/{endpoint}/1/0', data = server_obj, uri_options = '&replace=true')['status'] == 'CHANGED(204)' - server_obj = leshan.get(f'/clients/{endpoint}/1/0')['content'] + del server_obj[0][0] + data = { + 2: 101, + 3: 1010, + 5: 2000, + 6: True, + 7: 'U' + } + assert leshan.update_obj_instance(endpoint, '1/0', data)['status'] == 'CHANGED(204)' + resp = leshan.read(endpoint, '1/0') + assert resp[0][2] == 101 + assert resp[0][3] == 1010 + assert resp[0][5] == 2000 + assert resp[0][6] is True + assert resp[0][7] == 'U' + assert leshan.replace_obj_instance(endpoint, '1/0', server_obj[0])['status'] == 'CHANGED(204)' + server_obj = leshan.read(endpoint, '1/0') verify_server_object(server_obj) leshan.format = fmt @@ -339,6 +259,297 @@ def verify_LightweightM2M_1_1_int_221(shell: Shell, leshan: Leshan, endpoint: st assert leshan.write(endpoint, '0/0/0', 'coap://localhost')['status'] == 'UNAUTHORIZED(401)' assert leshan.put_raw(f'/clients/{endpoint}/0/attributes?pmin=10')['status'] == 'UNAUTHORIZED(401)' +def verify_LightweightM2M_1_1_int_222(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-222 - Read on Object") + resp = leshan.read(endpoint, '1') + assert len(resp) == 1 + assert len(resp[1][0]) == 9 + resp = leshan.read(endpoint, '3') + assert len(resp) == 1 + assert len(resp[3]) == 1 + assert len(resp[3][0]) == 15 + assert resp[3][0][0] == 'Zephyr' + +def verify_LightweightM2M_1_1_int_223(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-223 - Read on Object Instance") + resp = leshan.read(endpoint, '1/0') + assert len(resp[0]) == 9 + resp = leshan.read(endpoint, '3/0') + assert len(resp[0]) == 15 + assert resp[0][0] == 'Zephyr' + +def verify_LightweightM2M_1_1_int_224(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-224 - Read on Resource") + assert leshan.read(endpoint, '1/0/0') == 1 + assert leshan.read(endpoint, '1/0/1') == 86400 + assert leshan.read(endpoint, '1/0/6') is False + assert leshan.read(endpoint, '1/0/7') == 'U' + +def verify_LightweightM2M_1_1_int_225(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-225 - Read on Resource Instance") + assert leshan.read(endpoint, '3/0/11/0') == 0 + +def verify_LightweightM2M_1_1_int_226(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-226 - Write (Partial Update) on Object Instance") + lines = shell.get_filtered_output(shell.exec_command('lwm2m read 1/0/1 -u32')) + lifetime = int(lines[0]) + resources = { + 1: 60, + 6: True + } + assert leshan.update_obj_instance(endpoint, '1/0', resources)['status'] == 'CHANGED(204)' + assert leshan.read(endpoint, '1/0/1') == 60 + assert leshan.read(endpoint, '1/0/6') is True + resources = { + 1: lifetime, + 6: False + } + assert leshan.update_obj_instance(endpoint, '1/0', resources)['status'] == 'CHANGED(204)' + +def verify_LightweightM2M_1_1_int_227(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-227 - Write (replace) on Resource") + lines = shell.get_filtered_output(shell.exec_command('lwm2m read 1/0/1 -u32')) + lifetime = int(lines[0]) + assert leshan.write(endpoint, '1/0/1', int(63))['status'] == 'CHANGED(204)' + shell._device.readlines_until(regex='.*net_lwm2m_rd_client: Update Done', timeout=5.0) + latest = leshan.get(f'/clients/{endpoint}') + assert latest["lifetime"] == 63 + assert leshan.read(endpoint, '1/0/1') == 63 + assert leshan.write(endpoint, '1/0/1', lifetime)['status'] == 'CHANGED(204)' + +def verify_LightweightM2M_1_1_int_228(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-228 - Write on Resource Instance") + resources = { + 0: {0: 'a', 1: 'b'} + } + assert leshan.create_obj_instance(endpoint, '16/0', resources)['status'] == 'CREATED(201)' + shell._device.readlines_until(regex='.*net_lwm2m_rd_client: Update Done', timeout=5.0) + assert leshan.write(endpoint, '16/0/0/0', 'test')['status'] == 'CHANGED(204)' + assert leshan.read(endpoint, '16/0/0/0') == 'test' + +def verify_LightweightM2M_1_1_int_229(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-229 - Read-Composite Operation") + old_fmt = leshan.format + for fmt in ['SENML_JSON', 'SENML_CBOR']: + leshan.format = fmt + resp = leshan.composite_read(endpoint, ['/3', '1/0']) + assert len(resp.keys()) == 2 + assert resp[3] is not None + assert resp[1][0] is not None + assert len(resp[3][0]) == 15 + assert len(resp[1][0]) == 9 + + resp = leshan.composite_read(endpoint, ['1/0/1', '/3/0/11/0']) + logger.debug(resp) + assert len(resp.keys()) == 2 + assert resp[1][0][1] is not None + assert resp[3][0][11][0] is not None + leshan.format = old_fmt + +def verify_LightweightM2M_1_1_int_230(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-230 - Write-Composite Operation") + resources = { + "/1/0/1": 60, + "/1/0/6": True, + "/16/0/0": { + "0": "aa", + "1": "bb", + "2": "cc", + "3": "dd" + } + } + old_fmt = leshan.format + for fmt in ['SENML_JSON', 'SENML_CBOR']: + leshan.format = fmt + assert leshan.composite_write(endpoint, resources)['status'] == 'CHANGED(204)' + resp = leshan.read(endpoint, '1/0') + assert resp[0][1] == 60 + assert resp[0][6] is True + resp = leshan.read(endpoint, '16/0/0') + assert resp[0][0] == "aa" + assert resp[0][1] == "bb" + assert resp[0][2] == "cc" + assert resp[0][3] == "dd" + # Return to default + shell.exec_command('lwm2m write /1/0/1 -u32 86400') + shell.exec_command('lwm2m write /1/0/6 -u8 0') + leshan.format = old_fmt + +def query_basic_in_senml(leshan: Leshan, endpoint: str, fmt: str): + old_fmt = leshan.format + leshan.format = fmt + verify_server_object(leshan.read(endpoint, '1')[1]) + verify_device_object(leshan.read(endpoint, '3/0')) + assert leshan.read(endpoint, '3/0/16') == 'U' + assert leshan.read(endpoint, '3/0/11/0') == 0 + leshan.format = old_fmt + +def verify_LightweightM2M_1_1_int_231(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-231 - Querying basic information in SenML JSON format") + query_basic_in_senml(leshan, endpoint, 'SENML_JSON') + +def verify_LightweightM2M_1_1_int_232(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-232 - Querying basic information in SenML CBOR format") + query_basic_in_senml(leshan, endpoint, 'SENML_CBOR') + +def setting_basic_senml(shell: Shell, leshan: Leshan, endpoint: str, fmt: str): + old_fmt = leshan.format + leshan.format = fmt + resources = { + 1: 61, + 6: True, + } + assert leshan.update_obj_instance(endpoint, '1/0', resources)['status'] == 'CHANGED(204)' + srv_obj = leshan.read(endpoint, '1/0') + assert srv_obj[0][1] == 61 + assert srv_obj[0][6] is True + assert leshan.write(endpoint, '16/0/0/0', 'test_value')['status'] == 'CHANGED(204)' + portfolio = leshan.read(endpoint, '16') + assert portfolio[16][0][0][0] == 'test_value' + assert leshan.write(endpoint, '1/0/1', 63)['status'] == 'CHANGED(204)' + assert leshan.read(endpoint, '1/0/1') == 63 + shell.exec_command('lwm2m write /1/0/1 -u32 86400') + shell.exec_command('lwm2m write /1/0/6 -u8 0') + leshan.format = old_fmt + +def verify_LightweightM2M_1_1_int_233(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-233 - Setting basic information in SenML CBOR format") + setting_basic_senml(shell, leshan, endpoint, 'SENML_CBOR') + +def verify_LightweightM2M_1_1_int_234(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-234 - Setting basic information in SenML JSON format") + setting_basic_senml(shell, leshan, endpoint, 'SENML_JSON') + +def verify_LightweightM2M_1_1_int_235(): + """LightweightM2M-1.1-int-235 - Read-Composite Operation on root path""" + # Unsupported. Leshan does not allow this. + +def verify_LightweightM2M_1_1_int_236(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-236 - Read-Composite - Partial Presence") + resp = leshan.composite_read(endpoint, ['1/0', '/3/0/11/0', '/3339/0/5522', '/3353/0/6030']) + assert resp[1][0][1] is not None + assert resp[3][0][11][0] is not None + assert len(resp) == 2 + +def verify_LightweightM2M_1_1_int_237(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-237 - Read on Object without specifying Content-Type") + old_fmt = leshan.format + leshan.format = None + assert leshan.read(endpoint, '1')[1][0][1] is not None + assert leshan.read(endpoint, '3')[3][0][0] == 'Zephyr' + leshan.format = old_fmt + +def verify_LightweightM2M_1_1_int_241(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-241 - Executable Resource: Rebooting the device") + leshan.execute(endpoint, '3/0/4') + shell._device.readlines_until(regex='.*DEVICE: REBOOT', timeout=5.0) + shell._device.readlines_until(regex='.*rd_client_event: Disconnected', timeout=5.0) + shell.exec_command(f'lwm2m start {endpoint} -b 0') + shell._device.readlines_until(regex='.*Registration Done', timeout=5.0) + assert leshan.get(f'/clients/{endpoint}') + +def verify_LightweightM2M_1_1_int_256(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-256 - Write Operation Failure") + lines = shell.get_filtered_output(shell.exec_command('lwm2m read 1/0/0 -u16')) + short_id = int(lines[0]) + assert leshan.write(endpoint, '1/0/0', 123)['status'] == 'METHOD_NOT_ALLOWED(405)' + assert leshan.read(endpoint, '1/0/0') == short_id + +def verify_LightweightM2M_1_1_int_257(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-257 - Write-Composite Operation") + resources = { + "/1/0/2": 102, + "/1/0/6": True, + "/3/0/13": datetime.fromtimestamp(0) + } + old_fmt = leshan.format + for fmt in ['SENML_JSON', 'SENML_CBOR']: + leshan.format = fmt + assert leshan.composite_write(endpoint, resources)['status'] == 'CHANGED(204)' + assert leshan.read(endpoint, '1/0/2') == 102 + assert leshan.read(endpoint, '1/0/6') is True + # Cannot verify the /3/0/13, it is a timestamp that moves forward. + + # Return to default + shell.exec_command(f'lwm2m write /3/0/13 -u32 {int(datetime.now().timestamp())}') + shell.exec_command('lwm2m write /1/0/6 -u8 0') + shell.exec_command('lwm2m write /1/0/2 -u32 1') + leshan.format = old_fmt + +def verify_LightweightM2M_1_1_int_260(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-260 - Discover Command") + resp = leshan.discover(endpoint, '3') + expected_keys = ['/3', '/3/0', '/3/0/1', '/3/0/2', '/3/0/3', '/3/0/4', '/3/0/6', '/3/0/7', '/3/0/8', '/3/0/9', '/3/0/11', '/3/0/16'] + missing_keys = [key for key in expected_keys if key not in resp.keys()] + assert len(missing_keys) == 0 + assert leshan.put_raw(f'/clients/{endpoint}/3/attributes?pmin=10')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/attributes?pmax=200')['status'] == 'CHANGED(204)' + resp = leshan.discover(endpoint, '3/0') + assert int(resp['/3/0/6']['dim']) == 2 + assert int(resp['/3/0/7']['dim']) == 2 + assert int(resp['/3/0/8']['dim']) == 2 + assert leshan.put_raw(f'/clients/{endpoint}/3/0/7/attributes?lt=1')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/7/attributes?gt=6')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/7/attributes?st=1')['status'] == 'CHANGED(204)' + resp = leshan.discover(endpoint, '3/0') + expected_keys = ['/3/0', '/3/0/1', '/3/0/2', '/3/0/3', '/3/0/4', '/3/0/6', '/3/0/7', '/3/0/8', '/3/0/9', '/3/0/11', '/3/0/16'] + missing_keys = [key for key in expected_keys if key not in resp.keys()] + assert len(missing_keys) == 0 + assert int(resp['/3/0/7']['dim']) == 2 + assert float(resp['/3/0/7']['lt']) == 1.0 + assert float(resp['/3/0/7']['gt']) == 6.0 + assert float(resp['/3/0/7']['st']) == 1.0 + resp = leshan.discover(endpoint, '3/0/7') + expected_keys = ['/3/0/7', '/3/0/7/0', '/3/0/7/1'] + missing_keys = [key for key in expected_keys if key not in resp.keys()] + assert len(missing_keys) == 0 + assert len(resp) == len(expected_keys) + +def verify_LightweightM2M_1_1_int_261(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-261 - Write-Attribute Operation on a multiple resource") + resp = leshan.discover(endpoint, '3/0/11') + logger.debug(resp) + expected_keys = ['/3/0/11', '/3/0/11/0'] + missing_keys = [key for key in expected_keys if key not in resp.keys()] + assert len(missing_keys) == 0 + assert len(resp) == len(expected_keys) + assert int(resp['/3/0/11']['dim']) == 1 + assert leshan.put_raw(f'/clients/{endpoint}/3/attributes?pmin=10')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/attributes?pmax=200')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/attributes?pmax=320')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/11/0/attributes?pmax=100')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/11/0/attributes?epmin=1')['status'] == 'CHANGED(204)' + assert leshan.put_raw(f'/clients/{endpoint}/3/0/11/0/attributes?epmax=20')['status'] == 'CHANGED(204)' + resp = leshan.discover(endpoint, '3/0/11') + logger.debug(resp) + assert int(resp['/3/0/11']['pmin']) == 10 + assert int(resp['/3/0/11']['pmax']) == 320 + assert int(resp['/3/0/11/0']['pmax']) == 100 + assert int(resp['/3/0/11/0']['epmin']) == 1 + assert int(resp['/3/0/11/0']['epmax']) == 20 + +def verify_LightweightM2M_1_1_int_280(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-280 - Successful Read-Composite Operation") + resp = leshan.composite_read(endpoint, ['/3/0/16', '/3/0/11/0', '/1/0']) + logger.debug(resp) + assert len(resp) == 2 + assert len(resp[3]) == 1 + assert len(resp[3][0]) == 2 # No extra resources + assert resp[3][0][11][0] == 0 + assert resp[3][0][16] == 'U' + assert resp[1][0][0] == 1 + assert resp[1][0][1] == 86400 + assert resp[1][0][6] is False + assert resp[1][0][7] == 'U' + +def verify_LightweightM2M_1_1_int_281(shell: Shell, leshan: Leshan, endpoint: str): + logger.info("LightweightM2M-1.1-int-281 - Partially Successful Read-Composite Operation") + resp = leshan.composite_read(endpoint, ['/1/0/1', '/1/0/7', '/1/0/8']) + assert len(resp) == 1 + assert len(resp[1][0]) == 2 # /1/0/8 should not be there + assert resp[1][0][1] == 86400 + assert resp[1][0][7] == 'U' + def verify_LightweightM2M_1_1_int_401(shell: Shell, leshan: Leshan, endpoint: str): logger.info("LightweightM2M-1.1-int-401 - UDP Channel Security - Pre-shared Key Mode") lines = shell.get_filtered_output(shell.exec_command('lwm2m read 0/0/0 -s')) @@ -354,11 +565,15 @@ def test_lwm2m_bootstrap_psk(shell: Shell, leshan, leshan_bootstrap): try: # Generate randon device id and password (PSK key) endpoint = 'client_' + binascii.b2a_hex(os.urandom(1)).decode() + bs_passwd = ''.join(random.choice(string.ascii_lowercase) for i in range(16)) passwd = ''.join(random.choice(string.ascii_lowercase) for i in range(16)) + logger.debug('Endpoint: %s', endpoint) + logger.debug('Boostrap PSK: %s', binascii.b2a_hex(bs_passwd.encode()).decode()) + logger.debug('PSK: %s', binascii.b2a_hex(passwd.encode()).decode()) # Create device entries in Leshan and Bootstrap server - leshan_bootstrap.create_bs_device(endpoint, f'coaps://{LESHAN_IP}:{COAPS_PORT}', passwd) + leshan_bootstrap.create_bs_device(endpoint, f'coaps://{LESHAN_IP}:{COAPS_PORT}', bs_passwd, passwd) leshan.create_psk_device(endpoint, passwd) # Allow engine to start & stop once. @@ -373,7 +588,7 @@ def test_lwm2m_bootstrap_psk(shell: Shell, leshan, leshan_bootstrap): shell.exec_command('lwm2m write 0/0/1 -b 1') shell.exec_command('lwm2m write 0/0/2 -u8 0') shell.exec_command(f'lwm2m write 0/0/3 -s {endpoint}') - shell.exec_command(f'lwm2m write 0/0/5 -s {passwd}') + shell.exec_command(f'lwm2m write 0/0/5 -s {bs_passwd}') shell.exec_command(f'lwm2m start {endpoint} -b 1') @@ -403,6 +618,30 @@ def test_lwm2m_bootstrap_psk(shell: Shell, leshan, leshan_bootstrap): verify_LightweightM2M_1_1_int_211(shell, leshan, endpoint) verify_LightweightM2M_1_1_int_212(shell, leshan, endpoint) verify_LightweightM2M_1_1_int_215(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_220(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_221(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_222(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_223(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_224(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_225(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_226(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_227(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_228(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_229(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_230(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_231(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_232(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_233(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_234(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_236(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_237(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_241(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_256(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_257(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_260(shell, leshan, endpoint) + # skip, not supported in Leshan, verify_LightweightM2M_1_1_int_261(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_280(shell, leshan, endpoint) + verify_LightweightM2M_1_1_int_281(shell, leshan, endpoint) shell.exec_command('lwm2m stop') shell._device.readlines_until(regex=r'.*Deregistration success', timeout=10.0) @@ -413,7 +652,6 @@ def test_lwm2m_bootstrap_psk(shell: Shell, leshan, leshan_bootstrap): leshan.delete_device(endpoint) leshan_bootstrap.delete_bs_device(endpoint) - def test_lwm2m_nosecure(shell: Shell, leshan, helperclient): # Allow engine to start & stop once. @@ -438,9 +676,6 @@ def test_lwm2m_nosecure(shell: Shell, leshan, helperclient): verify_LightweightM2M_1_1_int_101(shell, leshan, endpoint) verify_LightweightM2M_1_1_int_105(shell, leshan, endpoint, helperclient) # needs no-security - verify_LightweightM2M_1_1_int_215(shell, leshan, endpoint) - verify_LightweightM2M_1_1_int_220(shell, leshan, endpoint) - verify_LightweightM2M_1_1_int_221(shell, leshan, endpoint) # All done shell.exec_command('lwm2m stop')