Skip to content

Commit

Permalink
feat: implement new CoAP vocabulary terms
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Jan 23, 2023
1 parent b68e902 commit 0b83b53
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 57 deletions.
39 changes: 22 additions & 17 deletions lib/src/binding_coap/coap_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ class CoapClient extends ProtocolClient {
Content? content,
coap.CoapMediaType? format,
coap.CoapMediaType? accept,
int? block1Size,
int? block2Size,
coap.BlockSize? block1Size,
coap.BlockSize? block2Size,
}) async {
final payload = Uint8Buffer();
if (content != null) {
Expand All @@ -115,6 +115,14 @@ class CoapClient extends ProtocolClient {
..accept = accept
..contentFormat = format;

if (block1Size != null) {
request.block1 = coap.Block1Option.fromParts(0, block1Size);
}

if (block2Size != null) {
request.block2 = coap.Block2Option.fromParts(0, block2Size);
}

if (uri.query.isNotEmpty) {
request.uriQuery = uri.query;
}
Expand All @@ -127,16 +135,17 @@ class CoapClient extends ProtocolClient {
OperationType operationType, [
Content? content,
]) async {
final requestMethod =
CoapRequestMethod.fromForm(form) ?? operationType.requestMethod;
final requestMethod = form.method ?? operationType.requestMethod;
final code = requestMethod.code;

return _sendRequest(
form.resolvedHref,
code,
content: content,
format: form.format,
format: form.contentFormat,
accept: form.accept,
block1Size: form.block1Size,
block2Size: form.block2Size,
form: form,
);
}
Expand All @@ -150,8 +159,8 @@ class CoapClient extends ProtocolClient {
required Form? form,
coap.CoapMediaType? format,
coap.CoapMediaType? accept,
int? block1Size,
int? block2Size,
coap.BlockSize? block1Size,
coap.BlockSize? block2Size,
coap.CoapMulticastResponseHandler? multicastResponseHandler,
}) async {
final coapClient = coap.CoapClient(
Expand Down Expand Up @@ -204,8 +213,8 @@ class CoapClient extends ProtocolClient {
required Form? form,
coap.CoapMediaType? format,
coap.CoapMediaType? accept,
int? block1Size,
int? block2Size,
coap.BlockSize? block1Size,
coap.BlockSize? block2Size,
coap.CoapMulticastResponseHandler? multicastResponseHandler,
}) async {
final responseContent = await _sendRequest(
Expand All @@ -226,15 +235,14 @@ class CoapClient extends ProtocolClient {
Future<AuthServerRequestCreationHint?> _obtainCreationHintFromResourceServer(
Form form,
) async {
final requestMethod =
(CoapRequestMethod.fromForm(form) ?? CoapRequestMethod.get).code;
final requestMethod = (form.method ?? CoapRequestMethod.get).code;

final creationHintUri = form.resolvedHref.replace(scheme: 'coap');

final request = await _createRequest(
requestMethod,
creationHintUri,
format: form.format,
format: form.contentFormat,
accept: form.accept,
);

Expand Down Expand Up @@ -415,13 +423,10 @@ class CoapClient extends ProtocolClient {
next(response.content);
}

final requestMethod =
(CoapRequestMethod.fromForm(form) ?? CoapRequestMethod.get).code;

final request = await _createRequest(
requestMethod,
(form.method ?? CoapRequestMethod.get).code,
form.resolvedHref,
format: form.format,
format: form.contentFormat,
accept: form.accept,
);

Expand Down
18 changes: 2 additions & 16 deletions lib/src/binding_coap/coap_definitions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import 'dart:collection';
import 'package:coap/coap.dart';
import 'package:curie/curie.dart';

import '../definitions/form.dart';

/// [PrefixMapping] for expanding CoAP Vocabulary terms from compact IRIs.
final coapPrefixMapping =
PrefixMapping(defaultPrefixValue: 'http://www.example.org/coap-binding#');
Expand Down Expand Up @@ -48,21 +46,9 @@ enum CoapRequestMethod {
values.map((e) => MapEntry(e.code.description, e)),
);

static CoapRequestMethod? _fromString(String stringValue) =>
/// Generates a [CoapRequestMethod] from a [stringValue].
static CoapRequestMethod? fromString(String stringValue) =>
_registry[stringValue];

/// Determines the [CoapRequestMethod] to use based on a given [form].
static CoapRequestMethod? fromForm(Form form) {
final curieString =
coapPrefixMapping.expandCurie(Curie(reference: 'method'));
final dynamic formDefinition = form.additionalFields[curieString];

if (formDefinition is! String) {
return null;
}

return CoapRequestMethod._fromString(formDefinition);
}
}

/// Enumeration of available CoAP subprotocols.
Expand Down
101 changes: 77 additions & 24 deletions lib/src/binding_coap/coap_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import 'package:coap/coap.dart';
import 'package:dcaf/dcaf.dart';

import '../core/content.dart';
import '../definitions/expected_response.dart';
import '../definitions/form.dart';
import '../definitions/operation_type.dart';
import '../definitions/security/ace_security_scheme.dart';
import '../definitions/security/auto_security_scheme.dart';
import '../definitions/security/psk_security_scheme.dart';
import '../definitions/validation/validation_exception.dart';
import 'coap_binding_exception.dart';
import 'coap_definitions.dart';

const _validBlockwiseValues = [16, 32, 64, 128, 256, 512, 1024];

/// Extension which makes it easier to handle [Uri]s containing
/// [InternetAddress]es.
extension InternetAddressMethods on Uri {
Expand All @@ -27,6 +27,17 @@ extension InternetAddressMethods on Uri {

/// CoAP-specific extensions for the [Form] class.
extension CoapFormExtension on Form {
T? _obtainVocabularyTerm<T>(String vocabularyTerm) {
final curieString = coapPrefixMapping.expandCurieString(vocabularyTerm);
final formDefinition = additionalFields[curieString];

if (formDefinition is T) {
return formDefinition;
}

return null;
}

/// Determines if this [Form] supports the [PskSecurityScheme].
bool get usesPskScheme =>
securityDefinitions.whereType<PskSecurityScheme>().isNotEmpty;
Expand All @@ -44,55 +55,97 @@ extension CoapFormExtension on Form {
return null;
}

CoapMediaType _determineContentFormat(String contentType, String? encoding) {
return CoapMediaType.parse(contentType, encoding) ??
CoapMediaType.applicationJson;
}

/// The Content-Format for CoAP request and response payloads.
CoapMediaType get format {
return _determineContentFormat(contentType, contentCoding);
CoapMediaType get contentFormat {
final formDefinition = _obtainVocabularyTerm<int>('contentFormat');
final contentFormat = CoapMediaType.fromIntValue(formDefinition ?? -1);

return contentFormat ??
CoapMediaType.parse(contentType, contentCoding) ??
CoapMediaType.applicationJson;
}

/// The Content-Format for the Accept option CoAP request and response
/// payloads.
CoapMediaType get accept {
// TODO: The algorithm for accept needs to be adjusted
return _determineContentFormat(contentType, contentCoding);
CoapMediaType? get accept {
final formDefinition = _obtainVocabularyTerm<int>('accept');
return CoapMediaType.fromIntValue(formDefinition ?? -1);
}

int? _determineBlockSize(String fieldName) {
const blockwiseVocabularyName = 'blockwise';
final curieString =
coapPrefixMapping.expandCurieString(blockwiseVocabularyName);
final dynamic formDefinition = additionalFields[curieString];
BlockSize? _determineBlockSize(String fieldName) {
final blockwiseParameters =
_obtainVocabularyTerm<Map<String, dynamic>>('blockwise');

if (formDefinition is! Map<String, dynamic>) {
if (blockwiseParameters == null) {
return null;
}

final blockwiseParameterName =
coapPrefixMapping.expandCurieString(fieldName);
final dynamic value = formDefinition[blockwiseParameterName];
final dynamic value = blockwiseParameters[blockwiseParameterName];

if (value is int && !_validBlockwiseValues.contains(value)) {
return value;
if (value is! int) {
return null;
}

return null;
// FIXME: Should not throw an ArgumentError
try {
return BlockSize.fromDecodedValue(value);
// ignore: avoid_catching_errors
} on ArgumentError {
throw ValidationException(
'Encountered invalid blocksize $value in CoAP form',
);
}
}

/// Indicates the Block2 size preferred by a server.
int? get block2Size => _determineBlockSize('block2SZX');
BlockSize? get block2Size => _determineBlockSize('block2SZX');

/// Indicates the Block1 size preferred by a server.
int? get block1Size => _determineBlockSize('block1SZX');
BlockSize? get block1Size => _determineBlockSize('block1SZX');

// TODO: Consider default method
/// Indicates the [CoapRequestMethod] contained in this [Form].
CoapRequestMethod? get method {
final methodDefinition = _obtainVocabularyTerm<String>('method');

if (methodDefinition == null) {
return null;
}

return CoapRequestMethod.fromString(methodDefinition);
}

/// Gets a list of all defined [AceSecurityScheme]s for this form.
List<AceSecurityScheme> get aceSecuritySchemes =>
securityDefinitions.whereType<AceSecurityScheme>().toList();
}

/// CoAP-specific extensions for the [ExpectedResponse] class.
extension CoapExpectedResponseExtension on ExpectedResponse {
T? _obtainVocabularyTerm<T>(String vocabularyTerm) {
final curieString = coapPrefixMapping.expandCurieString(vocabularyTerm);
final formDefinition = additionalFields?[curieString];

if (formDefinition is T) {
return formDefinition;
}

return null;
}

/// The Content-Format for CoAP request and response payloads.
CoapMediaType get contentFormat {
final formDefinition = _obtainVocabularyTerm<int>('contentFormat');
final contentFormat = CoapMediaType.fromIntValue(formDefinition ?? -1);

return contentFormat ??
CoapMediaType.parse(contentType) ??
CoapMediaType.applicationJson;
}
}

/// Extension for determining the corresponding [CoapRequestMethod] and
/// [CoapSubprotocol] for an [OperationType].
extension OperationTypeExtension on OperationType {
Expand Down
85 changes: 85 additions & 0 deletions test/binding_coap/coap_vocabulary_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2023 The NAMIB Project Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: MIT OR Apache-2.0

import 'package:coap/coap.dart';
import 'package:dart_wot/dart_wot.dart';
import 'package:dart_wot/src/binding_coap/coap_definitions.dart';
import 'package:dart_wot/src/binding_coap/coap_extensions.dart';
import 'package:dart_wot/src/definitions/validation/validation_exception.dart';
import 'package:test/test.dart';

void main() {
group('CoAP Vocabulary Tests', () {
test('Should deserialize CoAP Forms', () async {
const thingDescriptionJson = {
'@context': [
'https://www.w3.org/2022/wot/td/v1.1',
{'cov': 'http://www.example.org/coap-binding#'}
],
'title': 'Test Thing',
'properties': {
'status': {
'forms': [
{
'href': 'coap://example.org',
'cov:method': 'iPATCH',
'contentType': 'application/cbor',
'cov:contentFormat': 60,
'cov:accept': 60,
'cov:blockwise': {
'cov:block1SZX': 32,
'cov:block2SZX': 64,
},
'response': {
'contentType': 'application/cbor',
'cov:contentFormat': 60,
}
},
{
'href': 'coap://example.org',
'cov:blockwise': {
'cov:block1SZX': 5000,
'cov:block2SZX': 4096,
},
},
]
}
},
'securityDefinitions': {
'nosec_sc': {'scheme': 'nosec'}
},
'security': ['nosec_sc']
};

final thingDescription = ThingDescription.fromJson(thingDescriptionJson);
final property = thingDescription.properties['status'];
final form = property?.forms[0];

expect(form?.href, Uri.parse('coap://example.org'));
expect(form?.method, CoapRequestMethod.ipatch);
expect(form?.contentFormat, CoapMediaType.applicationCbor);
expect(form?.accept, CoapMediaType.applicationCbor);
expect(form?.block1Size, BlockSize.blockSize32);
expect(form?.block2Size, BlockSize.blockSize64);
expect(form?.response?.contentFormat, CoapMediaType.applicationCbor);

// TODO(JKRhb): Validation should happen earlier
final invalidForm = property?.forms[1];
expect(
() => invalidForm?.block1Size,
throwsA(isA<ValidationException>()),
);
expect(
() => invalidForm?.block2Size,
throwsA(isA<ValidationException>()),
);
});
});
}

0 comments on commit 0b83b53

Please sign in to comment.