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
« 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
5"""This module contains helpers that inspect available modules and platform
6specifics to give sane values to aiocoap defaults.
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.
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"""
21import os
22import socket
23import sys
24import warnings
26try:
27 import pyodide # noqa: F401
28 import js # noqa: F401
29except ImportError:
30 is_pyodide = False
31else:
32 is_pyodide = True
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.
39 If an explicit ``AIOCOAP_CLIENT_TRANSPORT`` environment variable is set, it
40 is read as a colon separated list of transport names.
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 """
47 if use_env and "AIOCOAP_CLIENT_TRANSPORT" in os.environ:
48 yield from os.environ["AIOCOAP_CLIENT_TRANSPORT"].split(":")
49 return
51 if not oscore_missing_modules():
52 yield "oscore"
54 if not is_pyodide:
55 # There, those would just raise NotImplementedError all over the place
57 if not dtls_missing_modules():
58 yield "tinydtls"
60 yield "tcpclient"
61 yield "tlsclient"
63 if not ws_missing_modules():
64 yield "ws"
66 if is_pyodide:
67 # There, the remaining ones would just raise NotImplementedError all over the place
68 return
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
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
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.
86 If an explicit ``AIOCOAP_SERVER_TRANSPORT`` environment variable is set, it
87 is read as a colon separated list of transport names.
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 """
97 if use_env and "AIOCOAP_SERVER_TRANSPORT" in os.environ:
98 yield from os.environ["AIOCOAP_SERVER_TRANSPORT"].split(":")
99 return
101 if not oscore_missing_modules():
102 yield "oscore"
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"
111 yield "tcpserver"
112 yield "tcpclient"
113 yield "tlsserver"
114 yield "tlsclient"
116 if not ws_missing_modules():
117 yield "ws"
119 if is_pyodide:
120 # There, the remaining ones would just raise NotImplementedError all over the place
121 return
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
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
136def has_reuse_port(*, use_env=True):
137 """Return true if the platform indicates support for SO_REUSEPORT.
139 Can be overridden by explicitly setting ``AIOCOAP_REUSE_PORT`` to 1 or
140 0."""
142 if use_env and os.environ.get("AIOCOAP_REUSE_PORT"):
143 return bool(int(os.environ["AIOCOAP_REUSE_PORT"]))
145 return hasattr(socket, "SO_REUSEPORT")
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
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.
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"""
164 missing = []
166 try:
167 from DTLSSocket import dtls # noqa: F401
168 except ImportError:
169 missing.append("DTLSSocket")
171 return missing
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
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")
198 try:
199 import ge25519 # noqa: F401
200 except ImportError:
201 missing.append("ge25519")
203 try:
204 import lakers # noqa: F401
205 except ImportError:
206 missing.append("lakers-python")
208 return missing
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"""
215 if is_pyodide:
216 return []
218 missing = []
219 try:
220 import websockets # noqa: F401
221 except ImportError:
222 missing.append("websockets")
224 return missing
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
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
260def log_secret(secret):
261 """Wrapper around secret values that go into log output.
263 Unless AIOCOAP_REVEAL_KEYS is set accordingly, this ignores the input and
264 just produces redacted response."""
265 return "[redacted]"
268if os.environ.get("AIOCOAP_REVEAL_KEYS") == "show secrets in logs":
269 if os.access(__file__, mode=os.W_OK):
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 )
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}
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]