Coverage for aiocoap / defaults.py: 84%
148 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 12:28 +0000
« 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
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 not slipmux_missing_modules():
67 yield "slipmux"
69 if is_pyodide:
70 # There, the remaining ones would just raise NotImplementedError all over the place
71 return
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
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
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.
89 If an explicit ``AIOCOAP_SERVER_TRANSPORT`` environment variable is set, it
90 is read as a colon separated list of transport names.
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 """
100 if use_env and "AIOCOAP_SERVER_TRANSPORT" in os.environ:
101 yield from os.environ["AIOCOAP_SERVER_TRANSPORT"].split(":")
102 return
104 if not oscore_missing_modules():
105 yield "oscore"
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"
114 yield "tcpserver"
115 yield "tcpclient"
116 yield "tlsserver"
117 yield "tlsclient"
119 if not ws_missing_modules():
120 yield "ws"
122 if is_pyodide:
123 # There, the remaining ones would just raise NotImplementedError all over the place
124 return
126 if not slipmux_missing_modules():
127 yield "slipmux"
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
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
142def has_reuse_port(*, use_env=True):
143 """Return true if the platform indicates support for SO_REUSEPORT.
145 Can be overridden by explicitly setting ``AIOCOAP_REUSE_PORT`` to 1 or
146 0."""
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"]))
156 return hasattr(socket, "SO_REUSEPORT")
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
166 warnings.warn(
167 "AI_V4MAPPED emulation is not used any more at all", DeprecationWarning
168 )
169 return False
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.
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"""
179 missing = []
181 try:
182 from DTLSSocket import dtls # noqa: F401
183 except ImportError:
184 missing.append("DTLSSocket")
186 return missing
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
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")
214 try:
215 import ge25519 # noqa: F401
216 except ImportError:
217 missing.append("ge25519")
219 try:
220 import lakers # noqa: F401
221 except ImportError:
222 missing.append("lakers-python")
224 return missing
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"""
231 if is_pyodide:
232 return []
234 missing = []
235 try:
236 import websockets # noqa: F401
237 except ImportError:
238 missing.append("websockets")
240 return missing
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
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
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
286def log_secret(secret):
287 """Wrapper around secret values that go into log output.
289 Unless AIOCOAP_REVEAL_KEYS is set accordingly, this ignores the input and
290 just produces redacted response."""
291 return "[redacted]"
294if os.environ.get("AIOCOAP_REVEAL_KEYS") == "show secrets in logs":
295 if os.access(__file__, mode=os.W_OK):
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 )
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}
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]