1# -*- coding: utf-8 -*-
2# Copyright 2019 Oticon A/S
3# SPDX-License-Identifier: Apache-2.0
4
5from enum import IntEnum;
6from components.utils import *;
7from components.basic_commands import *;
8from components.address import *;
9from components.events import *;
10
11class InitiatorFilterPolicy(IntEnum):
12    FILTER_NONE            = 0     # Filter Accept list is not used to determine which advertiser to connect to. Peer_Address_Type and Peer_Address shall be used.
13    FILTER_ACCEPT_LIST_ONLY = 1     # Filter Accept list is used to determine which advertiser to connect to. Peer_Address_Type and Peer_Address shall be ignored.
14
15class Initiator:
16    """
17        A Initiator can create and destroy connections.
18        - Set parameters for connection creation
19        - Monitor connection status
20        - Terminate connection
21    """
22    """
23        Constructor:
24            transport         - PTTT_nwtsim object
25            initiator         - Number; Device identifier
26            peer              - Number; Device identifier
27            trace             - Trace object
28            initiatorAddress  - ExtendedAddressType enum holding the address of the initiator (only the address type is used)
29            peerAddress       - IdentityAddressType enum holding the address of the peer (both type and address are used)
30            filterPolicy      - InitiatorFilterPolicy enum holding the type of scanning filter to apply
31            useExtended       - Use the extended advertising commands instead of legacy commands
32            initiatingPHYs    - PHYs to initiate connection on; Only used if useExtended is set
33    """
34    def __init__(self, transport, initiator, peer, trace, initiatorAddress, peerAddress, filterPolicy=InitiatorFilterPolicy.FILTER_NONE, useExtended=False, initiatingPHYs=0x00):
35        self.transport = transport;
36        self.initiator = initiator;
37        self.peer = peer;
38        self.trace = trace;
39        self.initiatorAddress = initiatorAddress;
40        self.peerAddress = peerAddress;
41        self.filterPolicy = filterPolicy;
42        self.useExtended = useExtended;
43        self.initiatingPHYs = initiatingPHYs;
44
45        self.RPAs = [ [ 0 for _ in range(6) ], [ 0 for _ in range(6) ] ];
46        self.handles = [-1, -1];
47        self.reasons = [-1, -1];
48        self.txPhys, self.rxPhys = -1, -1;
49
50        """
51           Note: intervalMin shall not be greater than intervalMax.
52                 supervisionTimeout in milliseconds shall be larger than (1 + latency) * intervalMax * 2, where intervalMax is in milliseconds.
53
54                 With intervalMax =    6 -> supervisionTimeout >   12 * 1.25 ms =   15 ms (with a latency of zero)
55                      intervalMax =   24 -> supervisionTimeout >   48 * 1.25 ms =   60 ms (with a latency of zero)
56                      intervalMax = 3200 -> supervisionTimeout > 6400 * 1.25 ms = 8000 ms (with a latency of zero)
57        """
58        self.scanInterval       = 32; # Scan Interval := 32 x 0.625 ms = 20 ms
59        self.scanWindow         = 32; # Scan Window   := 32 x 0.625 ms = 20 ms
60        self.intervalMin        = 24; # Minimum Connection interval := 24 x 1.25 ms = 30 ms
61        self.intervalMax        = 24; # Maximum Connection interval := 24 x 1.25 ms = 30 ms
62        self.latency            = 0;  # Don't allow peer to skip any connection events
63        self.supervisionTimeout = 25; # Supervision timeout := 25 x 10 ms = 250 ms.
64        self.minCeLen           = 0;  # Minimum Connection event interval = 0 x 0.625 ms = 0 ms
65        self.maxCeLen           = 0;  # Maximum Connection event interval = 0 x 0.625 ms = 0 ms
66
67        self.status             = 0;
68        self.prevInterval       = 0;
69        self.pre_connected, self.__rolesSwitched = False, False;
70        self.__savedEvent = [ None, None ];
71
72        self.updInitiatorRequest, self.updPeerRequest, self.checkPrematureDisconnect = False, False, True;
73
74        self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, self.cpr_latency, self.cpr_timeout = -1, 0, 0, 0, 0;
75
76    """
77        Private Event handler procedures...
78
79        Check for LE Connection Complete Event | LE Enhanced Connection Complete Event...
80    """
81    def __hasConnectionCompleteEvent(self, idx, timeout):
82
83        handle, role, interval = -1, -1, -1;
84        localRPA = Address(None, None);
85
86        success = has_event(self.transport, idx, timeout)[0];
87        if success:
88            event = get_event(self.transport, idx, 200);
89            self.trace.trace(7, str(event));
90            if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_COMPLETE:
91                self.status, handle, role, address, interval, latency, timeout, accuracy = event.decode();
92                success = self.status == 0;
93            elif event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE:
94                self.status, handle, role, address, localRPA, peerRPA, interval, latency, timeout, accuracy = event.decode();
95                success = self.status == 0;
96            else:
97                success = False;
98        return success, handle, role, localRPA.address, interval;
99
100    """
101        Check for Disconnect Complete Event...
102    """
103    def __hasDisconnectCompleteEvent(self, idx, timeout):
104
105        handle, reason = -1, -1;
106
107        success, number_of_events = has_event(self.transport, idx, timeout);
108
109        while number_of_events > 0:
110            event = get_event(self.transport, idx, 200);
111            self.trace.trace(7, str(event));
112            if event.event == Events.BT_HCI_EVT_DISCONN_COMPLETE:
113                self.status, handle, reason = event.decode();
114                success = self.status == 0;
115                break
116            else:
117                # When waiting for a disconnect event, we usually don't care about
118                # other events. Discard them.
119                self.trace.trace(3, "Warning: Discarding event before disconnect % s" % str(event));
120                success = False;
121            number_of_events -= 1
122        return success, handle, reason;
123
124    """
125        Check for LE PHY Update Complete Event...
126    """
127    def __hasPhysUpdateCompleteEvent(self, idx, timeout):
128
129        txPhys, rxPhys = -1, -1;
130
131        success = has_event(self.transport, idx, timeout)[0];
132        if success:
133            event = get_event(self.transport, idx, 200);
134            self.trace.trace(7, str(event));
135            if event.subEvent == MetaEvents.BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE:
136                self.status, handle, txPhys, rxPhys = event.decode();
137                success = self.status == 0;
138            else:
139                success = False;
140        return success, txPhys, rxPhys;
141
142    """
143        Check for LE Connection Parameter Update Request Event...
144    """
145    def __hasConnectionParamRequestEvent(self, idx, timeout):
146
147        handle, minInterval, maxInterval, latency, supervisionTimeout = -1, -1, -1, -1, -1;
148
149        success = has_event(self.transport, idx, timeout)[0];
150        while success:
151            event = get_event(self.transport, idx, 200);
152            self.trace.trace(7, str(event));
153            if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_PARAM_REQ:
154                handle, minInterval, maxInterval, latency, supervisionTimeout = event.decode();
155                break;
156            else:
157                """
158                    We didn't get a LE Connection Parameter Update Request Event...
159                    We could receive a LE Connection Parameter Update Complete Event instead - save it!
160                """
161                self.__savedEvent[idx] = event;
162                success = has_event(self.transport, idx, timeout)[0];
163
164        return success, handle, minInterval, maxInterval, latency, supervisionTimeout;
165
166    """
167        Check for LE Connection Parameter Update Complete Event...
168    """
169    def __hasConnectionUpdateCompleteEvent(self, idx, timeout):
170        status, handle, interval, latency, visionTimeout = -1, -1, -1, -1, -1;
171
172        success = not self.__savedEvent[idx] is None;
173        if success:
174            event = self.__savedEvent[idx];
175            self.__savedEvent[idx] = None;
176        else:
177            success = has_event(self.transport, idx, timeout)[0];
178            if success:
179                event = get_event(self.transport, idx, 200);
180
181        if success:
182            self.trace.trace(7, str(event));
183            if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE:
184                status, handle, interval, latency, visionTimeout = event.decode();
185                success = status == 0;
186
187        return success, status, handle, interval, latency, visionTimeout;
188
189    """
190        Send Create Connection command...
191    """
192    def __initiating(self):
193
194        try:
195            if self.useExtended:
196                # Count number of PHYs (via number of bits set) and create appropriate array arguments
197                PHYCount = bin(self.initiatingPHYs).count("1")
198                if PHYCount < 1 or PHYCount > 3:
199                    self.trace.trace(3, "Initiating connection failed: Invalid initiatingPHYs value")
200                    return False
201
202                scanInterval = [self.scanInterval for _ in range(PHYCount)]
203                scanWindow = [self.scanWindow for _ in range(PHYCount)]
204                connIntervalMin = [self.intervalMin for _ in range(PHYCount)]
205                connIntervalMax = [self.intervalMax for _ in range(PHYCount)]
206                maxLatency = [self.latency for _ in range(PHYCount)]
207                supervisionTimeout = [self.supervisionTimeout for _ in range(PHYCount)]
208                minCeLen =  [self.minCeLen for _ in range(PHYCount)]
209                maxCeLen =  [self.maxCeLen for _ in range(PHYCount)]
210
211                le_extended_create_connection(self.transport, self.initiator, self.filterPolicy, self.initiatorAddress.type, self.peerAddress.type,
212                                              self.peerAddress.address, self.initiatingPHYs, scanInterval, scanWindow, connIntervalMin, connIntervalMax,
213                                              maxLatency, supervisionTimeout, minCeLen, maxCeLen, 200)
214            else:
215                le_create_connection(self.transport, self.initiator, self.scanInterval, self.scanWindow, self.filterPolicy, self.peerAddress.type, \
216                                     self.peerAddress.address, self.initiatorAddress.type, self.intervalMin, self.intervalMax, self.latency, \
217                                     self.supervisionTimeout, self.minCeLen, self.maxCeLen, 200);
218
219            event = get_event(self.transport, self.initiator, 200);
220            self.trace.trace(7, str(event));
221            success = event.isCommandStatus();
222            if success:
223                self.status = event.decode()[-1];
224                success = self.status == 0;
225        except Exception as e:
226            self.trace.trace(3, "LE (Extended) Create Connection Command failed: %s" % str(e));
227            success = False;
228
229        return success;
230
231    """
232        Send Disconnect command...
233    """
234    def __terminating(self, reason):
235
236        try:
237            success = False;
238            disconnect(self.transport, self.initiator, self.handles[0], reason, 200);
239            while has_event(self.transport, self.initiator, 200)[0]:
240                event = get_event(self.transport, self.initiator, 200);
241                self.trace.trace(7, str(event));
242                if event.isCommandStatus():
243                    opcode, status = event.decode()[1:];
244                    if opcode == HCICommands.BT_HCI_OP_DISCONNECT:
245                        self.status = status;
246                        success = status == 0;
247                        break;
248
249        except Exception as e:
250            self.trace.trace(3, "Disconnect Command failed: %s" % str(e));
251            success = False;
252
253        return success;
254
255    """
256        Send Create Connection Cancel command...
257    """
258    def __cancel(self):
259
260        status = le_create_connection_cancel(self.transport, self.initiator, 200);
261        event = get_event(self.transport, self.initiator, 200);
262        self.trace.trace(7, str(event));
263        return event.isCommandComplete() and (status == 0);
264
265    """
266        Send LE Connection Update command...
267    """
268    def __update(self, minInterval, maxInterval, latency, timeout):
269
270        try:
271            if 4 * timeout <= (1+latency) * maxInterval:
272                timeout = (((1+latency) * maxInterval) + 7) / 4;
273
274            self.status = le_connection_update(self.transport, self.initiator, self.handles[0], minInterval, maxInterval, latency, timeout, self.minCeLen, self.maxCeLen, 200);
275
276            event = get_event(self.transport, self.initiator, 200);
277            self.trace.trace(7, str(event));
278            success = event.isCommandStatus() and (self.status == 0);
279        except Exception as e:
280            self.trace.trace(3, "LE Connection Update Command failed: %s" % str(e));
281            success = False;
282
283        return success;
284
285    """
286        Send LE Set PHY command...
287    """
288    def __updatePhys(self, allPhys, txPhys, rxPhys, optionPhys):
289
290        try:
291            self.status = le_set_phy(self.transport, self.initiator, self.handles[0], allPhys, txPhys, rxPhys, optionPhys, 200);
292
293            event = get_event(self.transport, self.initiator, 200);
294            self.trace.trace(7, str(event));
295            success = event.isCommandStatus() and (self.status == 0);
296        except Exception as e:
297            self.trace.trace(3, "LE Set PHY Command failed: %s" % str(e));
298            success = False;
299
300        return success;
301
302    def __updatedPhys(self):
303        """
304            Initiator should receive a LE_PHY_Update_Complete event to signal that the command has been effectuated (could be no change)...
305        """
306        success, txPhys, rxPhys = self.__hasPhysUpdateCompleteEvent(self.initiator, 1000);
307
308        if success and ((txPhys != self.txPhys) or (rxPhys != self.rxPhys)):
309            self.txPhys = txPhys; self.rxPhys = rxPhys;
310            """
311                If Phys has changed - Peer should also receive a LE_PHY_Update_Complete event to signal that the command has been effectuated...
312            """
313            if not self.peer is None:
314                success, txPhys, rxPhys = self.__hasPhysUpdateCompleteEvent(self.peer, 200);
315
316        return success;
317
318    """
319        Carry out first part of the connect procedure...
320        Send connect request and wait for command status event
321    """
322    def preConnect(self):
323        self.pre_connected = self.__initiating();
324        return self.pre_connected;
325
326    """
327        Carry out last part of the connect procedure...
328        Wait for (connection complete | enhanced connection complete) events
329    """
330    def postConnect(self, timeout=600):
331        success = self.pre_connected;
332        """
333            Both initiator and peer can receive a LE Connection Complete Event if connection succeeds...
334        """
335        if success:
336            """
337                Check for LE Connection Complete Event | LE Enhanced Connection Complete Event in initiator...
338            """
339            initiatorConnected, handle, role, localRPA, interval = self.__hasConnectionCompleteEvent(self.initiator, timeout);
340            if initiatorConnected:
341                self.handles[0] = handle; self.prevInterval = interval;
342                self.RPAs[0] = localRPA[ : ];
343                self.trace.trace(7, "%s is %s" % ('UpperTester' if self.initiator == 0 else 'LowerTester', 'CENTRAL' if role == 0 else 'PERIPHERAL'));
344                """
345                    Check for LE Connection Complete Event | LE Enhanced Connection Complete Event in peer...
346                """
347                if not self.peer is None:
348                    peerConnected, handle, role, localRPA, interval = self.__hasConnectionCompleteEvent(self.peer, timeout);
349                    if peerConnected:
350                        self.handles[1] = handle;
351                        self.RPAs[1] = localRPA[ : ];
352                        self.trace.trace(7, "%s is %s" % ('UpperTester' if self.peer == 0 else 'LowerTester', 'CENTRAL' if role == 0 else 'PERIPHERAL'));
353                else:
354                    peerConnected = True;
355            """
356                Check for Disconnection Complete Event in initiator...
357            """
358            if self.checkPrematureDisconnect:
359                initiatorDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.initiator, 200);
360                if initiatorDisconnected:
361                    self.handles[0] = -1;
362                    initiatorConnected = False;
363
364        return success and initiatorConnected and peerConnected;
365
366    """
367        Carry out the connect procedure...
368    """
369    def connect(self, timeout=600):
370        success = self.preConnect();
371        success = success and self.postConnect(timeout);
372        if success:
373            self.txPhys = self.rxPhys = 1;
374
375        return success;
376
377    """
378        Disconnect...
379    """
380    def disconnect(self, reason):
381        success = self.__terminating(reason) if not self.handles[0] == -1 else False;
382        """
383            Both initiator and peer can receive a Disconnection Complete Events...
384        """
385        if success:
386            initiatorDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.initiator, 100 * int((99 + 10 * self.prevInterval * 1.25) / 100));
387            if initiatorDisconnected:
388                self.handles[0] = -1; self.reasons[0] = reason;
389
390            if not self.peer is None:
391                peerDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.peer, 200);
392                if peerDisconnected:
393                    self.handles[1] = -1; self.reasons[1] = reason;
394            else:
395                peerDisconnected = True;
396
397        return success and initiatorDisconnected and peerDisconnected;
398
399    def cancelConnect(self):
400        success = self.pre_connected;
401
402        if success:
403            success = self.__cancel();
404            if success:
405                self.__hasConnectionCompleteEvent(self.initiator, 200)[0];
406
407        return success;
408
409    """
410        Update connection parameters...
411    """
412    def update(self, minInterval, maxInterval, latency, timeout):
413        self.updInitiatorRequest = self.__update(minInterval, maxInterval, latency, timeout) if not self.handles[0] == -1 else False;
414        if self.updInitiatorRequest:
415            if not self.peer is None:
416                # NOTE: Wait upto 3 intervals for Connection Parameter Response to be dispatched on air
417                # FIXME: Using 10 intervals to pass incorrect LL/CON/CEN/BV-27-C implementation, where TST is generating the LL_REJECT_EXT_IND
418                #        instead of controller detecting the Different Transaction Collision.
419                #        Zephyr controller as tester will not generate Connection Parameter Request if it is in Channel Map procedure
420                #        and waiting for instant to pass.
421                self.updPeerRequest, self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, self.cpr_latency, self.cpr_timeout = \
422                    self.__hasConnectionParamRequestEvent(self.peer, 100 * int((99 + 10 * self.prevInterval * 1.25) / 100));
423            else:
424                self.updPeerRequest = False;
425        return self.updInitiatorRequest;
426
427    """
428        Accept the request for connection parameters update, by responding to the LE Remote Connection Parameter Request Event...
429    """
430    def acceptUpdate(self):
431        success = self.updInitiatorRequest and self.updPeerRequest;
432
433        if success:
434            """
435                Send a LL_CONNECTION_PARAM_RSP as a reaction to the LE Remote Connection Parameter Request Event...
436            """
437            status, handle = le_remote_connection_parameter_request_reply(self.transport, self.peer, self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, \
438                                                                          self.cpr_latency, self.cpr_timeout, self.minCeLen, self.maxCeLen, 100);
439            success = status == 0;
440            event = get_event(self.transport, self.peer, 200);
441            self.trace.trace(7, str(event));
442            success = success and (event.event == Events.BT_HCI_EVT_CMD_COMPLETE);
443
444        return success;
445
446    """
447        Reject the request for connection parameters update, by responding to the LE Remote Connection Parameter Request Event...
448    """
449    def rejectUpdate(self, reason):
450        success = self.updInitiatorRequest and self.updPeerRequest;
451
452        if success:
453            """
454                Send a LL_REJECT_EXT_IND as a reaction to the LE Remote Connection Parameter Request Event...
455            """
456            status, handle = le_remote_connection_parameter_request_negative_reply(self.transport, self.peer, self.cpr_handle, reason, 200);
457            success = status == 0;
458            event = get_event(self.transport, self.peer, 200);
459            self.trace.trace(7, str(event));
460            success = success and (event.event == Events.BT_HCI_EVT_CMD_COMPLETE);
461
462        return success;
463
464    def updated(self):
465        success = self.updInitiatorRequest;
466
467        if success:
468            """
469                Check for LE Connection Update Complete Event in initiator...
470                NOTE: Timing depends on the connect interval in effect - update is usually 12 events after previous reponse event
471                      wait upto 3 intervals each for Connection Parameter Response and Connection Update Indication to be dispatched on air
472                      then wait another 6 intervals to pass the Connection Update instant
473                      Hence wait for 12 intervals before response timeout
474            """
475            initiatorUpdated, self.status, handle, interval, latency, timeout = \
476                self.__hasConnectionUpdateCompleteEvent(self.initiator, 100 * int((99 + (12 * self.prevInterval + self.cpr_maxInterval) * 1.25) / 100));
477            initiatorUpdated = initiatorUpdated and (self.handles[0] == handle);
478            if initiatorUpdated:
479                self.intervalMin = self.intervalMax = self.prevInterval = interval;
480                self.latency = latency;
481                self.supervisionTimeout = timeout;
482
483            """
484                Check for LE Connection Update Complete Event in peer...
485            """
486            if not self.peer is None:
487                peerUpdated, status, handle, interval, latency, timeout = self.__hasConnectionUpdateCompleteEvent(self.peer, 100 * int((99 + interval * 1.25) / 100));
488                peerUpdated = peerUpdated and (self.handles[1] == handle);
489            else:
490                peerUpdated = True;
491
492            success = initiatorUpdated and peerUpdated;
493
494        return success;
495
496    def updatePhys(self, allPhys, txPhys, rxPhys, optionPhys):
497        success = self.__updatePhys(allPhys, txPhys, rxPhys, optionPhys) if not self.handles[0] == -1 else False;
498
499        if success:
500            success = self.__updatedPhys();
501
502        return success;
503
504    """
505        Switch roles of initiator and peer - used to let the peer do an update ...
506    """
507    def switchRoles(self):
508        self.initiator, self.peer = self.peer, self.initiator;
509        self.handles[0], self.handles[1] = self.handles[1], self.handles[0];
510        self.__rolesSwitched ^= True;
511
512    """
513        Reset roles of initiator and peer - if they vere switched ...
514    """
515    def resetRoles(self):
516        if self.__rolesSwitched:
517            self.switchRoles();
518
519    """
520        Obtain local Resolvable Private Address from LE Enhanced Connection Complete Event ...
521    """
522    def localRPA(self):
523        return self.RPAs[0];
524
525    """
526        Obtain peer Resolvable Private Address from LE Enhanced Connection Complete Event ...
527    """
528    def peerRPA(self):
529        return self.RPAs[1];
530