Coverage for aiocoap/interfaces.py: 95%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

84 statements  

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"""This module provides interface base classes to various aiocoap software 

10components, especially with respect to request and response handling. It 

11describes `abstract base classes`_ for messages, endpoints etc. 

12 

13It is *completely unrelated* to the concept of "network interfaces". 

14 

15.. _`abstract base classes`: https://docs.python.org/3/library/abc""" 

16 

17from __future__ import annotations 

18 

19import abc 

20from aiocoap.numbers.constants import DEFAULT_BLOCK_SIZE_EXP 

21from aiocoap.plumbingrequest import PlumbingRequest 

22 

23from typing import Optional, Callable 

24 

25class MessageInterface(metaclass=abc.ABCMeta): 

26 """A MessageInterface is an object that can exchange addressed messages over 

27 unreliable transports. Implementations send and receive messages with 

28 message type and message ID, and are driven by a Context that deals with 

29 retransmission. 

30 

31 Usually, an MessageInterface refers to something like a local socket, and 

32 send messages to different remote endpoints depending on the message's 

33 addresses. Just as well, a MessageInterface can be useful for one single 

34 address only, or use various local addresses depending on the remote 

35 address. 

36 """ 

37 

38 @abc.abstractmethod 

39 async def shutdown(self): 

40 """Deactivate the complete transport, usually irrevertably. When the 

41 coroutine returns, the object must have made sure that it can be 

42 destructed by means of ref-counting or a garbage collector run.""" 

43 

44 @abc.abstractmethod 

45 def send(self, message): 

46 """Send a given :class:`Message` object""" 

47 

48 @abc.abstractmethod 

49 async def determine_remote(self, message): 

50 """Return a value suitable for the message's remote property based on 

51 its .opt.uri_host or .unresolved_remote. 

52 

53 May return None, which indicates that the MessageInterface can not 

54 transport the message (typically because it is of the wrong scheme).""" 

55 

56class EndpointAddress(metaclass=abc.ABCMeta): 

57 """An address that is suitable for routing through the application to a 

58 remote endpoint. 

59 

60 Depending on the MessageInterface implementation used, an EndpointAddress 

61 property of a message can mean the message is exchanged "with 

62 [2001:db8::2:1]:5683, while my local address was [2001:db8:1::1]:5683" 

63 (typical of UDP6), "over the connected <Socket at 

64 0x1234>, whereever that's connected to" (simple6 or TCP) or "with 

65 participant 0x01 of the OSCAP key 0x..., routed over <another 

66 EndpointAddress>". 

67 

68 EndpointAddresses are only concstructed by MessageInterface objects, 

69 either for incoming messages or when populating a message's .remote in 

70 :meth:`MessageInterface.determine_remote`. 

71 

72 There is no requirement that those address are always identical for a given 

73 address. However, incoming addresses must be hashable and hash-compare 

74 identically to requests from the same context. The "same context", for the 

75 purpose of EndpointAddresses, means that the message must be eligible for 

76 request/response, blockwise (de)composition and observations. (For example, 

77 in a DTLS context, the hash must change between epochs due to RFC7252 

78 Section 9.1.2). 

79 

80 So far, it is required that hash-identical objects also compare the same. 

81 That requirement might go away in future to allow equality to reflect finer 

82 details that are not hashed. (The only property that is currently known not 

83 to be hashed is the local address in UDP6, because that is *unknown* in 

84 initially sent packages, and thus disregarded for comparison but needed to 

85 round-trip through responses.) 

86 """ 

87 

88 @property 

89 @abc.abstractmethod 

90 def hostinfo(self): 

91 """The authority component of URIs that this endpoint represents when 

92 request are sent to it 

93 

94 Note that the presence of a hostinfo does not necessarily mean that 

95 globally meaningful or even syntactically valid URI can be constructed 

96 out of it; use the :attr:`.uri` property for this.""" 

97 

98 @property 

99 @abc.abstractmethod 

100 def hostinfo_local(self): 

101 """The authority component of URIs that this endpoint represents when 

102 requests are sent from it. 

103 

104 As with :attr:`.hostinfo`, this does not necessarily produce sufficient 

105 input for a URI; use :attr:`.uri_local` instead.""" 

106 

107 @property 

108 def uri(self): 

109 """Deprecated alias for uri_base""" 

110 return self.uri_base 

111 

