tests: lwm2m: Add interoperability tests 222 - 281

Add remaining test cases from
Device management & Service Enablement Interface

* LightweightM2M-1.1-int-222 – Read on Object
* LightweightM2M-1.1-int-223 – Read on Object Instance
* LightweightM2M-1.1-int-224 – Read on Resource
* LightweightM2M-1.1-int-225 – Read on Resource Instance
* LightweightM2M-1.1-int-226 – Write (Partial Update) on
  Object Instance
* LightweightM2M-1.1-int-227 - Write (replace) on Resource
* LightweightM2M-1.1-int-228 - Write on Resource Instance
* LightweightM2M-1.1-int-229 - Read-Composite Operation
* LightweightM2M-1.1-int-230 - Write-Composite Operation
* LightweightM2M-1.1-int-231 - Querying basic information
  in SenML JSON format
* LightweightM2M-1.1-int-232 - Querying basic information
  in SenML CBOR format
* LightweightM2M-1.1-int-233 - Setting basic information
  in SenML CBOR format
* LightweightM2M-1.1-int-234 - Setting basic information
  in SenML JSON format
* LightweightM2M-1.1-int-235 - Read-Composite Operation on root path
* LightweightM2M-1.1-int-236 - Read-Composite - Partial Presence
* LightweightM2M-1.1-int-237 - Read on Object without specifying
  Content-Type
* LightweightM2M-1.1-int-241 - Executable Resource: Rebooting the device
* LightweightM2M-1.1-int-256 - Write Operation Failure
* LightweightM2M-1.1-int-257 - Write-Composite Operation
* LightweightM2M-1.1-int-260 - Discover Command
* LightweightM2M-1.1-int-261 - Write-Attribute Operation on a multiple
  resource
* LightweightM2M-1.1-int-280 - Successful Read-Composite Operation
* LightweightM2M-1.1-int-281 - Partially Successful Read-Composite
  Operation

Modify tests binary
* Allow CoAP payload of 1211 bytes.
* Allow outgoing messages to use block-wise if they are bigger.
* Use 4kB encode buffer for outgoing messages.
* Check configured heap and stack sizes.
* Limit number of interface addresses, sockets and contexts

Qemu-cortex-m3 platform ran out of RAM, so reconfigure it to use
* packet size of 256 bytes.
* encode buffer of 2kB
This exercises the block-transferring on some of the test cases.

Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
This commit is contained in:
Seppo Takalo 2023-10-13 16:34:49 +03:00 committed by Carles Cufí
commit e01657c269
3 changed files with 711 additions and 186 deletions

View file

@ -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

View file

@ -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):

View file

@ -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')