Coverage for aiocoap/numbers/optionnumbers.py: 94%

106 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-09-30 11:17 +0000

1# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors 

2# 

3# SPDX-License-Identifier: MIT 

4 

5"""Known values for CoAP option numbers 

6 

7The values defined in `OptionNumber` correspond to the IANA registry "CoRE 

8Parameters", subregistries "CoAP Method Codes" and "CoAP Response Codes". 

9 

10The option numbers come with methods that can be used to evaluate their 

11properties, see the `OptionNumber` class for details. 

12""" 

13 

14import warnings 

15 

16from ..util import ExtensibleIntEnum 

17from .. import optiontypes 

18 

19 

20class OptionNumber(ExtensibleIntEnum): 

21 """A CoAP option number. 

22 

23 As the option number contains information on whether the option is 

24 critical, and whether it is safe-to-forward, those properties can be 

25 queried using the `is_*` group of methods. 

26 

27 Note that whether an option may be repeated or not does not only depend on 

28 the option, but also on the context, and is thus handled in the `Options` 

29 object instead.""" 

30 

31 IF_MATCH = 1 

32 URI_HOST = 3 

33 ETAG = 4 

34 IF_NONE_MATCH = 5 

35 OBSERVE = 6 

36 URI_PORT = 7 

37 LOCATION_PATH = 8 

38 OSCORE = 9 

39 URI_PATH = 11 

40 CONTENT_FORMAT = 12 

41 URI_PATH_ABBREV = 13 # from draft-ietf-core-uri-path-abbrev 

42 MAX_AGE = 14 

43 URI_QUERY = 15 

44 HOP_LIMIT = 16 

45 ACCEPT = 17 

46 Q_BLOCK1 = 19 

47 LOCATION_QUERY = 20 

48 EDHOC = 21 

49 BLOCK2 = 23 

50 BLOCK1 = 27 

51 SIZE2 = 28 

52 Q_BLOCK2 = 31 

53 PROXY_URI = 35 

54 PROXY_SCHEME = 39 

55 SIZE1 = 60 

56 ECHO = 252 

57 NO_RESPONSE = 258 

58 REQUEST_TAG = 292 

59 

60 # experimental for draft-amsuess-core-cachable-oscore 

61 # 

62 # Using the number suggested there (rather than a high one) as this is 

63 # going to be used in overhead comparisons. 

64 REQUEST_HASH = 548 

65 

66 @property 

67 def OBJECT_SECURITY(self): 

68 warnings.warn("OBJECT_SECURITY is a deprecated alias for OSCORE") 

69 return self.OSCORE 

70 

71 def __add__(self, delta): 

72 """Addition makes sense on these due to the delta encoding in CoAP 

73 serialization""" 

74 return type(self)(int(self) + delta) 

75 

76 def is_critical(self): 

77 return self & 0x01 == 0x01 

78 

79 def is_elective(self): 

80 return not self.is_critical() 

81 

82 def is_unsafe(self): 

83 return self & 0x02 == 0x02 

84 

85 def is_safetoforward(self): 

86 return not self.is_unsafe() 

87 

88 def is_nocachekey(self): 

89 if self.is_unsafe(): 

90 raise ValueError("NoCacheKey is only meaningful for safe options") 

91 return self & 0x1E == 0x1C 

92 

93 def is_cachekey(self): 

94 return not self.is_nocachekey() 

95 

96 def _get_format(self): 

97 if hasattr(self, "_format"): 

98 return self._format 

99 else: 

100 return optiontypes.OpaqueOption 

101 

102 def set_format(self, value): 

103 """Set the serialization format. 

104 

105 This affects any use of the option throughout the program; existing 

106 options should not be altered incompatibly. Use this on custom or 

107 experimental options. 

108 

109 This is available as a setter function in addition to write access 

110 through the `format` property to satisfy requirements imposed by mypy's 

111 special handling of enums. 

112 """ 

113 if hasattr(self, "_format"): 

114 if self._format != value: 

115 warnings.warn( 

116 "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." 

117 ) 

118 self._format = value 

119 

120 format = property( 

121 _get_format, 

122 set_format, 

123 doc="Serialization format; see :func:`~aiocoap.numbers.optionnumbers.OptionNumber.set_format`", 

124 ) 