112 @property 

113 @abc.abstractmethod 

114 def uri_base(self): 

115 """The base URI for the peer (typically scheme plus .hostinfo). 

116 

117 This raises :class:`.error.AnonymousHost` when executed on an address 

118 whose peer coordinates can not be expressed meaningfully in a URI.""" 

119 

120 @property 

121 @abc.abstractmethod 

122 def uri_base_local(self): 

123 """The base URI for the local side of this remote. 

124 

125 This raises :class:`.error.AnonymousHost` when executed on an address 

126 whose local coordinates can not be expressed meaningfully in a URI.""" 

127 

128 @property 

129 @abc.abstractmethod 

130 def is_multicast(self): 

131 """True if the remote address is a multicast address, otherwise false.""" 

132 

133 @property 

134 @abc.abstractmethod 

135 def is_multicast_locally(self): 

136 """True if the local address is a multicast address, otherwise false.""" 

137 

138 @property 

139 @abc.abstractmethod 

140 def scheme(Self): 

141 """The that is used with addresses of this kind 

142 

143 This is usually a class property. It is applicable to both sides of the 

144 communication. (Should there ever be a scheme that addresses the 

145 participants differently, a scheme_local will be added.)""" 

146 

147 maximum_block_size_exp = DEFAULT_BLOCK_SIZE_EXP 

148 """The maximum negotiated block size that can be sent to this remote.""" 

149 

150 maximum_payload_size = 1024 

151 """The maximum payload size that can be sent to this remote. Only relevant 

152 if maximum_block_size_exp is 7. This will be removed in favor of a maximum 

153 message size when the block handlers can get serialization length 

154 predictions from the remote. Must be divisible by 1024.""" 

155 

156 def as_response_address(self): 

157 """Address to be assigned to a response to messages that arrived with 

158 this message 

159 

160 This can (and does, by default) return self, but gives the protocol the 

161 opportunity to react to create a modified copy to deal with variations 

162 from multicast. 

163 """ 

164 return self 

165 

166 @property 

167 def authenticated_claims(self): 

168 """Iterable of objects representing any claims (e.g. an identity, or 

169 generally objects that can be used to authorize particular accesses) 

170 that were authenticated for this remote. 

171 

172 This is experimental and may be changed without notice. 

173 

174 Its primary use is on the server side; there, a request handler (or 

175 resource decorator) can use the claims to decide whether the client is 

176 authorized for a particular request. Use on the client side is planned 

177 as a requirement on a request, although (especially on side-effect free 

178 non-confidential requests) it can also be used in response 

179 processing.""" 

180 # "no claims" is a good default 

181 return () 

182 

183class MessageManager(metaclass=abc.ABCMeta): 

184 """The interface an entity that drives a MessageInterface provides towards 

185 the MessageInterface for callbacks and object acquisition.""" 

186 

187 @abc.abstractmethod 

188 def dispatch_message(self, message): 

189 """Callback to be invoked with an incoming message""" 

190 

191 @abc.abstractmethod 

192 def dispatch_error(self, error: Exception, remote): 

193 """Callback to be invoked when the operating system indicated an error 

194 condition from a particular remote.""" 

195 

196 @property 

197 @abc.abstractmethod 

198 def client_credentials(self): 

199 """A CredentialsMap that transports should consult when trying to 

200 establish a security context""" 

201 

202class TokenInterface(metaclass=abc.ABCMeta): 

203 @abc.abstractmethod 

204 def send_message(self, message, messageerror_monitor) -> Optional[Callable[[], None]]: 

205 """Send a message. If it returns a a callable, the caller is asked to 

206 call in case it no longer needs the message sent, and to dispose of if 

207 it doesn't intend to any more. 

208 

209 messageerror_monitor is a function that will be called at most once by 

210 the token interface: When the underlying layer is indicating that this 

211 concrete message could not be processed. This is typically the case for 

212 RSTs on from the message layer, and used to cancel observations. Errors 

213 that are not likely to be specific to a message (like retransmission 

214 timeouts, or ICMP errors) are reported through dispatch_error instead. 

215 (While the information which concrete message triggered that might be 

216 available, it is not likely to be relevant). 

217 

218 Currently, it is up to the TokenInterface to unset the no_response 

219 option in response messages, and to possibly not send them.""" 

220 

221 @abc.abstractmethod 

