5
5
import asyncio
6
6
import logging
7
7
import time
8
+ from typing import TYPE_CHECKING , Any , Dict , List , Optional , Tuple
8
9
9
10
from cryptography .exceptions import InvalidTag
10
11
import h11
17
18
from .hap_handler import HAPResponse , HAPServerHandler
18
19
from .util import async_create_background_task
19
20
21
+ if TYPE_CHECKING :
22
+ from .accessory_driver import AccessoryDriver
23
+
20
24
logger = logging .getLogger (__name__ )
21
25
22
26
HIGH_WRITE_BUFFER_SIZE = 2 ** 19
29
33
30
34
EVENT_COALESCE_TIME_WINDOW = 0.5
31
35
36
+ H11_END_OF_MESSAGE = h11 .EndOfMessage ()
37
+ H11_CONNECTION_CLOSED = h11 .ConnectionClosed ()
38
+
32
39
33
40
class HAPServerProtocol (asyncio .Protocol ):
34
41
"""A asyncio.Protocol implementing the HAP protocol."""
35
42
36
- def __init__ (self , loop , connections , accessory_driver ) -> None :
37
- self .loop = loop
43
+ def __init__ (
44
+ self ,
45
+ loop : asyncio .AbstractEventLoop ,
46
+ connections : Dict [str , "HAPServerProtocol" ],
47
+ accessory_driver : "AccessoryDriver" ,
48
+ ) -> None :
49
+ self .loop : asyncio .AbstractEventLoop = loop
38
50
self .conn = h11 .Connection (h11 .SERVER )
39
51
self .connections = connections
40
52
self .accessory_driver = accessory_driver
41
- self .handler = None
42
- self .peername = None
43
- self .transport = None
44
-
45
- self .request = None
46
- self .request_body = None
47
- self .response = None
53
+ self .handler : Optional [HAPServerHandler ] = None
54
+ self .peername : Optional [str ] = None
55
+ self .transport : Optional [asyncio .Transport ] = None
48
56
49
- self .last_activity = None
50
- self .hap_crypto = None
51
- self ._event_timer = None
52
- self ._event_queue = {}
57
+ self .request : Optional [h11 .Request ] = None
58
+ self .request_body : Optional [bytes ] = None
59
+ self .response : Optional [HAPResponse ] = None
53
60
54
- self .start_time = None
61
+ self .last_activity : Optional [float ] = None
62
+ self .hap_crypto : Optional [HAPCrypto ] = None
63
+ self ._event_timer : Optional [asyncio .TimerHandle ] = None
64
+ self ._event_queue : Dict [Tuple [int , int ], Dict [str , Any ]] = {}
55
65
56
66
def connection_lost (self , exc : Exception ) -> None :
57
67
"""Handle connection lost."""
@@ -128,24 +138,29 @@ def send_response(self, response: HAPResponse) -> None:
128
138
# Force Content-Length as iOS can sometimes
129
139
# stall if it gets chunked encoding
130
140
response .headers .append (("Content-Length" , str (body_len )))
141
+ send = self .conn .send
131
142
self .write (
132
- self .conn .send (
133
- h11 .Response (
134
- status_code = response .status_code ,
135
- reason = response .reason ,
136
- headers = response .headers ,
143
+ b"" .join (
144
+ (
145
+ send (
146
+ h11 .Response (
147
+ status_code = response .status_code ,
148
+ reason = response .reason ,
149
+ headers = response .headers ,
150
+ )
151
+ ),
152
+ send (h11 .Data (data = response .body )),
153
+ send (H11_END_OF_MESSAGE ),
137
154
)
138
155
)
139
- + self .conn .send (h11 .Data (data = response .body ))
140
- + self .conn .send (h11 .EndOfMessage ())
141
156
)
142
157
143
- def finish_and_close (self ):
158
+ def finish_and_close (self )
F438
-> None :
144
159
"""Cleanly finish and close the connection."""
145
- self .conn .send (h11 . ConnectionClosed () )
160
+ self .conn .send (H11_CONNECTION_CLOSED )
146
161
self .close ()
147
162
148
- def check_idle (self , now ) -> None :
163
+ def check_idle (self , now : float ) -> None :
149
164
"""Abort when do not get any data within the timeout."""
150
165
if self .last_activity + IDLE_CONNECTION_TIMEOUT_SECONDS >= now :
151
166
return
@@ -193,7 +208,7 @@ def data_received(self, data: bytes) -> None:
193
208
)
194
209
self ._process_events ()
195
210
196
- def _process_events (self ):
211
+ def _process_events (self ) -> None :
197
212
"""Process pending events."""
198
213
try :
199
214
while self ._process_one_event ():
@@ -203,7 +218,7 @@ def _process_events(self):
203
218
except h11 .ProtocolError as protocol_ex :
204
219
self ._handle_invalid_conn_state (protocol_ex )
205
220
206
- def _send_events (self ):
221
+ def _send_events (self ) -> None :
207
222
"""Send any pending events."""
208
223
if self ._event_timer :
209
224
self ._event_timer .cancel ()
@@ -215,7 +230,7 @@ def _send_events(self):
215
230
self .write (create_hap_event (subscribed_events ))
216
231
self ._event_queue .clear ()
217
232
218
- def _event_queue_with_active_subscriptions (self ):
233
+ def _event_queue_with_active_subscriptions (self ) -> List [ Dict [ str , Any ]] :
219
234
"""Remove any topics that have been unsubscribed after the event was generated."""
220
235
topics = self .accessory_driver .topics
221
236
return [
@@ -256,7 +271,7 @@ def _process_one_event(self) -> bool:
256
271
257
272
return self ._handle_invalid_conn_state (f"Unexpected event: { event } " )
258
273
259
- def _process_response (self , response ) -> None :
274
+ def _process_response (self , response : HAPResponse ) -> None :
260
275
"""Process a response from the handler."""
261
276
if response .task :
262
277
# If there is a task pending we will schedule
@@ -298,7 +313,7 @@ def _handle_response_ready(self, task: asyncio.Task) -> None:
298
313
return
299
314
self .send_response (response )
300
315
301
- def _handle_invalid_conn_state (self , message ) :
316
+ def _handle_invalid_conn_state (self , message : Exception ) -> bool :
302
317
"""Log invalid state and close."""
303
318
logger .debug (
304
319
"%s (%s): Invalid state: %s: close the client socket" ,
0 commit comments