Coverage for aiocoap/error.py: 84%

123 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-08-01 17:22 +0000

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

2# 

3# SPDX-License-Identifier: MIT 

4 

5""" 

6Common errors for the aiocoap library 

7""" 

8 

9import warnings 

10import abc 

11import errno 

12 

13from .numbers import codes 

14from . import util 

15 

16 

17class Error(Exception): 

18 """ 

19 Base exception for all exceptions that indicate a failed request 

20 """ 

21 

22 

23class RenderableError(Error, metaclass=abc.ABCMeta): 

24 """ 

25 Exception that can meaningfully be represented in a CoAP response 

26 """ 

27 

28 @abc.abstractmethod 

29 def to_message(self): 

30 """Create a CoAP message that should be sent when this exception is 

31 rendered""" 

32 

33 

34class ResponseWrappingError(Error): 

35 """ 

36 An exception that is raised due to an unsuccessful but received response. 

37 

38 A better relationship with :mod:`.numbers.codes` should be worked out to do 

39 ``except UnsupportedMediaType`` (similar to the various ``OSError`` 

40 subclasses). 

41 """ 

42 

43 def __init__(self, coapmessage): 

44 self.coapmessage = coapmessage 

45 

46 def to_message(self): 

47 return self.coapmessage 

48 

49 def __repr__(self): 

50 return "<%s: %s %r>" % ( 

51 type(self).__name__, 

52 self.coapmessage.code, 

53 self.coapmessage.payload, 

54 ) 

55 

56 

57class ConstructionRenderableError(RenderableError): 

58 """ 

59 RenderableError that is constructed from class attributes :attr:`code` and 

60 :attr:`message` (where the can be overridden in the constructor). 

61 """ 

62 

63 def __init__(self, message=None): 

64 if message is not None: 

65 self.message = message 

66 

67 def to_message(self): 

68 from .message import Message 

69 

70 return Message(code=self.code, payload=self.message.encode("utf8")) 

71 

72 code = codes.INTERNAL_SERVER_ERROR #: Code assigned to messages built from it 

73 message = "" #: Text sent in the built message's payload 

74 

75 

76# This block is code-generated to make the types available to static checkers. 

77# The __debug__ check below ensures that it stays up to date. 

78class BadRequest(ConstructionRenderableError): 

79 code = codes.BAD_REQUEST 

80 

81 

82class Unauthorized(ConstructionRenderableError): 

83 code = codes.UNAUTHORIZED 

84 

85 

86class BadOption(ConstructionRenderableError): 

87 code = codes.BAD_OPTION 

88 

89 

90class Forbidden(ConstructionRenderableError): 

91 code = codes.FORBIDDEN 

92 

93 

94class NotFound(ConstructionRenderableError): 

95 code = codes.NOT_FOUND 

96 

97 

98class MethodNotAllowed(ConstructionRenderableError): 

99 code = codes.METHOD_NOT_ALLOWED 

100 

101 

102class NotAcceptable(ConstructionRenderableError): 

103 code = codes.NOT_ACCEPTABLE 

104 

105 

106class RequestEntityIncomplete(ConstructionRenderableError): 

107 code = codes.REQUEST_ENTITY_INCOMPLETE 

108 

109 

110class Conflict(ConstructionRenderableError): 

111 code = codes.CONFLICT 

112 

113 

114class PreconditionFailed(ConstructionRenderableError): 

115 code = codes.PRECONDITION_FAILED 

116 

117 

118class RequestEntityTooLarge(ConstructionRenderableError): 

119 code = codes.REQUEST_ENTITY_TOO_LARGE 

120 

121 

122class UnsupportedContentFormat(ConstructionRenderableError): 

123 code = codes.UNSUPPORTED_CONTENT_FORMAT 

124 

125 

126class UnprocessableEntity(ConstructionRenderableError): 

127 code = codes.UNPROCESSABLE_ENTITY 

128 

129 

130class TooManyRequests(ConstructionRenderableError): 

131 code = codes.TOO_MANY_REQUESTS 

132 

133 

134class InternalServerError(ConstructionRenderableError): 

135 code = codes.INTERNAL_SERVER_ERROR 

136 

