Coverage for src/aiocoap/defaults.py: 0%

134 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-03-20 17:26 +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 is_pyodide: 

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

68 return 

69 

70 if sys.platform != "linux": 

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

72 # add more platforms. 

73 yield "simple6" 

74 return 

75 

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

77 # that should be managable in udp6 too. 

78 yield "udp6" 

79 return 

80 

81 

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

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

84 context is created. 

85 

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

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

88 

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

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

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

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

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

94 at. 

95 """ 

96 

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

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

99 return 

100 

101 if not oscore_missing_modules(): 

102 yield "oscore" 

103 

104 if not is_pyodide: 

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

106 if not dtls_missing_modules(): 

107 if "AIOCOAP_DTLSSERVER_ENABLED" in os.environ: 

108 yield "tinydtls_server" 

109 yield "tinydtls" 

110 

111 yield "tcpserver" 

112 yield "tcpclient" 

113 yield "tlsserver" 

114 yield "tlsclient" 

115 

116 if not ws_missing_modules(): 

117 yield "ws" 

118 

119 if is_pyodide: 

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

121 return 

122 

123 if sys.platform != "linux": 

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

125 # add more platforms. 

126 yield "simple6" 

127 yield "simplesocketserver" 

128 return 

129 

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

131 # that should be managable in udp6 too. 

132 yield "udp6" 

133 return 

134 

135 

136def has_reuse_port(*, use_env=True): 

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

138 

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

140 0.""" 

141 

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

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

144 

145 return hasattr(socket, "SO_REUSEPORT") 

146 

147 

148def use_ai_v4mapped_emulation(): 

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

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

151 warnings.warn( 

152 "AI_V4MAPPED emulation is not used any more at all", warnings.DeprecationWarning 

153 ) 

154 return False 

155 

156 

157# FIXME: If there were a way to check for the extras defined in setup.py, or to link these lists to what is descibed there, that'd be great. 

158 

159 

160def dtls_missing_modules(): 

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

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

163 

164 missing = [] 

165 

166 try: 

167 from DTLSSocket import dtls # noqa: F401 

168 except ImportError: 

169 missing.append("DTLSSocket") 

170 

171 return missing 

172 

173 

174def oscore_missing_modules(): 

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

176 false value if everything is present""" 

177 missing = [] 

178 try: 

179 import cbor2 # noqa: F401 

180 except ImportError: 

181 missing.append("cbor2") 

182 try: 

183 import cryptography # noqa: F401 

184 except ImportError: 

185 missing.append("cryptography") 

186 else: 

187 try: 

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

189 

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

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

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

193 try: 

194 import filelock # noqa: F401 

195 except ImportError: 

196 missing.append("filelock") 

197 

198 try: 

199 import ge25519 # noqa: F401 

200 except ImportError: 

201 missing.append("ge25519") 

202 

203 try: 

204 import lakers # noqa: F401 

205 except ImportError: 

206 missing.append("lakers-python") 

207 

208 return missing 

209 

210 

211def ws_missing_modules(): 

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

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

214 

215 if is_pyodide: 

216 return [] 

217 

218 missing = [] 

219 try: 

220 import websockets # noqa: F401 

221 except ImportError: 

222 missing.append("websockets") 

223 

224 return missing 

225 

226 

227def linkheader_missing_modules(): 

228 """Return a list of moudles that are missing in order to use link_header 

229 functionaity (eg. running a resource directory), of a false value if 

230 everything is present.""" 

231 missing = [] 

232 # The link_header module is now provided in-tree 

233 return missing 

234 

235 

236def prettyprint_missing_modules(): 

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

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

239 missing = [] 

240 missing.extend(linkheader_missing_modules()) 

241 try: 

242 import cbor2 # noqa: F401 

243 except ImportError: 

244 missing.append("cbor2") 

245 try: 

246 import pygments # noqa: F401 

247 except ImportError: 

248 missing.append("pygments") 

249 try: 

250 import cbor_diag # noqa: F401 

251 except ImportError: 

252 missing.append("cbor-diag") 

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

254 # externally visible optional dependency groups managable, but the things 

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

256 # colorlog is in or not. 

257 return missing 

258 

259 

260def log_secret(secret): 

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

262 

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

264 just produces redacted response.""" 

265 return "[redacted]" 

266 

267 

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

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

270 

271 def log_secret(secret): 

272 return secret 

273 else: 

274 raise RuntimeError( 

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

276 ) 

277 

278missing_module_functions = { 

279 "dtls": dtls_missing_modules, 

280 "oscore": oscore_missing_modules, 

281 "linkheader": linkheader_missing_modules, 

282 "prettyprint": prettyprint_missing_modules, 

283 "ws": ws_missing_modules, 

284} 

285 

286__all__ = [ 

287 "get_default_clienttransports", 

288 "get_default_servertransports", 

289 "has_reuse_port", 

290 "dtls_missing_modules", 

291 "oscore_missing_modules", 

292 "ws_missing_modules", 

293 "linkheader_missing_modules", 

294 "prettyprint_missing_modules", 

295 "missing_module_functions", 

296]