1# This file is part of the Python aiocoap library project.
2#
3# Copyright (c) 2012-2014 Maciej Wasilak <http://sixpinetrees.blogspot.com/>,
4#               2013-2014 Christian Amsüss <c.amsuess@energyharvesting.at>
5#
6# aiocoap is free software, this file is published under the MIT license as
7# described in the accompanying LICENSE file.
8
9"""
10Common errors for the aiocoap library
11"""
12
13import warnings
14import abc
15
16from .numbers import codes
17
18class Error(Exception):
19    """
20    Base exception for all exceptions that indicate a failed request
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
33class ResponseWrappingError(Error):
34    """
35    An exception that is raised due to an unsuccessful but received response.
36
37    A better relationship with :mod:`.numbers.codes` should be worked out to do
38    ``except UnsupportedMediaType`` (similar to the various ``OSError``
39    subclasses).
40    """
41    def __init__(self, coapmessage):
42        self.coapmessage = coapmessage
43
44    def to_message(self):
45        return self.coapmessage
46
47    def __repr__(self):
48        return "<%s: %s %r>"%(type(self).__name__, self.coapmessage.code, self.coapmessage.payload)
49
50class ConstructionRenderableError(RenderableError):
51    """
52    RenderableError that is constructed from class attributes :attr:`code` and
53    :attr:`message` (where the can be overridden in the constructor).
54    """
55
56    def __init__(self, message=None):
57        if message is not None:
58            self.message = message
59
60    def to_message(self):
61        from .message import Message
62        return Message(code=self.code, payload=self.message.encode('utf8'))
63
64    code = codes.INTERNAL_SERVER_ERROR #: Code assigned to messages built from it
65    message = "" #: Text sent in the built message's payload
66
67# FIXME: this should be comprehensive, maybe generted from the code list
68
69class NotFound(ConstructionRenderableError):
70    code = codes.NOT_FOUND
71
72class MethodNotAllowed(ConstructionRenderableError):
73    code = codes.METHOD_NOT_ALLOWED
74
75class UnsupportedContentFormat(ConstructionRenderableError):
76    code = codes.UNSUPPORTED_CONTENT_FORMAT
77
78class Unauthorized(ConstructionRenderableError):
79    code = codes.UNAUTHORIZED
80
81class BadRequest(ConstructionRenderableError):
82    code = codes.BAD_REQUEST
83
84# More detailed versions of code based errors
85
86class NoResource(NotFound):
87    """
88    Raised when resource is not found.
89    """
90    message = "Error: Resource not found!"
91    def __init__(self):
92        warnings.warn("NoResource is deprecated in favor of NotFound", DeprecationWarning, stacklevel=2)
93
94class UnallowedMethod(MethodNotAllowed):
95    """
96    Raised by a resource when request method is understood by the server
97    but not allowed for that particular resource.
98    """
99    message = "Error: Method not allowed!"
100
101class UnsupportedMethod(MethodNotAllowed):
102    """
103    Raised when request method is not understood by the server at all.
104    """
105    message = "Error: Method not recognized!"
106
107
108class NetworkError(Error):
109    """Base class for all "something went wrong with name resolution, sending
110    or receiving packages".
111
112    Errors of these kinds are raised towards client callers when things went
113    wrong network-side, or at context creation. They are often raised from
114    socket.gaierror or similar classes, but these are wrapped in order to make
115    catching them possible independently of the underlying transport."""
116
117class ResolutionError(NetworkError):
118    """Resolving the host component of a URI to a usable transport address was
119    not possible"""
120
121class MessageError(NetworkError):
122    """Received an error from the remote on the CoAP message level (typically a
123    RST)"""
124
125class NotImplemented(Error):
126    """
127    Raised when request is correct, but feature is not implemented
128    by library.
129    For example non-sequential blockwise transfers
130    """
131
132class RemoteServerShutdown(NetworkError):
133    """The peer a request was sent to in a stateful connection closed the
134    connection around the time the request was sent"""
135
136class TimeoutError(NetworkError):
137    """Base for all timeout-ish errors.
138
139    Like NetworkError, receiving this alone does not indicate whether the
140    request may have reached the server or not.
141    """
142
143class ConRetransmitsExceeded(TimeoutError):
144    """A transport that retransmits CON messages has failed to obtain a response
145    within its retransmission timeout.
146
147    When this is raised in a transport, requests failing with it may or may
148    have been received by the server.
149    """
150
151class RequestTimedOut(TimeoutError):
152    """
153    Raised when request is timed out.
154
155    This error is currently not produced by aiocoap; it is deprecated. Users
156    can now catch error.TimeoutError, or newer more detailed subtypes
157    introduced later.
158    """
159
160
161class WaitingForClientTimedOut(TimeoutError):
162    """
163    Raised when server expects some client action:
164
165        - sending next PUT/POST request with block1 or block2 option
166        - sending next GET request with block2 option
167
168    but client does nothing.
169
170    This error is currently not produced by aiocoap; it is deprecated. Users
171    can now catch error.TimeoutError, or newer more detailed subtypes
172    introduced later.
173    """
174
175class ResourceChanged(Error):
176    """
177    The requested resource was modified during the request and could therefore
178    not be received in a consistent state.
179    """
180
181class UnexpectedBlock1Option(Error):
182    """
183    Raised when a server responds with block1 options that just don't match.
184    """
185
186class UnexpectedBlock2(Error):
187    """
188    Raised when a server responds with another block2 than expected.
189    """
190
191class MissingBlock2Option(Error):
192    """
193    Raised when response with Block2 option is expected
194    (previous response had Block2 option with More flag set),
195    but response without Block2 option is received.
196    """
197
198class NotObservable(Error):
199    """
200    The server did not accept the request to observe the resource.
201    """
202
203class ObservationCancelled(Error):
204    """
205    The server claimed that it will no longer sustain the observation.
206    """
207
208class UnparsableMessage(Error):
209    """
210    An incoming message does not look like CoAP.
211
212    Note that this happens rarely -- the requirements are just two bit at the
213    beginning of the message, and a minimum length.
214    """
215
216class LibraryShutdown(Error):
217    """The library or a transport registered with it was requested to shut
218    down; this error is raised in all outstanding requests."""
219
220class AnonymousHost(Error):
221    """This is raised when it is attempted to express as a reference a (base)
222    URI of a host or a resource that can not be reached by any process other
223    than this.
224
225    Typically, this happens when trying to serialize a link to a resource that
226    is hosted on a CoAP-over-TCP or -WebSockets client: Such resources can be
227    accessed for as long as the connection is active, but can not be used any
228    more once it is closed or even by another system."""
229
230_deprecated_aliases = {
231        "UnsupportedMediaType": "UnsupportedContentFormat",
232        "RequestTimedOut": "TimeoutError",
233        "WaitingForClientTimedOut": "TimeoutError",
234        }
235def __getattr__(name):
236    if name in _deprecated_aliases:
237        modern = _deprecated_aliases[name]
238        from warnings import warn
239        warn(f"{name} is deprecated, use {modern} instead", DeprecationWarning,
240                stacklevel=2)
241        return globals()[modern]
242    raise AttributeError(f"module {__name__} has no attribute {name}")
243