137 

138class NotImplemented(ConstructionRenderableError): 

139 code = codes.NOT_IMPLEMENTED 

140 

141 

142class BadGateway(ConstructionRenderableError): 

143 code = codes.BAD_GATEWAY 

144 

145 

146class ServiceUnavailable(ConstructionRenderableError): 

147 code = codes.SERVICE_UNAVAILABLE 

148 

149 

150class GatewayTimeout(ConstructionRenderableError): 

151 code = codes.GATEWAY_TIMEOUT 

152 

153 

154class ProxyingNotSupported(ConstructionRenderableError): 

155 code = codes.PROXYING_NOT_SUPPORTED 

156 

157 

158class HopLimitReached(ConstructionRenderableError): 

159 code = codes.HOP_LIMIT_REACHED 

160 

161 

162if __debug__: 

163 _missing_codes = False 

164 _full_code = "" 

165 for code in codes.Code: 

166 if code.is_successful() or not code.is_response(): 

167 continue 

168 classname = "".join(w.title() for w in code.name.split("_")) 

169 _full_code += f""" 

170class {classname}(ConstructionRenderableError): 

171 code = codes.{code.name}""" 

172 if classname not in locals(): 

173 warnings.warn(f"Missing exception type: f{classname}") 

174 _missing_codes = True 

175 continue 

176 if locals()[classname].code != code: 

177 warnings.warn( 

178 f"Mismatched code for {classname}: Should be {code}, is {locals()[classname].code}" 

179 ) 

180 _missing_codes = True 

181 continue 

182 if _missing_codes: 

183 warnings.warn( 

184 "Generated exception list is out of sync, should be:\n" + _full_code 

185 ) 

186 

187# More detailed versions of code based errors 

188 

189 

190class NoResource(NotFound): 

191 """ 

192 Raised when resource is not found. 

193 """ 

194 

195 message = "Error: Resource not found!" 

196 

197 def __init__(self): 

198 warnings.warn( 

199 "NoResource is deprecated in favor of NotFound", 

200 DeprecationWarning, 

201 stacklevel=2, 

202 ) 

203 

204 

205class UnallowedMethod(MethodNotAllowed): 

206 """ 

207 Raised by a resource when request method is understood by the server 

208 but not allowed for that particular resource. 

209 """ 

210 

211 message = "Error: Method not allowed!" 

212 

213 

214class UnsupportedMethod(MethodNotAllowed): 

215 """ 

216 Raised when request method is not understood by the server at all. 

217 """ 

218 

219 message = "Error: Method not recognized!" 

220 

221 

222class NetworkError(Error): 

223 """Base class for all "something went wrong with name resolution, sending 

224 or receiving packages". 

225 

226 Errors of these kinds are raised towards client callers when things went 

227 wrong network-side, or at context creation. They are often raised from 

228 socket.gaierror or similar classes, but these are wrapped in order to make 

229 catching them possible independently of the underlying transport.""" 

230 

231 def extra_help(self): 

232 """Information printed at aiocoap-client or similar occasions when the 

233 error message itself may be insufficient to point the user in the right 

234 direction""" 

235 if isinstance(self.__cause__, OSError): 

236 if self.__cause__.errno == errno.ECONNREFUSED: 

237 # seen trying to reach any used address with the port closed 

238 return "The remote host could be reached, but reported that the requested port is not open. Check whether a CoAP server is running at the address, or whether it is running on a different port." 

239 if self.__cause__.errno == errno.EHOSTUNREACH: 

240 # seen trying to reach any unused local address 

241 return "No way of contacting the remote host could be found. This could be because a host on the local network is offline or firewalled. Tools for debugging in the next step could be ping or traceroute." 

242 if self.__cause__.errno == errno.ENETUNREACH: 

243 # seen trying to reach an IPv6 host through an IP literal from a v4-only system, or trying to reach 2001:db8::1 

244 return "No way of contacting the remote network could be found. This may be due to lack of IPv6 connectivity, lack of a concrete route (eg. trying to reach a private use network which there is no route to). Tools for debugging in the next step could be ping or traceroute." 

245 if self.__cause__.errno == errno.EACCES: 