125 

126 def create_option(self, decode=None, value=None): 

127 """Return an Option element of the appropriate class from this option 

128 number. 

129 

130 An initial value may be set using the decode or value options, and will 

131 be fed to the resulting object's decode method or value property, 

132 respectively.""" 

133 option = self.format(self) 

134 if decode is not None: 

135 option.decode(decode) 

136 if value is not None: 

137 option.value = value 

138 return option 

139 

140 @property 

141 def name_printable(self): 

142 """The name of the code in human-readable form 

143 

144 This is only available on options that have a known name.""" 

145 return self.name.replace("_", " ").title().replace(" ", "-") 

146 

147 def _repr_html_(self): 

148 import html 

149 

150 properties = f"{'critical' if self.is_critical() else 'elective'}, {'safe-to-forward' if self.is_safetoforward() else 'proxy unsafe'}" 

151 if self.is_safetoforward(): 

152 properties += ( 

153 ", part of the cache key" 

154 if self.is_cachekey() 

155 else ", not part of the cache key" 

156 ) 

157 if hasattr(self, "name"): 

158 return f'<abbr title="option {int(self)}: {properties}">{html.escape(self.name_printable)}</abbr>' 

159 else: 

160 return f'<abbr title="{properties}">Option {int(self)}</abbr>' 

161 

162 

163# OpaqueOption is set on formats where it is known to be used even though it is 

164# the default. This allows developers to rely on those interfaces to be stable 

165# (or at least to be notified visibly in the release notes). 

166 

167# RFC 7252 

168 

169OptionNumber.IF_MATCH.set_format(optiontypes.OpaqueOption) 

170OptionNumber.URI_HOST.set_format(optiontypes.StringOption) 

171OptionNumber.ETAG.set_format(optiontypes.OpaqueOption) 

172OptionNumber.URI_PORT.set_format(optiontypes.UintOption) 

173OptionNumber.LOCATION_PATH.set_format(optiontypes.StringOption) 

174OptionNumber.URI_PATH.set_format(optiontypes.StringOption) 

175OptionNumber.CONTENT_FORMAT.set_format(optiontypes.ContentFormatOption) 

176OptionNumber.MAX_AGE.set_format(optiontypes.UintOption) 

177OptionNumber.URI_QUERY.set_format(optiontypes.StringOption) 

178OptionNumber.ACCEPT.set_format(optiontypes.ContentFormatOption) 

179OptionNumber.LOCATION_QUERY.set_format(optiontypes.StringOption) 

180OptionNumber.PROXY_URI.set_format(optiontypes.StringOption) 

181OptionNumber.PROXY_SCHEME.set_format(optiontypes.StringOption) 

182OptionNumber.SIZE1.set_format(optiontypes.UintOption) 

183 

184# RFC 7959 

185 

186OptionNumber.BLOCK2.set_format(optiontypes.BlockOption) 

187OptionNumber.BLOCK1.set_format(optiontypes.BlockOption) 

188OptionNumber.SIZE2.set_format(optiontypes.UintOption) 

189 

190# RFC 7641 

191 

192OptionNumber.OBSERVE.set_format(optiontypes.UintOption) 

193 

194# RFC 7967 

195 

196OptionNumber.NO_RESPONSE.set_format(optiontypes.UintOption) 

197 

198# RFC 8613 

199 

200OptionNumber.OSCORE.set_format(optiontypes.OpaqueOption) 

201 

202# RFC 9175 

203 

204OptionNumber.ECHO.set_format(optiontypes.OpaqueOption) 

205OptionNumber.REQUEST_TAG.set_format(optiontypes.OpaqueOption) 

206 

207# RFC 8768 

208 

209OptionNumber.HOP_LIMIT.set_format(optiontypes.UintOption) 

210 

211# experimental for draft-amsuess-core-cachable-oscore 

212 

213OptionNumber.REQUEST_HASH.set_format(optiontypes.OpaqueOption) 

214 

215# draft-ietf-core-uri-path-abbrev 

216 

217# FIXME: Should we express this as an enum right away? 

218OptionNumber.URI_PATH_ABBREV.set_format(optiontypes.UintOption)