# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors
#
# SPDX-License-Identifier: MIT
"""Known values for CoAP option numbers
The values defined in `OptionNumber` correspond to the IANA registry "CoRE
Parameters", subregistries "CoAP Method Codes" and "CoAP Response Codes".
The option numbers come with methods that can be used to evaluate their
properties, see the `OptionNumber` class for details.
"""
import warnings
from ..util import ExtensibleIntEnum
from .. import optiontypes
class OptionNumber(ExtensibleIntEnum):
"""A CoAP option number.
As the option number contains information on whether the option is
critical, and whether it is safe-to-forward, those properties can be
queried using the `is_*` group of methods.
Note that whether an option may be repeated or not does not only depend on
the option, but also on the context, and is thus handled in the `Options`
object instead."""
IF_MATCH = 1
URI_HOST = 3
ETAG = 4
IF_NONE_MATCH = 5
OBSERVE = 6
URI_PORT = 7
LOCATION_PATH = 8
OSCORE = 9
URI_PATH = 11
CONTENT_FORMAT = 12
URI_PATH_ABBREV = 13 # from draft-ietf-core-uri-path-abbrev
MAX_AGE = 14
URI_QUERY = 15
HOP_LIMIT = 16
ACCEPT = 17
Q_BLOCK1 = 19
LOCATION_QUERY = 20
EDHOC = 21
BLOCK2 = 23
BLOCK1 = 27
SIZE2 = 28
Q_BLOCK2 = 31
PROXY_URI = 35
PROXY_SCHEME = 39
SIZE1 = 60
ECHO = 252
NO_RESPONSE = 258
REQUEST_TAG = 292
# experimental for draft-amsuess-core-cachable-oscore
#
# Using the number suggested there (rather than a high one) as this is
# going to be used in overhead comparisons.
REQUEST_HASH = 548
@property
def OBJECT_SECURITY(self):
warnings.warn("OBJECT_SECURITY is a deprecated alias for OSCORE")
return self.OSCORE
def __add__(self, delta):
"""Addition makes sense on these due to the delta encoding in CoAP
serialization"""
return type(self)(int(self) + delta)
def is_critical(self):
return self & 0x01 == 0x01
def is_elective(self):
return not self.is_critical()
def is_unsafe(self):
return self & 0x02 == 0x02
def is_safetoforward(self):
return not self.is_unsafe()
def is_nocachekey(self):
if self.is_unsafe():
raise ValueError("NoCacheKey is only meaningful for safe options")
return self & 0x1E == 0x1C
def is_cachekey(self):
return not self.is_nocachekey()
def _get_format(self):
if hasattr(self, "_format"):
return self._format
else:
return optiontypes.OpaqueOption
def set_format(self, value):
"""Set the serialization format.
This affects any use of the option throughout the program; existing
options should not be altered incompatibly. Use this on custom or
experimental options.
This is available as a setter function in addition to write access
through the `format` property to satisfy requirements imposed by mypy's
special handling of enums.
"""
if hasattr(self, "_format"):
if self._format != value:
warnings.warn(
"Altering the serialization format of {self}. This is a severe interoperability hazard with other modules, and should only be used during experimentation. This warning may be converted into an error at any time."
)
self._format = value
format = property(
_get_format,
set_format,
doc="Serialization format; see :func:`~aiocoap.numbers.optionnumbers.OptionNumber.set_format`",
)
def create_option(self, decode=None, value=None):
"""Return an Option element of the appropriate class from this option
number.
An initial value may be set using the decode or value options, and will
be fed to the resulting object's decode method or value property,
respectively."""
option = self.format(self)
if decode is not None:
option.decode(decode)
if value is not None:
option.value = value
return option
@property
def name_printable(self):
"""The name of the code in human-readable form
This is only available on options that have a known name."""
return self.name.replace("_", " ").title().replace(" ", "-")
def _repr_html_(self):
import html
properties = f"{'critical' if self.is_critical() else 'elective'}, {'safe-to-forward' if self.is_safetoforward() else 'proxy unsafe'}"
if self.is_safetoforward():
properties += (
", part of the cache key"
if self.is_cachekey()
else ", not part of the cache key"
)
if hasattr(self, "name"):
return f'{html.escape(self.name_printable)}'
else:
return f'Option {int(self)}'
# OpaqueOption is set on formats where it is known to be used even though it is
# the default. This allows developers to rely on those interfaces to be stable
# (or at least to be notified visibly in the release notes).
# RFC 7252
OptionNumber.IF_MATCH.set_format(optiontypes.OpaqueOption)
OptionNumber.URI_HOST.set_format(optiontypes.StringOption)
OptionNumber.ETAG.set_format(optiontypes.OpaqueOption)
OptionNumber.URI_PORT.set_format(optiontypes.UintOption)
OptionNumber.LOCATION_PATH.set_format(optiontypes.StringOption)
OptionNumber.URI_PATH.set_format(optiontypes.StringOption)
OptionNumber.CONTENT_FORMAT.set_format(optiontypes.ContentFormatOption)
OptionNumber.MAX_AGE.set_format(optiontypes.UintOption)
OptionNumber.URI_QUERY.set_format(optiontypes.StringOption)
OptionNumber.ACCEPT.set_format(optiontypes.ContentFormatOption)
OptionNumber.LOCATION_QUERY.set_format(optiontypes.StringOption)
OptionNumber.PROXY_URI.set_format(optiontypes.StringOption)
OptionNumber.PROXY_SCHEME.set_format(optiontypes.StringOption)
OptionNumber.SIZE1.set_format(optiontypes.UintOption)
# RFC 7959
OptionNumber.BLOCK2.set_format(optiontypes.BlockOption)
OptionNumber.BLOCK1.set_format(optiontypes.BlockOption)
OptionNumber.SIZE2.set_format(optiontypes.UintOption)
# RFC 7641
OptionNumber.OBSERVE.set_format(optiontypes.UintOption)
# RFC 7967
OptionNumber.NO_RESPONSE.set_format(optiontypes.UintOption)
# RFC 8613
OptionNumber.OSCORE.set_format(optiontypes.OpaqueOption)
# RFC 9175
OptionNumber.ECHO.set_format(optiontypes.OpaqueOption)
OptionNumber.REQUEST_TAG.set_format(optiontypes.OpaqueOption)
# RFC 8768
OptionNumber.HOP_LIMIT.set_format(optiontypes.UintOption)
# experimental for draft-amsuess-core-cachable-oscore
OptionNumber.REQUEST_HASH.set_format(optiontypes.OpaqueOption)
# draft-ietf-core-uri-path-abbrev
# FIXME: Should we express this as an enum right away?
OptionNumber.URI_PATH_ABBREV.set_format(optiontypes.UintOption)