246 # seen trying to reach the broadcast address of a local network 

247 return "The operating system refused to send the request. For example, this can occur when attempting to send broadcast requests instead of multicast requests." 

248 

249 

250class ResolutionError(NetworkError): 

251 """Resolving the host component of a URI to a usable transport address was 

252 not possible""" 

253 

254 

255class MessageError(NetworkError): 

256 """Received an error from the remote on the CoAP message level (typically a 

257 RST)""" 

258 

259 

260class RemoteServerShutdown(NetworkError): 

261 """The peer a request was sent to in a stateful connection closed the 

262 connection around the time the request was sent""" 

263 

264 

265class TimeoutError(NetworkError): 

266 """Base for all timeout-ish errors. 

267 

268 Like NetworkError, receiving this alone does not indicate whether the 

269 request may have reached the server or not. 

270 """ 

271 

272 def extra_help(self): 

273 return "Neither a response nor an error was received. This can have a wide range of causes, from the address being wrong to the server being stuck." 

274 

275 

276class ConRetransmitsExceeded(TimeoutError): 

277 """A transport that retransmits CON messages has failed to obtain a response 

278 within its retransmission timeout. 

279 

280 When this is raised in a transport, requests failing with it may or may 

281 have been received by the server. 

282 """ 

283 

284 

285class RequestTimedOut(TimeoutError): 

286 """ 

287 Raised when request is timed out. 

288 

289 This error is currently not produced by aiocoap; it is deprecated. Users 

290 can now catch error.TimeoutError, or newer more detailed subtypes 

291 introduced later. 

292 """ 

293 

294 

295class WaitingForClientTimedOut(TimeoutError): 

296 """ 

297 Raised when server expects some client action: 

298 

299 - sending next PUT/POST request with block1 or block2 option 

300 - sending next GET request with block2 option 

301 

302 but client does nothing. 

303 

304 This error is currently not produced by aiocoap; it is deprecated. Users 

305 can now catch error.TimeoutError, or newer more detailed subtypes 

306 introduced later. 

307 """ 

308 

309 

310class ResourceChanged(Error): 

311 """ 

312 The requested resource was modified during the request and could therefore 

313 not be received in a consistent state. 

314 """ 

315 

316 

317class UnexpectedBlock1Option(Error): 

318 """ 

319 Raised when a server responds with block1 options that just don't match. 

320 """ 

321 

322 

323class UnexpectedBlock2(Error): 

324 """ 

325 Raised when a server responds with another block2 than expected. 

326 """ 

327 

328 

329class MissingBlock2Option(Error): 

330 """ 

331 Raised when response with Block2 option is expected 

332 (previous response had Block2 option with More flag set), 

333 but response without Block2 option is received. 

334 """ 

335 

336 

337class NotObservable(Error): 

338 """ 

339 The server did not accept the request to observe the resource. 

340 """ 

341 

342 

343class ObservationCancelled(Error): 

344 """ 

345 The server claimed that it will no longer sustain the observation. 

346 """ 

347 

348 

349class UnparsableMessage(Error): 

350 """ 

351 An incoming message does not look like CoAP. 

352 

353 Note that this happens rarely -- the requirements are just two bit at the 

354 beginning of the message, and a minimum length. 

355 """ 

356 

357 

358class LibraryShutdown(Error): 

359 """The library or a transport registered with it was requested to shut 

360 down; this error is raised in all outstanding requests.""" 

361 

362 

363class AnonymousHost(Error): 

364 """This is raised when it is attempted to express as a reference a (base) 

365 URI of a host or a resource that can not be reached by any process other 

366 than this. 

367 

368 Typically, this happens when trying to serialize a link to a resource that 

369 is hosted on a CoAP-over-TCP or -WebSockets client: Such resources can be 

370 accessed for as long as the connection is active, but can not be used any 

371 more once it is closed or even by another system.""" 

372 

373 

374__getattr__ = util.deprecation_getattr( 

375 { 

376 "UnsupportedMediaType": "UnsupportedContentFormat", 

377 "RequestTimedOut": "TimeoutError", 

378 "WaitingForClientTimedOut": "TimeoutError", 

379 }, 

380 globals(), 

381)