222 async def fill_or_recognize_remote(self, message): 

223 """Return True if the message is recognized to already have a .remote 

224 managedy by this TokenInterface, or return True and set a .remote on 

225 message if it should (by its unresolved remote or Uri-* options) be 

226 routed through this TokenInterface, or return False otherwise.""" 

227 

228class TokenManager(metaclass=abc.ABCMeta): 

229 # to be described in full; at least there is a dispatch_error in analogy to MessageManager's 

230 pass 

231 

232class RequestInterface(metaclass=abc.ABCMeta): 

233 @abc.abstractmethod 

234 async def fill_or_recognize_remote(self, message): 

235 pass 

236 

237 @abc.abstractmethod 

238 def request(self, request: PlumbingRequest): 

239 pass 

240 

241class RequestProvider(metaclass=abc.ABCMeta): 

242 @abc.abstractmethod 

243 def request(self, request_message): 

244 """Create and act on a a :class:`Request` object that will be handled 

245 according to the provider's implementation. 

246 

247 Note that the request is not necessarily sent on the wire immediately; 

248 it may (but, depend on the transport does not necessarily) rely on the 

249 response to be waited for.""" 

250 

251class Request(metaclass=abc.ABCMeta): 

252 """A CoAP request, initiated by sending a message. Typically, this is not 

253 instanciated directly, but generated by a :meth:`RequestProvider.request` 

254 method.""" 

255 

256 response = """A future that is present from the creation of the object and \ 

257 fullfilled with the response message. 

258 

259 When legitimate errors occur, this becomes an aiocoap.Error. (Eg. on 

260 any kind of network failure, encryption trouble, or protocol 

261 violations). Any other kind of exception raised from this is a bug in 

262 aiocoap, and should better stop the whole application. 

263 """ 

264 

265class Resource(metaclass=abc.ABCMeta): 

266 """Interface that is expected by a :class:`.protocol.Context` to be present 

267 on the serversite, which renders all requests to that context.""" 

268 

269 @abc.abstractmethod 

270 async def render(self, request): 

271 """Return a message that can be sent back to the requester. 

272 

273 This does not need to set any low-level message options like remote, 

274 token or message type; it does however need to set a response code. 

275 

276 A response returned may carry a no_response option (which is actually 

277 specified to apply to requests only); the underlying transports will 

278 decide based on that and its code whether to actually transmit the 

279 response.""" 

280 

281 @abc.abstractmethod 

282 async def needs_blockwise_assembly(self, request): 

283 """Indicator to the :class:`.protocol.Responder` about whether it 

284 should assemble request blocks to a single request and extract the 

285 requested blocks from a complete-resource answer (True), or whether 

286 the resource will do that by itself (False).""" 

287 

288 async def can_render_to_plumbingrequest(self, request): 

289 """Indicates with False that the resource is on the (older) `.render()` 

290 interface where the Context manages things like blockwising and 

291 observation (in which `.needs_blockwise_assembly()` and 

292 `.add_observation()` are used), and with True that 

293 `.render_to_plumbingrequest()` is to be called. In the latter case, 

294 block management is done by on-demand wrapping, and non-traditional 

295 responses like observations are indicated by sending non-last 

296 events.""" 

297 return False 

298 

299 async def render_to_plumbingrequest(self, request: PlumbingRequest): 

300 raise RuntimeError("can_render_to_plumbingrequest reported True but resource can not render to PlumbingRequest") 

301 

302class ObservableResource(Resource, metaclass=abc.ABCMeta): 

303 """Interface the :class:`.protocol.ServerObservation` uses to negotiate 

304 whether an observation can be established based on a request. 

305 

306 This adds only functionality for registering and unregistering observations; 

307 the notification contents will be retrieved from the resource using the 

308 regular :meth:`.render` method from crafted (fake) requests. 

309 """ 

310 @abc.abstractmethod 

311 async def add_observation(self, request, serverobservation): 

312 """Before the incoming request is sent to :meth:`.render`, the 

313 :meth:`.add_observation` method is called. If the resource chooses to 

314 accept the observation, it has to call the 

315 `serverobservation.accept(cb)` with a callback that will be called when 

316 the observation ends. After accepting, the ObservableResource should 

317 call `serverobservation.trigger()` whenever it changes its state; the 

318 ServerObservation will then initiate notifications by having the 

319 request rendered again."""