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:
parent
1506a43705
commit
e01657c269
3 changed files with 711 additions and 186 deletions
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue