Coverage for aiocoap / defaults.py: 84%

148 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 12:28 +0000

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

2# 

3# SPDX-License-Identifier: MIT 

4 

5"""This module contains helpers that inspect available modules and platform 

6specifics to give sane values to aiocoap defaults. 

7 

8All of this should eventually overridable by other libraries wrapping/using 

9aiocoap and by applications using aiocoap; however, these overrides do not 

10happen in the defaults module but where these values are actually accessed, so 

11this module is considered internal to aiocoap and not part of the API. 

12 

13The ``_missing_modules`` functions are helpers for inspecting what is 

14reasonable to expect to work. They can influence default values, but should not 

15be used in the rest of the code for feature checking (just raise the 

16ImportErrors) unless it's directly user-visible ("You configured OSCORE key 

17material, but OSCORE needs the following unavailable modules") or in the test 

18suite to decide which tests to skip. 

19""" 

20 

21import os 

22import socket 

23import sys 

24import warnings 

25 

26try: 

27 import pyodide # noqa: F401 

28 import js # noqa: F401 

29except ImportError: 

30 is_pyodide = False 

31else: 

32 is_pyodide = True 

33 

34 

35def get_default_clienttransports(*, loop=None, use_env=True): 

36 """Return a list of transports that should be connected when a client 

37 context is created. 

38 

39 If an explicit ``AIOCOAP_CLIENT_TRANSPORT`` environment variable is set, it 

40 is read as a colon separated list of transport names. 

41 

42 By default, a DTLS mechanism will be picked if the required modules are 

43 available, and a UDP transport will be selected depending on whether the 

44 full udp6 transport is known to work. 

45 """ 

46 

47 if use_env and "AIOCOAP_CLIENT_TRANSPORT" in os.environ: 

48 yield from os.environ["AIOCOAP_CLIENT_TRANSPORT"].split(":") 

49 return 

50 

51 if not oscore_missing_modules(): 

52 yield "oscore" 

53 

54 if not is_pyodide: 

55 # There, those would just raise NotImplementedError all over the place 

56 

57 if not dtls_missing_modules(): 

58 yield "tinydtls" 

59 

60 yield "tcpclient" 

61 yield "tlsclient" 

62 

63 if not ws_missing_modules(): 

64 yield "ws" 

65 

66 if not slipmux_missing_modules(): 

67 yield "slipmux" 

68 

69 if is_pyodide: 

70 # There, the remaining ones would just raise NotImplementedError all over the place 

71 return 

72 

73 if sys.platform != "linux": 

74 # udp6 was never reported to work on anything but linux; would happily 

75 # add more platforms. 

76 yield "simple6" 

77 return 

78 

79 # on android it seems that it's only the AI_V4MAPPED that causes trouble, 

80 # that should be manageable in udp6 too. 

81 yield "udp6" 

82 return 

83 

84 

85def get_default_servertransports(*, loop=None, use_env=True): 

86 """Return a list of transports that should be connected when a server 

87 context is created. 

88 

89 If an explicit ``AIOCOAP_SERVER_TRANSPORT`` environment variable is set, it 

90 is read as a colon separated list of transport names. 

91 

92 By default, a DTLS mechanism will be picked if the required modules are 

93 available, and a UDP transport will be selected depending on whether the 

94 full udp6 transport is known to work. Both a simple6 and a simplesocketserver 

95 will be selected when udp6 is not available, and the simple6 will be used 

96 for any outgoing requests, which the simplesocketserver could serve but is worse 

97 at. 

98 """ 

99 

100 if use_env and "AIOCOAP_SERVER_TRANSPORT" in os.environ: 

101 yield from os.environ["AIOCOAP_SERVER_TRANSPORT"].split(":") 

102 return 

103 

104 if not oscore_missing_modules(): 

105 yield "oscore" 

106 

107 if not is_pyodide: 

108 # There, those would just raise NotImplementedError all over the place 

109 if not dtls_missing_modules(): 

110 if "AIOCOAP_DTLSSERVER_ENABLED" in os.environ: 

111 yield "tinydtls_server" 

112 yield "tinydtls" 

113 

114 yield "tcpserver" 

115 yield "tcpclient" 

116 yield "tlsserver" 

117 yield "tlsclient" 

118 

119 if not ws_missing_modules(): 

120 yield "ws" 

121 

122 if is_pyodide: 

123 # There, the remaining ones would just raise NotImplementedError all over the place 

124 return 

125 

126 if not slipmux_missing_modules(): 

127 yield "slipmux" 

128 

129 if sys.platform != "linux": 

130 # udp6 was never reported to work on anything but linux; would happily 

131 # add more platforms. 

132 yield "simple6" 

133 yield "simplesocketserver" 

134 return 

135 

136 # on android it seems that it's only the AI_V4MAPPED that causes trouble, 

137 # that should be manageable in udp6 too. 

138 yield "udp6" 

139 return 

140 

141 

142def has_reuse_port(*, use_env=True): 

143 """Return true if the platform indicates support for SO_REUSEPORT. 

144 

145 Can be overridden by explicitly setting ``AIOCOAP_REUSE_PORT`` to 1 or 

146 0.""" 

147 

148 if use_env and os.environ.get("AIOCOAP_REUSE_PORT"): 

149 warnings.warn( 

150 "The AIOCOAP_REUSE_PORT environment variable is deprecated in favor " 

151 "of providing TransportParameters at Context creation", 

152 warnings.DeprecationWarning, 

153 ) 

154 return bool(int(os.environ["AIOCOAP_REUSE_PORT"])) 

155 

156 return hasattr(socket, "SO_REUSEPORT") 

157 

158 

159def use_ai_v4mapped_emulation(): 

160 """This used to indicate when ai_v4mapped emulation was used. Given it is 

161 not used at all any more, the function is deprecated.""" 

162 # Importing late because many internal modules want to call functions of 

163 # defaults early on 

164 from aiocoap.util import DeprecationWarning 

165 

166 warnings.warn( 

167 "AI_V4MAPPED emulation is not used any more at all", DeprecationWarning 

168 ) 

169 return False 

170 

171 

172# FIXME: If there were a way to check for the extras defined in pyprojec.toml, or to link these lists to what is described there, that'd be great. 

173 

174 

175def dtls_missing_modules(): 

176 """Return a list of modules that are missing in order to use the DTLS 

177 transport, or a false value if everything is present""" 

178 

179 missing = [] 

180 

181 try: 

182 from DTLSSocket import dtls # noqa: F401 

183 except ImportError: 

184 missing.append("DTLSSocket") 

185 

186 return missing 

187 

188 

189def oscore_missing_modules(): 

190 """Return a list of modules that are missing in order to use OSCORE, or a 

191 false value if everything is present""" 

192 missing = [] 

193 try: 

194 import cbor2 # noqa: F401 

195 except ImportError: 

196 missing.append("cbor2") 

197 try: 

198 import cryptography # noqa: F401 

199 import cryptography.exceptions 

200 except ImportError: 

201 missing.append("cryptography") 

202 else: 

203 try: 

204 from cryptography.hazmat.primitives.ciphers.aead import AESCCM 

205 

206 AESCCM(b"x" * 16, 8) 

207 except (cryptography.exceptions.UnsupportedAlgorithm, ImportError): 

208 missing.append("a version of OpenSSL that supports AES-CCM") 

209 try: 

210 import filelock # noqa: F401 

211 except ImportError: 

212 missing.append("filelock") 

213 

214 try: 

215 import ge25519 # noqa: F401 

216 except ImportError: 

217 missing.append("ge25519") 

218 

219 try: 

220 import lakers # noqa: F401 

221 except ImportError: 

222 missing.append("lakers-python") 

223 

224 return missing 

225 

226 

227def ws_missing_modules(): 

228 """Return a list of modules that are missing in order to user CoAP-over-WS, 

229 or a false value if everything is present""" 

230 

231 if is_pyodide: 

232 return [] 

233 

234 missing = [] 

235 try: 

236 import websockets # noqa: F401 

237 except ImportError: 

238 missing.append("websockets") 

239 

240 return missing 

241 

242 

243def linkheader_missing_modules(): 

244 """Return a list of modules that are missing in order to use link_header 

245 functionality (eg. running a resource directory), of a false value if 

246 everything is present.""" 

247 missing = [] 

248 # The link_header module is now provided in-tree 

249 return missing 

250 

251 

252def prettyprint_missing_modules(): 

253 """Return a list of modules that are missing in order to use pretty 

254 printing (ie. full aiocoap-client)""" 

255 missing = [] 

256 missing.extend(linkheader_missing_modules()) 

257 try: 

258 import cbor2 # noqa: F401 

259 except ImportError: 

260 missing.append("cbor2") 

261 try: 

262 import pygments # noqa: F401 

263 except ImportError: 

264 missing.append("pygments") 

265 try: 

266 import cbor_diag # noqa: F401 

267 except ImportError: 

268 missing.append("cbor-diag") 

269 # explicitly not covering colorlog: They are bundled to keep the number of 

270 # externally visible optional dependency groups manageable, but the things 

271 # that depend on `prettyprint_missing_modules` work no matter whether 

272 # colorlog is in or not. 

273 return missing 

274 

275 

276def slipmux_missing_modules(): 

277 """Return a list of modules that are missing in order to use slipmux""" 

278 missing = [] 

279 try: 

280 import serial_asyncio # noqa: F401 

281 except ImportError: 

282 missing.append("pyserial-asyncio") 

283 return missing 

284 

285 

286def log_secret(secret): 

287 """Wrapper around secret values that go into log output. 

288 

289 Unless AIOCOAP_REVEAL_KEYS is set accordingly, this ignores the input and 

290 just produces redacted response.""" 

291 return "[redacted]" 

292 

293 

294if os.environ.get("AIOCOAP_REVEAL_KEYS") == "show secrets in logs": 

295 if os.access(__file__, mode=os.W_OK): 

296 

297 def log_secret(secret): 

298 return secret 

299 else: 

300 raise RuntimeError( 

301 "aiocoap was requested to reveal keys in log files, but aiocoap installation is not writable by user." 

302 ) 

303 

304missing_module_functions = { 

305 "dtls": dtls_missing_modules, 

306 "oscore": oscore_missing_modules, 

307 "linkheader": linkheader_missing_modules, 

308 "prettyprint": prettyprint_missing_modules, 

309 "ws": ws_missing_modules, 

310 "slipmux": slipmux_missing_modules, 

311} 

312 

313__all__ = [ 

314 "get_default_clienttransports", 

315 "get_default_servertransports", 

316 "has_reuse_port", 

317 "dtls_missing_modules", 

318 "oscore_missing_modules", 

319 "ws_missing_modules", 

320 "linkheader_missing_modules", 

321 "prettyprint_missing_modules", 

322 "slipmux_missing_modules", 

323 "missing_module_functions", 

324]