Coverage for src/aiocoap/defaults.py: 0%
135 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 08:24 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 08:24 +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 import cryptography.exceptions
185 except ImportError:
186 missing.append("cryptography")
187 else:
188 try:
189 from cryptography.hazmat.primitives.ciphers.aead import AESCCM
191 AESCCM(b"x" * 16, 8)
192 except (cryptography.exceptions.UnsupportedAlgorithm, ImportError):
193 missing.append("a version of OpenSSL that supports AES-CCM")
194 try:
195 import filelock # noqa: F401
196 except ImportError:
197 missing.append("filelock")
199 try:
200 import ge25519 # noqa: F401
201 except ImportError:
202 missing.append("ge25519")
204 try:
205 import lakers # noqa: F401
206 except ImportError:
207 missing.append("lakers-python")
209 return missing
212def ws_missing_modules():
213 """Return a list of modules that are missing in order to user CoAP-over-WS,
214 or a false value if everything is present"""
216 if is_pyodide:
217 return []
219 missing = []
220 try:
221 import websockets # noqa: F401
222 except ImportError:
223 missing.append("websockets")
225 return missing
228def linkheader_missing_modules():
229 """Return a list of moudles that are missing in order to use link_header
230 functionaity (eg. running a resource directory), of a false value if
231 everything is present."""
232 missing = []
233 # The link_header module is now provided in-tree
234 return missing
237def prettyprint_missing_modules():
238 """Return a list of modules that are missing in order to use pretty
239 printing (ie. full aiocoap-client)"""
240 missing = []
241 missing.extend(linkheader_missing_modules())
242 try:
243 import cbor2 # noqa: F401
244 except ImportError:
245 missing.append("cbor2")
246 try:
247 import pygments # noqa: F401
248 except ImportError:
249 missing.append("pygments")
250 try:
251 import cbor_diag # noqa: F401
252 except ImportError:
253 missing.append("cbor-diag")
254 # explicitly not covering colorlog: They are bundled to keep the number of
255 # externally visible optional dependency groups managable, but the things
256 # that depend on `prettyprint_missing_modules` work no matter whether
257 # colorlog is in or not.
258 return missing
261def log_secret(secret):
262 """Wrapper around secret values that go into log output.
264 Unless AIOCOAP_REVEAL_KEYS is set accordingly, this ignores the input and
265 just produces redacted response."""
266 return "[redacted]"
269if os.environ.get("AIOCOAP_REVEAL_KEYS") == "show secrets in logs":
270 if os.access(__file__, mode=os.W_OK):
272 def log_secret(secret):
273 return secret
274 else:
275 raise RuntimeError(
276 "aiocoap was requested to reveal keys in log files, but aiocoap installation is not writable by user."
277 )
279missing_module_functions = {
280 "dtls": dtls_missing_modules,
281 "oscore": oscore_missing_modules,
282 "linkheader": linkheader_missing_modules,
283 "prettyprint": prettyprint_missing_modules,
284 "ws": ws_missing_modules,
285}
287__all__ = [
288 "get_default_clienttransports",
289 "get_default_servertransports",
290 "has_reuse_port",
291 "dtls_missing_modules",
292 "oscore_missing_modules",
293 "ws_missing_modules",
294 "linkheader_missing_modules",
295 "prettyprint_missing_modules",
296 "missing_module_functions",
297]