Home | History | Annotate | Line # | Download | only in examples
      1  1.1  christos #!/usr/bin/python3
      2  1.1  christos #
      3  1.1  christos # Example nfcpy to wpa_supplicant wrapper for DPP NFC operations
      4  1.1  christos # Copyright (c) 2012-2013, Jouni Malinen <j (at] w1.fi>
      5  1.1  christos # Copyright (c) 2019-2020, The Linux Foundation
      6  1.1  christos #
      7  1.1  christos # This software may be distributed under the terms of the BSD license.
      8  1.1  christos # See README for more details.
      9  1.1  christos 
     10  1.1  christos import binascii
     11  1.1  christos import errno
     12  1.1  christos import os
     13  1.1  christos import struct
     14  1.1  christos import sys
     15  1.1  christos import time
     16  1.1  christos import threading
     17  1.1  christos import argparse
     18  1.1  christos 
     19  1.1  christos import nfc
     20  1.1  christos import ndef
     21  1.1  christos 
     22  1.1  christos import logging
     23  1.1  christos 
     24  1.1  christos scriptsdir = os.path.dirname(os.path.realpath(sys.modules[__name__].__file__))
     25  1.1  christos sys.path.append(os.path.join(scriptsdir, '..', '..', 'wpaspy'))
     26  1.1  christos import wpaspy
     27  1.1  christos 
     28  1.1  christos wpas_ctrl = '/var/run/wpa_supplicant'
     29  1.1  christos ifname = None
     30  1.1  christos init_on_touch = False
     31  1.1  christos in_raw_mode = False
     32  1.1  christos prev_tcgetattr = 0
     33  1.1  christos no_input = False
     34  1.1  christos continue_loop = True
     35  1.1  christos terminate_now = False
     36  1.1  christos summary_file = None
     37  1.1  christos success_file = None
     38  1.1  christos netrole = None
     39  1.1  christos operation_success = False
     40  1.1  christos mutex = threading.Lock()
     41  1.1  christos 
     42  1.1  christos C_NORMAL = '\033[0m'
     43  1.1  christos C_RED = '\033[91m'
     44  1.1  christos C_GREEN = '\033[92m'
     45  1.1  christos C_YELLOW = '\033[93m'
     46  1.1  christos C_BLUE = '\033[94m'
     47  1.1  christos C_MAGENTA = '\033[95m'
     48  1.1  christos C_CYAN = '\033[96m'
     49  1.1  christos 
     50  1.1  christos def summary(txt, color=None):
     51  1.1  christos     with mutex:
     52  1.1  christos         if color:
     53  1.1  christos             print(color + txt + C_NORMAL)
     54  1.1  christos         else:
     55  1.1  christos             print(txt)
     56  1.1  christos         if summary_file:
     57  1.1  christos             with open(summary_file, 'a') as f:
     58  1.1  christos                 f.write(txt + "\n")
     59  1.1  christos 
     60  1.1  christos def success_report(txt):
     61  1.1  christos     summary(txt)
     62  1.1  christos     if success_file:
     63  1.1  christos         with open(success_file, 'a') as f:
     64  1.1  christos             f.write(txt + "\n")
     65  1.1  christos 
     66  1.1  christos def wpas_connect():
     67  1.1  christos     ifaces = []
     68  1.1  christos     if os.path.isdir(wpas_ctrl):
     69  1.1  christos         try:
     70  1.1  christos             ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
     71  1.1  christos         except OSError as error:
     72  1.1  christos             summary("Could not find wpa_supplicant: %s", str(error))
     73  1.1  christos             return None
     74  1.1  christos 
     75  1.1  christos     if len(ifaces) < 1:
     76  1.1  christos         summary("No wpa_supplicant control interface found")
     77  1.1  christos         return None
     78  1.1  christos 
     79  1.1  christos     for ctrl in ifaces:
     80  1.1  christos         if ifname and ifname not in ctrl:
     81  1.1  christos             continue
     82  1.1  christos         if os.path.basename(ctrl).startswith("p2p-dev-"):
     83  1.1  christos             # skip P2P management interface
     84  1.1  christos             continue
     85  1.1  christos         try:
     86  1.1  christos             summary("Trying to use control interface " + ctrl)
     87  1.1  christos             wpas = wpaspy.Ctrl(ctrl)
     88  1.1  christos             return wpas
     89  1.1  christos         except Exception as e:
     90  1.1  christos             pass
     91  1.1  christos     summary("Could not connect to wpa_supplicant")
     92  1.1  christos     return None
     93  1.1  christos 
     94  1.1  christos def dpp_nfc_uri_process(uri):
     95  1.1  christos     wpas = wpas_connect()
     96  1.1  christos     if wpas is None:
     97  1.1  christos         return False
     98  1.1  christos     peer_id = wpas.request("DPP_NFC_URI " + uri)
     99  1.1  christos     if "FAIL" in peer_id:
    100  1.1  christos         summary("Could not parse DPP URI from NFC URI record", color=C_RED)
    101  1.1  christos         return False
    102  1.1  christos     peer_id = int(peer_id)
    103  1.1  christos     summary("peer_id=%d for URI from NFC Tag: %s" % (peer_id, uri))
    104  1.1  christos     cmd = "DPP_AUTH_INIT peer=%d" % peer_id
    105  1.1  christos     global enrollee_only, configurator_only, config_params
    106  1.1  christos     if enrollee_only:
    107  1.1  christos         cmd += " role=enrollee"
    108  1.1  christos     elif configurator_only:
    109  1.1  christos         cmd += " role=configurator"
    110  1.1  christos     if config_params:
    111  1.1  christos         cmd += " " + config_params
    112  1.1  christos     summary("Initiate DPP authentication: " + cmd)
    113  1.1  christos     res = wpas.request(cmd)
    114  1.1  christos     if "OK" not in res:
    115  1.1  christos         summary("Failed to initiate DPP Authentication", color=C_RED)
    116  1.1  christos         return False
    117  1.1  christos     summary("DPP Authentication initiated")
    118  1.1  christos     return True
    119  1.1  christos 
    120  1.1  christos def dpp_hs_tag_read(record):
    121  1.1  christos     wpas = wpas_connect()
    122  1.1  christos     if wpas is None:
    123  1.1  christos         return False
    124  1.1  christos     summary(record)
    125  1.1  christos     if len(record.data) < 5:
    126  1.1  christos         summary("Too short DPP HS", color=C_RED)
    127  1.1  christos         return False
    128  1.1  christos     if record.data[0] != 0:
    129  1.1  christos         summary("Unexpected URI Identifier Code", color=C_RED)
    130  1.1  christos         return False
    131  1.1  christos     uribuf = record.data[1:]
    132  1.1  christos     try:
    133  1.1  christos         uri = uribuf.decode()
    134  1.1  christos     except:
    135  1.1  christos         summary("Invalid URI payload", color=C_RED)
    136  1.1  christos         return False
    137  1.1  christos     summary("URI: " + uri)
    138  1.1  christos     if not uri.startswith("DPP:"):
    139  1.1  christos         summary("Not a DPP URI", color=C_RED)
    140  1.1  christos         return False
    141  1.1  christos     return dpp_nfc_uri_process(uri)
    142  1.1  christos 
    143  1.1  christos def get_status(wpas, extra=None):
    144  1.1  christos     if extra:
    145  1.1  christos         extra = "-" + extra
    146  1.1  christos     else:
    147  1.1  christos         extra = ""
    148  1.1  christos     res = wpas.request("STATUS" + extra)
    149  1.1  christos     lines = res.splitlines()
    150  1.1  christos     vals = dict()
    151  1.1  christos     for l in lines:
    152  1.1  christos         try:
    153  1.1  christos             [name, value] = l.split('=', 1)
    154  1.1  christos         except ValueError:
    155  1.1  christos             summary("Ignore unexpected status line: %s" % l)
    156  1.1  christos             continue
    157  1.1  christos         vals[name] = value
    158  1.1  christos     return vals
    159  1.1  christos 
    160  1.1  christos def get_status_field(wpas, field, extra=None):
    161  1.1  christos     vals = get_status(wpas, extra)
    162  1.1  christos     if field in vals:
    163  1.1  christos         return vals[field]
    164  1.1  christos     return None
    165  1.1  christos 
    166  1.1  christos def own_addr(wpas):
    167  1.1  christos     addr = get_status_field(wpas, "address")
    168  1.1  christos     if addr is None:
    169  1.1  christos         addr = get_status_field(wpas, "bssid[0]")
    170  1.1  christos     return addr
    171  1.1  christos 
    172  1.1  christos def dpp_bootstrap_gen(wpas, type="qrcode", chan=None, mac=None, info=None,
    173  1.1  christos                       curve=None, key=None):
    174  1.1  christos     cmd = "DPP_BOOTSTRAP_GEN type=" + type
    175  1.1  christos     if chan:
    176  1.1  christos         cmd += " chan=" + chan
    177  1.1  christos     if mac:
    178  1.1  christos         if mac is True:
    179  1.1  christos             mac = own_addr(wpas)
    180  1.1  christos         if mac is None:
    181  1.1  christos             summary("Could not determine local MAC address for bootstrap info")
    182  1.1  christos         else:
    183  1.1  christos             cmd += " mac=" + mac.replace(':', '')
    184  1.1  christos     if info:
    185  1.1  christos         cmd += " info=" + info
    186  1.1  christos     if curve:
    187  1.1  christos         cmd += " curve=" + curve
    188  1.1  christos     if key:
    189  1.1  christos         cmd += " key=" + key
    190  1.1  christos     res = wpas.request(cmd)
    191  1.1  christos     if "FAIL" in res:
    192  1.1  christos         raise Exception("Failed to generate bootstrapping info")
    193  1.1  christos     return int(res)
    194  1.1  christos 
    195  1.1  christos def dpp_start_listen(wpas, freq):
    196  1.1  christos     if get_status_field(wpas, "bssid[0]"):
    197  1.1  christos         summary("Own AP freq: %s MHz" % str(get_status_field(wpas, "freq")))
    198  1.1  christos         if get_status_field(wpas, "beacon_set", extra="DRIVER") is None:
    199  1.1  christos             summary("Enable beaconing to have radio ready for RX")
    200  1.1  christos             wpas.request("DISABLE")
    201  1.1  christos             wpas.request("SET start_disabled 0")
    202  1.1  christos             wpas.request("ENABLE")
    203  1.1  christos     cmd = "DPP_LISTEN %d" % freq
    204  1.1  christos     global enrollee_only
    205  1.1  christos     global configurator_only
    206  1.1  christos     if enrollee_only:
    207  1.1  christos         cmd += " role=enrollee"
    208  1.1  christos     elif configurator_only:
    209  1.1  christos         cmd += " role=configurator"
    210  1.1  christos     global netrole
    211  1.1  christos     if netrole:
    212  1.1  christos         cmd += " netrole=" + netrole
    213  1.1  christos     summary(cmd)
    214  1.1  christos     res = wpas.request(cmd)
    215  1.1  christos     if "OK" not in res:
    216  1.1  christos         summary("Failed to start DPP listen", color=C_RED)
    217  1.1  christos         return False
    218  1.1  christos     return True
    219  1.1  christos 
    220  1.1  christos def wpas_get_nfc_uri(start_listen=True, pick_channel=False, chan_override=None):
    221  1.1  christos     listen_freq = 2412
    222  1.1  christos     wpas = wpas_connect()
    223  1.1  christos     if wpas is None:
    224  1.1  christos         return None
    225  1.1  christos     global own_id, chanlist
    226  1.1  christos     if chan_override:
    227  1.1  christos         chan = chan_override
    228  1.1  christos     else:
    229  1.1  christos         chan = chanlist
    230  1.1  christos     if chan and chan.startswith("81/"):
    231  1.1  christos         listen_freq = int(chan[3:].split(',')[0]) * 5 + 2407
    232  1.1  christos     if chan is None and get_status_field(wpas, "bssid[0]"):
    233  1.1  christos         freq = get_status_field(wpas, "freq")
    234  1.1  christos         if freq:
    235  1.1  christos             freq = int(freq)
    236  1.1  christos             if freq >= 2412 and freq <= 2462:
    237  1.1  christos                 chan = "81/%d" % ((freq - 2407) / 5)
    238  1.1  christos                 summary("Use current AP operating channel (%d MHz) as the URI channel list (%s)" % (freq, chan))
    239  1.1  christos                 listen_freq = freq
    240  1.1  christos     if chan is None and pick_channel:
    241  1.1  christos         chan = "81/6"
    242  1.1  christos         summary("Use channel 2437 MHz since no other preference provided")
    243  1.1  christos         listen_freq = 2437
    244  1.1  christos     own_id = dpp_bootstrap_gen(wpas, type="nfc-uri", chan=chan, mac=True)
    245  1.1  christos     res = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip()
    246  1.1  christos     if "FAIL" in res:
    247  1.1  christos         return None
    248  1.1  christos     if start_listen:
    249  1.1  christos         if not dpp_start_listen(wpas, listen_freq):
    250  1.1  christos             raise Exception("Failed to start listen operation on %d MHz" % listen_freq)
    251  1.1  christos     return res
    252  1.1  christos 
    253  1.1  christos def wpas_report_handover_req(uri):
    254  1.1  christos     wpas = wpas_connect()
    255  1.1  christos     if wpas is None:
    256  1.1  christos         return None
    257  1.1  christos     global own_id
    258  1.1  christos     cmd = "DPP_NFC_HANDOVER_REQ own=%d uri=%s" % (own_id, uri)
    259  1.1  christos     return wpas.request(cmd)
    260  1.1  christos 
    261  1.1  christos def wpas_report_handover_sel(uri):
    262  1.1  christos     wpas = wpas_connect()
    263  1.1  christos     if wpas is None:
    264  1.1  christos         return None
    265  1.1  christos     global own_id
    266  1.1  christos     cmd = "DPP_NFC_HANDOVER_SEL own=%d uri=%s" % (own_id, uri)
    267  1.1  christos     return wpas.request(cmd)
    268  1.1  christos 
    269  1.1  christos def dpp_handover_client(handover, alt=False):
    270  1.1  christos     summary("About to start run_dpp_handover_client (alt=%s)" % str(alt))
    271  1.1  christos     if alt:
    272  1.1  christos         handover.i_m_selector = False
    273  1.1  christos     run_dpp_handover_client(handover, alt)
    274  1.1  christos     summary("Done run_dpp_handover_client (alt=%s)" % str(alt))
    275  1.1  christos 
    276  1.1  christos def run_client_alt(handover, alt):
    277  1.1  christos     if handover.start_client_alt and not alt:
    278  1.1  christos         handover.start_client_alt = False
    279  1.1  christos         summary("Try to send alternative handover request")
    280  1.1  christos         dpp_handover_client(handover, alt=True)
    281  1.1  christos 
    282  1.1  christos class HandoverClient(nfc.handover.HandoverClient):
    283  1.1  christos     def __init__(self, handover, llc):
    284  1.1  christos         super(HandoverClient, self).__init__(llc)
    285  1.1  christos         self.handover = handover
    286  1.1  christos 
    287  1.1  christos     def recv_records(self, timeout=None):
    288  1.1  christos         msg = self.recv_octets(timeout)
    289  1.1  christos         if msg is None:
    290  1.1  christos             return None
    291  1.1  christos         records = list(ndef.message_decoder(msg, 'relax'))
    292  1.1  christos         if records and records[0].type == 'urn:nfc:wkt:Hs':
    293  1.1  christos             summary("Handover client received message '{0}'".format(records[0].type))
    294  1.1  christos             return list(ndef.message_decoder(msg, 'relax'))
    295  1.1  christos         summary("Handover client received invalid message: %s" + binascii.hexlify(msg))
    296  1.1  christos         return None
    297  1.1  christos 
    298  1.1  christos     def recv_octets(self, timeout=None):
    299  1.1  christos         start = time.time()
    300  1.1  christos         msg = bytearray()
    301  1.1  christos         while True:
    302  1.1  christos             poll_timeout = 0.1 if timeout is None or timeout > 0.1 else timeout
    303  1.1  christos             if not self.socket.poll('recv', poll_timeout):
    304  1.1  christos                 if timeout:
    305  1.1  christos                     timeout -= time.time() - start
    306  1.1  christos                     if timeout <= 0:
    307  1.1  christos                         return None
    308  1.1  christos                     start = time.time()
    309  1.1  christos                 continue
    310  1.1  christos             try:
    311  1.1  christos                 r = self.socket.recv()
    312  1.1  christos                 if r is None:
    313  1.1  christos                     return None
    314  1.1  christos                 msg += r
    315  1.1  christos             except TypeError:
    316  1.1  christos                 return b''
    317  1.1  christos             try:
    318  1.1  christos                 list(ndef.message_decoder(msg, 'strict', {}))
    319  1.1  christos                 return bytes(msg)
    320  1.1  christos             except ndef.DecodeError:
    321  1.1  christos                 if timeout:
    322  1.1  christos                     timeout -= time.time() - start
    323  1.1  christos                     if timeout <= 0:
    324  1.1  christos                         return None
    325  1.1  christos                     start = time.time()
    326  1.1  christos                 continue
    327  1.1  christos         return None
    328  1.1  christos 
    329  1.1  christos def run_dpp_handover_client(handover, alt=False):
    330  1.1  christos     chan_override = None
    331  1.1  christos     if alt:
    332  1.1  christos         chan_override = handover.altchanlist
    333  1.1  christos         handover.alt_proposal_used = True
    334  1.1  christos     global test_uri, test_alt_uri
    335  1.1  christos     if test_uri:
    336  1.1  christos         summary("TEST MODE: Using specified URI (alt=%s)" % str(alt))
    337  1.1  christos         uri = test_alt_uri if alt else test_uri
    338  1.1  christos     else:
    339  1.1  christos         uri = wpas_get_nfc_uri(start_listen=False, chan_override=chan_override)
    340  1.1  christos     if uri is None:
    341  1.1  christos         summary("Cannot start handover client - no bootstrap URI available",
    342  1.1  christos                 color=C_RED)
    343  1.1  christos         return
    344  1.1  christos     handover.my_uri = uri
    345  1.1  christos     uri = ndef.UriRecord(uri)
    346  1.1  christos     summary("NFC URI record for DPP: " + str(uri))
    347  1.1  christos     carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data)
    348  1.1  christos     global test_crn
    349  1.1  christos     if test_crn:
    350  1.1  christos         prev, = struct.unpack('>H', test_crn)
    351  1.1  christos         summary("TEST MODE: Use specified crn %d" % prev)
    352  1.1  christos         crn = test_crn
    353  1.1  christos         test_crn = struct.pack('>H', prev + 0x10)
    354  1.1  christos     else:
    355  1.1  christos         crn = os.urandom(2)
    356  1.1  christos     hr = ndef.HandoverRequestRecord(version="1.4", crn=crn)
    357  1.1  christos     hr.add_alternative_carrier('active', carrier.name)
    358  1.1  christos     message = [hr, carrier]
    359  1.1  christos     summary("NFC Handover Request message for DPP: " + str(message))
    360  1.1  christos 
    361  1.1  christos     if handover.peer_crn is not None and not alt:
    362  1.1  christos         summary("NFC handover request from peer was already received - do not send own[1]")
    363  1.1  christos         return
    364  1.1  christos     if handover.client:
    365  1.1  christos         summary("Use already started handover client")
    366  1.1  christos         client = handover.client
    367  1.1  christos     else:
    368  1.1  christos         summary("Start handover client")
    369  1.1  christos         client = HandoverClient(handover, handover.llc)
    370  1.1  christos         try:
    371  1.1  christos             summary("Trying to initiate NFC connection handover")
    372  1.1  christos             client.connect()
    373  1.1  christos             summary("Connected for handover")
    374  1.1  christos         except nfc.llcp.ConnectRefused:
    375  1.1  christos             summary("Handover connection refused")
    376  1.1  christos             client.close()
    377  1.1  christos             return
    378  1.1  christos         except Exception as e:
    379  1.1  christos             summary("Other exception: " + str(e))
    380  1.1  christos             client.close()
    381  1.1  christos             return
    382  1.1  christos         handover.client = client
    383  1.1  christos 
    384  1.1  christos     if handover.peer_crn is not None and not alt:
    385  1.1  christos         summary("NFC handover request from peer was already received - do not send own[2] (except alt)")
    386  1.1  christos         run_client_alt(handover, alt)
    387  1.1  christos         return
    388  1.1  christos 
    389  1.1  christos     summary("Sending handover request")
    390  1.1  christos 
    391  1.1  christos     handover.my_crn_ready = True
    392  1.1  christos 
    393  1.1  christos     if not client.send_records(message):
    394  1.1  christos         handover.my_crn_ready = False
    395  1.1  christos         summary("Failed to send handover request", color=C_RED)
    396  1.1  christos         run_client_alt(handover, alt)
    397  1.1  christos         return
    398  1.1  christos 
    399  1.1  christos     handover.my_crn, = struct.unpack('>H', crn)
    400  1.1  christos 
    401  1.1  christos     summary("Receiving handover response")
    402  1.1  christos     try:
    403  1.1  christos         start = time.time()
    404  1.1  christos         message = client.recv_records(timeout=3.0)
    405  1.1  christos         end = time.time()
    406  1.1  christos         summary("Received {} record(s) in {} seconds".format(len(message) if message is not None else -1, end - start))
    407  1.1  christos     except Exception as e:
    408  1.1  christos         # This is fine if we are the handover selector
    409  1.1  christos         if handover.hs_sent:
    410  1.1  christos             summary("Client receive failed as expected since I'm the handover server: %s" % str(e))
    411  1.1  christos         elif handover.alt_proposal_used and not alt:
    412  1.1  christos             summary("Client received failed for initial proposal as expected since alternative proposal was also used: %s" % str(e))
    413  1.1  christos         else:
    414  1.1  christos             summary("Client receive failed: %s" % str(e), color=C_RED)
    415  1.1  christos         message = None
    416  1.1  christos     if message is None:
    417  1.1  christos         if handover.hs_sent:
    418  1.1  christos             summary("No response received as expected since I'm the handover server")
    419  1.1  christos         elif handover.alt_proposal_used and not alt:
    420  1.1  christos             summary("No response received for initial proposal as expected since alternative proposal was also used")
    421  1.1  christos         elif handover.try_own and not alt:
    422  1.1  christos             summary("No response received for initial proposal as expected since alternative proposal will also be sent")
    423  1.1  christos         else:
    424  1.1  christos             summary("No response received", color=C_RED)
    425  1.1  christos         run_client_alt(handover, alt)
    426  1.1  christos         return
    427  1.1  christos     summary("Received message: " + str(message))
    428  1.1  christos     if len(message) < 1 or \
    429  1.1  christos        not isinstance(message[0], ndef.HandoverSelectRecord):
    430  1.1  christos         summary("Response was not Hs - received: " + message.type)
    431  1.1  christos         return
    432  1.1  christos 
    433  1.1  christos     summary("Received handover select message")
    434  1.1  christos     summary("alternative carriers: " + str(message[0].alternative_carriers))
    435  1.1  christos     if handover.i_m_selector:
    436  1.1  christos         summary("Ignore the received select since I'm the handover selector")
    437  1.1  christos         run_client_alt(handover, alt)
    438  1.1  christos         return
    439  1.1  christos 
    440  1.1  christos     if handover.alt_proposal_used and not alt:
    441  1.1  christos         summary("Ignore received handover select for the initial proposal since alternative proposal was sent")
    442  1.1  christos         client.close()
    443  1.1  christos         return
    444  1.1  christos 
    445  1.1  christos     dpp_found = False
    446  1.1  christos     for carrier in message:
    447  1.1  christos         if isinstance(carrier, ndef.HandoverSelectRecord):
    448  1.1  christos             continue
    449  1.1  christos         summary("Remote carrier type: " + carrier.type)
    450  1.1  christos         if carrier.type == "application/vnd.wfa.dpp":
    451  1.1  christos             if len(carrier.data) == 0 or carrier.data[0] != 0:
    452  1.1  christos                 summary("URI Identifier Code 'None' not seen", color=C_RED)
    453  1.1  christos                 continue
    454  1.1  christos             summary("DPP carrier type match - send to wpa_supplicant")
    455  1.1  christos             dpp_found = True
    456  1.1  christos             uri = carrier.data[1:].decode("utf-8")
    457  1.1  christos             summary("DPP URI: " + uri)
    458  1.1  christos             handover.peer_uri = uri
    459  1.1  christos             if test_uri:
    460  1.1  christos                 summary("TEST MODE: Fake processing")
    461  1.1  christos                 break
    462  1.1  christos             res = wpas_report_handover_sel(uri)
    463  1.1  christos             if res is None or "FAIL" in res:
    464  1.1  christos                 summary("DPP handover report rejected", color=C_RED)
    465  1.1  christos                 break
    466  1.1  christos 
    467  1.1  christos             success_report("DPP handover reported successfully (initiator)")
    468  1.1  christos             summary("peer_id=" + res)
    469  1.1  christos             peer_id = int(res)
    470  1.1  christos             wpas = wpas_connect()
    471  1.1  christos             if wpas is None:
    472  1.1  christos                 break
    473  1.1  christos 
    474  1.1  christos             global enrollee_only
    475  1.1  christos             global config_params
    476  1.1  christos             if enrollee_only:
    477  1.1  christos                 extra = " role=enrollee"
    478  1.1  christos             elif config_params:
    479  1.1  christos                 extra = " role=configurator " + config_params
    480  1.1  christos             else:
    481  1.1  christos                 # TODO: Single Configurator instance
    482  1.1  christos                 res = wpas.request("DPP_CONFIGURATOR_ADD")
    483  1.1  christos                 if "FAIL" in res:
    484  1.1  christos                     summary("Failed to initiate Configurator", color=C_RED)
    485  1.1  christos                     break
    486  1.1  christos                 conf_id = int(res)
    487  1.1  christos                 extra = " conf=sta-dpp configurator=%d" % conf_id
    488  1.1  christos             global own_id
    489  1.1  christos             summary("Initiate DPP authentication")
    490  1.1  christos             cmd = "DPP_AUTH_INIT peer=%d own=%d" % (peer_id, own_id)
    491  1.1  christos             cmd += extra
    492  1.1  christos             res = wpas.request(cmd)
    493  1.1  christos             if "FAIL" in res:
    494  1.1  christos                 summary("Failed to initiate DPP authentication", color=C_RED)
    495  1.1  christos             break
    496  1.1  christos 
    497  1.1  christos     if not dpp_found and handover.no_alt_proposal:
    498  1.1  christos         summary("DPP carrier not seen in response - do not allow alternative proposal anymore")
    499  1.1  christos     elif not dpp_found:
    500  1.1  christos         summary("DPP carrier not seen in response - allow peer to initiate a new handover with different parameters")
    501  1.1  christos         handover.alt_proposal = True
    502  1.1  christos         handover.my_crn_ready = False
    503  1.1  christos         handover.my_crn = None
    504  1.1  christos         handover.peer_crn = None
    505  1.1  christos         handover.hs_sent = False
    506  1.1  christos         summary("Returning from dpp_handover_client")
    507  1.1  christos         return
    508  1.1  christos 
    509  1.1  christos     summary("Remove peer")
    510  1.1  christos     handover.close()
    511  1.1  christos     summary("Done with handover")
    512  1.1  christos     global only_one
    513  1.1  christos     if only_one:
    514  1.1  christos         print("only_one -> stop loop")
    515  1.1  christos         global continue_loop
    516  1.1  christos         continue_loop = False
    517  1.1  christos 
    518  1.1  christos     global no_wait
    519  1.1  christos     if no_wait or only_one:
    520  1.1  christos         summary("Trying to exit..")
    521  1.1  christos         global terminate_now
    522  1.1  christos         terminate_now = True
    523  1.1  christos 
    524  1.1  christos     summary("Returning from dpp_handover_client")
    525  1.1  christos 
    526  1.1  christos class HandoverServer(nfc.handover.HandoverServer):
    527  1.1  christos     def __init__(self, handover, llc):
    528  1.1  christos         super(HandoverServer, self).__init__(llc)
    529  1.1  christos         self.sent_carrier = None
    530  1.1  christos         self.ho_server_processing = False
    531  1.1  christos         self.success = False
    532  1.1  christos         self.llc = llc
    533  1.1  christos         self.handover = handover
    534  1.1  christos 
    535  1.1  christos     def serve(self, socket):
    536  1.1  christos         peer_sap = socket.getpeername()
    537  1.1  christos         summary("Serving handover client on remote sap {0}".format(peer_sap))
    538  1.1  christos         send_miu = socket.getsockopt(nfc.llcp.SO_SNDMIU)
    539  1.1  christos         try:
    540  1.1  christos             while socket.poll("recv"):
    541  1.1  christos                 req = bytearray()
    542  1.1  christos                 while socket.poll("recv"):
    543  1.1  christos                     r = socket.recv()
    544  1.1  christos                     if r is None:
    545  1.1  christos                         return None
    546  1.1  christos                     summary("Received %d octets" % len(r))
    547  1.1  christos                     req += r
    548  1.1  christos                     if len(req) == 0:
    549  1.1  christos                         continue
    550  1.1  christos                     try:
    551  1.1  christos                         list(ndef.message_decoder(req, 'strict', {}))
    552  1.1  christos                     except ndef.DecodeError:
    553  1.1  christos                         continue
    554  1.1  christos                     summary("Full message received")
    555  1.1  christos                     resp = self._process_request_data(req)
    556  1.1  christos                     if resp is None or len(resp) == 0:
    557  1.1  christos                         summary("No handover select to send out - wait for a possible alternative handover request")
    558  1.1  christos                         handover.alt_proposal = True
    559  1.1  christos                         req = bytearray()
    560  1.1  christos                         continue
    561  1.1  christos 
    562  1.1  christos                     for offset in range(0, len(resp), send_miu):
    563  1.1  christos                         if not socket.send(resp[offset:offset + send_miu]):
    564  1.1  christos                             summary("Failed to send handover select - connection closed")
    565  1.1  christos                             return
    566  1.1  christos                     summary("Sent out full handover select")
    567  1.1  christos                     if handover.terminate_on_hs_send_completion:
    568  1.1  christos                         handover.delayed_exit()
    569  1.1  christos 
    570  1.1  christos         except nfc.llcp.Error as e:
    571  1.1  christos             global terminate_now
    572  1.1  christos             summary("HandoverServer exception: %s" % e,
    573  1.1  christos                     color=None if e.errno == errno.EPIPE or terminate_now else C_RED)
    574  1.1  christos         finally:
    575  1.1  christos             socket.close()
    576  1.1  christos             summary("Handover serve thread exiting")
    577  1.1  christos 
    578  1.1  christos     def process_handover_request_message(self, records):
    579  1.1  christos         handover = self.handover
    580  1.1  christos         self.ho_server_processing = True
    581  1.1  christos         global in_raw_mode
    582  1.1  christos         was_in_raw_mode = in_raw_mode
    583  1.1  christos         clear_raw_mode()
    584  1.1  christos         if was_in_raw_mode:
    585  1.1  christos             print("\n")
    586  1.1  christos         summary("HandoverServer - request received: " + str(records))
    587  1.1  christos 
    588  1.1  christos         for carrier in records:
    589  1.1  christos             if not isinstance(carrier, ndef.HandoverRequestRecord):
    590  1.1  christos                 continue
    591  1.1  christos             if carrier.collision_resolution_number:
    592  1.1  christos                 handover.peer_crn = carrier.collision_resolution_number
    593  1.1  christos                 summary("peer_crn: %d" % handover.peer_crn)
    594  1.1  christos 
    595  1.1  christos         if handover.my_crn is None and handover.my_crn_ready:
    596  1.1  christos             summary("Still trying to send own handover request - wait a moment to see if that succeeds before checking crn values")
    597  1.1  christos             for i in range(10):
    598  1.1  christos                 if handover.my_crn is not None:
    599  1.1  christos                     break
    600  1.1  christos                 time.sleep(0.01)
    601  1.1  christos         if handover.my_crn is not None:
    602  1.1  christos             summary("my_crn: %d" % handover.my_crn)
    603  1.1  christos 
    604  1.1  christos         if handover.my_crn is not None and handover.peer_crn is not None:
    605  1.1  christos             if handover.my_crn == handover.peer_crn:
    606  1.1  christos                 summary("Same crn used - automatic collision resolution failed")
    607  1.1  christos                 # TODO: Should generate a new Handover Request message
    608  1.1  christos                 return ''
    609  1.1  christos             if ((handover.my_crn & 1) == (handover.peer_crn & 1) and \
    610  1.1  christos                 handover.my_crn > handover.peer_crn) or \
    611  1.1  christos                ((handover.my_crn & 1) != (handover.peer_crn & 1) and \
    612  1.1  christos                 handover.my_crn < handover.peer_crn):
    613  1.1  christos                 summary("I'm the Handover Selector Device")
    614  1.1  christos                 handover.i_m_selector = True
    615  1.1  christos             else:
    616  1.1  christos                 summary("Peer is the Handover Selector device")
    617  1.1  christos                 summary("Ignore the received request.")
    618  1.1  christos                 return ''
    619  1.1  christos 
    620  1.1  christos         hs = ndef.HandoverSelectRecord('1.4')
    621  1.1  christos         sel = [hs]
    622  1.1  christos 
    623  1.1  christos         found = False
    624  1.1  christos 
    625  1.1  christos         for carrier in records:
    626  1.1  christos             if isinstance(carrier, ndef.HandoverRequestRecord):
    627  1.1  christos                 continue
    628  1.1  christos             summary("Remote carrier type: " + carrier.type)
    629  1.1  christos             if carrier.type == "application/vnd.wfa.dpp":
    630  1.1  christos                 summary("DPP carrier type match - add DPP carrier record")
    631  1.1  christos                 if len(carrier.data) == 0 or carrier.data[0] != 0:
    632  1.1  christos                     summary("URI Identifier Code 'None' not seen", color=C_RED)
    633  1.1  christos                     continue
    634  1.1  christos                 uri = carrier.data[1:].decode("utf-8")
    635  1.1  christos                 summary("Received DPP URI: " + uri)
    636  1.1  christos 
    637  1.1  christos                 global test_uri, test_alt_uri
    638  1.1  christos                 if test_uri:
    639  1.1  christos                     summary("TEST MODE: Using specified URI")
    640  1.1  christos                     data = test_sel_uri if test_sel_uri else test_uri
    641  1.1  christos                 elif handover.alt_proposal and handover.altchanlist:
    642  1.1  christos                     summary("Use alternative channel list while processing alternative proposal from peer")
    643  1.1  christos                     data = wpas_get_nfc_uri(start_listen=False,
    644  1.1  christos                                             chan_override=handover.altchanlist,
    645  1.1  christos                                             pick_channel=True)
    646  1.1  christos                 else:
    647  1.1  christos                     data = wpas_get_nfc_uri(start_listen=False,
    648  1.1  christos                                             pick_channel=True)
    649  1.1  christos                 summary("Own URI (pre-processing): %s" % data)
    650  1.1  christos 
    651  1.1  christos                 if test_uri:
    652  1.1  christos                     summary("TEST MODE: Fake processing")
    653  1.1  christos                     res = "OK"
    654  1.1  christos                     data += " [%s]" % uri
    655  1.1  christos                 else:
    656  1.1  christos                     res = wpas_report_handover_req(uri)
    657  1.1  christos                 if res is None or "FAIL" in res:
    658  1.1  christos                     summary("DPP handover request processing failed",
    659  1.1  christos                             color=C_RED)
    660  1.1  christos                     if handover.altchanlist:
    661  1.1  christos                         data = wpas_get_nfc_uri(start_listen=False,
    662  1.1  christos                                                 chan_override=handover.altchanlist)
    663  1.1  christos                         summary("Own URI (try another channel list): %s" % data)
    664  1.1  christos                     continue
    665  1.1  christos 
    666  1.1  christos                 if test_alt_uri:
    667  1.1  christos                     summary("TEST MODE: Reject initial proposal")
    668  1.1  christos                     continue
    669  1.1  christos 
    670  1.1  christos                 found = True
    671  1.1  christos 
    672  1.1  christos                 if not test_uri:
    673  1.1  christos                     wpas = wpas_connect()
    674  1.1  christos                     if wpas is None:
    675  1.1  christos                         continue
    676  1.1  christos                     global own_id
    677  1.1  christos                     data = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip()
    678  1.1  christos                     if "FAIL" in data:
    679  1.1  christos                         continue
    680  1.1  christos                 summary("Own URI (post-processing): %s" % data)
    681  1.1  christos                 handover.my_uri = data
    682  1.1  christos                 handover.peer_uri = uri
    683  1.1  christos                 uri = ndef.UriRecord(data)
    684  1.1  christos                 summary("Own bootstrapping NFC URI record: " + str(uri))
    685  1.1  christos 
    686  1.1  christos                 if not test_uri:
    687  1.1  christos                     info = wpas.request("DPP_BOOTSTRAP_INFO %d" % own_id)
    688  1.1  christos                     freq = None
    689  1.1  christos                     for line in info.splitlines():
    690  1.1  christos                         if line.startswith("use_freq="):
    691  1.1  christos                             freq = int(line.split('=')[1])
    692  1.1  christos                     if freq is None or freq == 0:
    693  1.1  christos                         summary("No channel negotiated over NFC - use channel 6")
    694  1.1  christos                         freq = 2437
    695  1.1  christos                     else:
    696  1.1  christos                         summary("Negotiated channel: %d MHz" % freq)
    697  1.1  christos                     if not dpp_start_listen(wpas, freq):
    698  1.1  christos                         break
    699  1.1  christos 
    700  1.1  christos                 carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data)
    701  1.1  christos                 summary("Own DPP carrier record: " + str(carrier))
    702  1.1  christos                 hs.add_alternative_carrier('active', carrier.name)
    703  1.1  christos                 sel = [hs, carrier]
    704  1.1  christos                 break
    705  1.1  christos 
    706  1.1  christos         summary("Sending handover select: " + str(sel))
    707  1.1  christos         if found:
    708  1.1  christos             summary("Handover completed successfully")
    709  1.1  christos             handover.terminate_on_hs_send_completion = True
    710  1.1  christos             self.success = True
    711  1.1  christos             handover.hs_sent = True
    712  1.1  christos             handover.i_m_selector = True
    713  1.1  christos         elif handover.no_alt_proposal:
    714  1.1  christos             summary("Do not try alternative proposal anymore - handover failed",
    715  1.1  christos                     color=C_RED)
    716  1.1  christos             handover.hs_sent = True
    717  1.1  christos         else:
    718  1.1  christos             summary("Try to initiate with alternative parameters")
    719  1.1  christos             handover.try_own = True
    720  1.1  christos             handover.hs_sent = False
    721  1.1  christos             handover.no_alt_proposal = True
    722  1.1  christos             if handover.client_thread:
    723  1.1  christos                 handover.start_client_alt = True
    724  1.1  christos             else:
    725  1.1  christos                 handover.client_thread = threading.Thread(target=llcp_worker,
    726  1.1  christos                                                           args=(self.llc, True))
    727  1.1  christos                 handover.client_thread.start()
    728  1.1  christos         return sel
    729  1.1  christos 
    730  1.1  christos def clear_raw_mode():
    731  1.1  christos     import sys, tty, termios
    732  1.1  christos     global prev_tcgetattr, in_raw_mode
    733  1.1  christos     if not in_raw_mode:
    734  1.1  christos         return
    735  1.1  christos     fd = sys.stdin.fileno()
    736  1.1  christos     termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
    737  1.1  christos     in_raw_mode = False
    738  1.1  christos 
    739  1.1  christos def getch():
    740  1.1  christos     import sys, tty, termios, select
    741  1.1  christos     global prev_tcgetattr, in_raw_mode
    742  1.1  christos     fd = sys.stdin.fileno()
    743  1.1  christos     prev_tcgetattr = termios.tcgetattr(fd)
    744  1.1  christos     ch = None
    745  1.1  christos     try:
    746  1.1  christos         tty.setraw(fd)
    747  1.1  christos         in_raw_mode = True
    748  1.1  christos         [i, o, e] = select.select([fd], [], [], 0.05)
    749  1.1  christos         if i:
    750  1.1  christos             ch = sys.stdin.read(1)
    751  1.1  christos     finally:
    752  1.1  christos         termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
    753  1.1  christos         in_raw_mode = False
    754  1.1  christos     return ch
    755  1.1  christos 
    756  1.1  christos def dpp_tag_read(tag):
    757  1.1  christos     success = False
    758  1.1  christos     for record in tag.ndef.records:
    759  1.1  christos         summary(record)
    760  1.1  christos         summary("record type " + record.type)
    761  1.1  christos         if record.type == "application/vnd.wfa.dpp":
    762  1.1  christos             summary("DPP HS tag - send to wpa_supplicant")
    763  1.1  christos             success = dpp_hs_tag_read(record)
    764  1.1  christos             break
    765  1.1  christos         if isinstance(record, ndef.UriRecord):
    766  1.1  christos             summary("URI record: uri=" + record.uri)
    767  1.1  christos             summary("URI record: iri=" + record.iri)
    768  1.1  christos             if record.iri.startswith("DPP:"):
    769  1.1  christos                 summary("DPP URI")
    770  1.1  christos                 if not dpp_nfc_uri_process(record.iri):
    771  1.1  christos                     break
    772  1.1  christos                 success = True
    773  1.1  christos             else:
    774  1.1  christos                 summary("Ignore unknown URI")
    775  1.1  christos             break
    776  1.1  christos 
    777  1.1  christos     if success:
    778  1.1  christos         success_report("Tag read succeeded")
    779  1.1  christos 
    780  1.1  christos     return success
    781  1.1  christos 
    782  1.1  christos def rdwr_connected_write_tag(tag):
    783  1.1  christos     summary("Tag found - writing - " + str(tag))
    784  1.1  christos     if not tag.ndef:
    785  1.1  christos         summary("Not a formatted NDEF tag", color=C_RED)
    786  1.1  christos         return
    787  1.1  christos     if not tag.ndef.is_writeable:
    788  1.1  christos         summary("Not a writable tag", color=C_RED)
    789  1.1  christos         return
    790  1.1  christos     global dpp_tag_data
    791  1.1  christos     if tag.ndef.capacity < len(dpp_tag_data):
    792  1.1  christos         summary("Not enough room for the message")
    793  1.1  christos         return
    794  1.1  christos     try:
    795  1.1  christos         tag.ndef.records = dpp_tag_data
    796  1.1  christos     except ValueError as e:
    797  1.1  christos         summary("Writing the tag failed: %s" % str(e), color=C_RED)
    798  1.1  christos         return
    799  1.1  christos     success_report("Tag write succeeded")
    800  1.1  christos     summary("Tag writing completed - remove tag", color=C_GREEN)
    801  1.1  christos     global only_one, operation_success
    802  1.1  christos     operation_success = True
    803  1.1  christos     if only_one:
    804  1.1  christos         global continue_loop
    805  1.1  christos         continue_loop = False
    806  1.1  christos     global dpp_sel_wait_remove
    807  1.1  christos     return dpp_sel_wait_remove
    808  1.1  christos 
    809  1.1  christos def write_nfc_uri(clf, wait_remove=True):
    810  1.1  christos     summary("Write NFC URI record")
    811  1.1  christos     data = wpas_get_nfc_uri()
    812  1.1  christos     if data is None:
    813  1.1  christos         summary("Could not get NFC URI from wpa_supplicant", color=C_RED)
    814  1.1  christos         return
    815  1.1  christos 
    816  1.1  christos     global dpp_sel_wait_remove
    817  1.1  christos     dpp_sel_wait_remove = wait_remove
    818  1.1  christos     summary("URI: %s" % data)
    819  1.1  christos     uri = ndef.UriRecord(data)
    820  1.1  christos     summary(uri)
    821  1.1  christos 
    822  1.1  christos     summary("Touch an NFC tag to write URI record", color=C_CYAN)
    823  1.1  christos     global dpp_tag_data
    824  1.1  christos     dpp_tag_data = [uri]
    825  1.1  christos     clf.connect(rdwr={'on-connect': rdwr_connected_write_tag})
    826  1.1  christos 
    827  1.1  christos def write_nfc_hs(clf, wait_remove=True):
    828  1.1  christos     summary("Write NFC Handover Select record on a tag")
    829  1.1  christos     data = wpas_get_nfc_uri()
    830  1.1  christos     if data is None:
    831  1.1  christos         summary("Could not get NFC URI from wpa_supplicant", color=C_RED)
    832  1.1  christos         return
    833  1.1  christos 
    834  1.1  christos     global dpp_sel_wait_remove
    835  1.1  christos     dpp_sel_wait_remove = wait_remove
    836  1.1  christos     summary("URI: %s" % data)
    837  1.1  christos     uri = ndef.UriRecord(data)
    838  1.1  christos     summary(uri)
    839  1.1  christos     carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data)
    840  1.1  christos     hs = ndef.HandoverSelectRecord('1.4')
    841  1.1  christos     hs.add_alternative_carrier('active', carrier.name)
    842  1.1  christos     summary(hs)
    843  1.1  christos     summary(carrier)
    844  1.1  christos 
    845  1.1  christos     summary("Touch an NFC tag to write HS record", color=C_CYAN)
    846  1.1  christos     global dpp_tag_data
    847  1.1  christos     dpp_tag_data = [hs, carrier]
    848  1.1  christos     summary(dpp_tag_data)
    849  1.1  christos     clf.connect(rdwr={'on-connect': rdwr_connected_write_tag})
    850  1.1  christos 
    851  1.1  christos def rdwr_connected(tag):
    852  1.1  christos     global only_one, no_wait
    853  1.1  christos     summary("Tag connected: " + str(tag))
    854  1.1  christos 
    855  1.1  christos     if tag.ndef:
    856  1.1  christos         summary("NDEF tag: " + tag.type)
    857  1.1  christos         summary(tag.ndef.records)
    858  1.1  christos         success = dpp_tag_read(tag)
    859  1.1  christos         if only_one and success:
    860  1.1  christos             global continue_loop
    861  1.1  christos             continue_loop = False
    862  1.1  christos     else:
    863  1.1  christos         summary("Not an NDEF tag - remove tag", color=C_RED)
    864  1.1  christos         return True
    865  1.1  christos 
    866  1.1  christos     return not no_wait
    867  1.1  christos 
    868  1.1  christos def llcp_worker(llc, try_alt):
    869  1.1  christos     global handover
    870  1.1  christos     print("Start of llcp_worker()")
    871  1.1  christos     if try_alt:
    872  1.1  christos         summary("Starting handover client (try_alt)")
    873  1.1  christos         dpp_handover_client(handover, alt=True)
    874  1.1  christos         summary("Exiting llcp_worker thread (try_alt)")
    875  1.1  christos         return
    876  1.1  christos     global init_on_touch
    877  1.1  christos     if init_on_touch:
    878  1.1  christos         summary("Starting handover client (init_on_touch)")
    879  1.1  christos         dpp_handover_client(handover)
    880  1.1  christos         summary("llcp_worker init_on_touch processing completed: try_own={} hs_sent={} no_alt_proposal={} start_client_alt={}".format(handover.try_own, handover.hs_sent, handover.no_alt_proposal, handover.start_client_alt))
    881  1.1  christos         if handover.start_client_alt and not handover.hs_sent:
    882  1.1  christos             summary("Try alternative handover request before exiting llcp_worker")
    883  1.1  christos             handover.start_client_alt = False
    884  1.1  christos             dpp_handover_client(handover, alt=True)
    885  1.1  christos         summary("Exiting llcp_worker thread (init_on_touch)")
    886  1.1  christos         return
    887  1.1  christos 
    888  1.1  christos     global no_input
    889  1.1  christos     if no_input:
    890  1.1  christos         summary("Wait for handover to complete")
    891  1.1  christos     else:
    892  1.1  christos         print("Wait for handover to complete - press 'i' to initiate")
    893  1.1  christos     while not handover.wait_connection and handover.srv.sent_carrier is None:
    894  1.1  christos         if handover.try_own:
    895  1.1  christos             handover.try_own = False
    896  1.1  christos             summary("Try to initiate another handover with own parameters")
    897  1.1  christos             handover.my_crn_ready = False
    898  1.1  christos             handover.my_crn = None
    899  1.1  christos             handover.peer_crn = None
    900  1.1  christos             handover.hs_sent = False
    901  1.1  christos             dpp_handover_client(handover, alt=True)
    902  1.1  christos             summary("Exiting llcp_worker thread (retry with own parameters)")
    903  1.1  christos             return
    904  1.1  christos         if handover.srv.ho_server_processing:
    905  1.1  christos             time.sleep(0.025)
    906  1.1  christos         elif no_input:
    907  1.1  christos             time.sleep(0.5)
    908  1.1  christos         else:
    909  1.1  christos             res = getch()
    910  1.1  christos             if res != 'i':
    911  1.1  christos                 continue
    912  1.1  christos             clear_raw_mode()
    913  1.1  christos             summary("Starting handover client")
    914  1.1  christos             dpp_handover_client(handover)
    915  1.1  christos             summary("Exiting llcp_worker thread (manual init)")
    916  1.1  christos             return
    917  1.1  christos 
    918  1.1  christos     global in_raw_mode
    919  1.1  christos     was_in_raw_mode = in_raw_mode
    920  1.1  christos     clear_raw_mode()
    921  1.1  christos     if was_in_raw_mode:
    922  1.1  christos         print("\r")
    923  1.1  christos     summary("Exiting llcp_worker thread")
    924  1.1  christos 
    925  1.1  christos class ConnectionHandover():
    926  1.1  christos     def __init__(self):
    927  1.1  christos         self.client = None
    928  1.1  christos         self.client_thread = None
    929  1.1  christos         self.reset()
    930  1.1  christos         self.exit_thread = None
    931  1.1  christos 
    932  1.1  christos     def reset(self):
    933  1.1  christos         self.wait_connection = False
    934  1.1  christos         self.my_crn_ready = False
    935  1.1  christos         self.my_crn = None
    936  1.1  christos         self.peer_crn = None
    937  1.1  christos         self.hs_sent = False
    938  1.1  christos         self.no_alt_proposal = False
    939  1.1  christos         self.alt_proposal_used = False
    940  1.1  christos         self.i_m_selector = False
    941  1.1  christos         self.start_client_alt = False
    942  1.1  christos         self.terminate_on_hs_send_completion = False
    943  1.1  christos         self.try_own = False
    944  1.1  christos         self.my_uri = None
    945  1.1  christos         self.peer_uri = None
    946  1.1  christos         self.connected = False
    947  1.1  christos         self.alt_proposal = False
    948  1.1  christos 
    949  1.1  christos     def start_handover_server(self, llc):
    950  1.1  christos         summary("Start handover server")
    951  1.1  christos         self.llc = llc
    952  1.1  christos         self.srv = HandoverServer(self, llc)
    953  1.1  christos 
    954  1.1  christos     def close(self):
    955  1.1  christos         if self.client:
    956  1.1  christos             self.client.close()
    957  1.1  christos             self.client = None
    958  1.1  christos 
    959  1.1  christos     def run_delayed_exit(self):
    960  1.1  christos         summary("Trying to exit (delayed)..")
    961  1.1  christos         time.sleep(0.25)
    962  1.1  christos         summary("Trying to exit (after wait)..")
    963  1.1  christos         global terminate_now
    964  1.1  christos         terminate_now = True
    965  1.1  christos 
    966  1.1  christos     def delayed_exit(self):
    967  1.1  christos         global only_one
    968  1.1  christos         if only_one:
    969  1.1  christos             self.exit_thread = threading.Thread(target=self.run_delayed_exit)
    970  1.1  christos             self.exit_thread.start()
    971  1.1  christos 
    972  1.1  christos def llcp_startup(llc):
    973  1.1  christos     global handover
    974  1.1  christos     handover.start_handover_server(llc)
    975  1.1  christos     return llc
    976  1.1  christos 
    977  1.1  christos def llcp_connected(llc):
    978  1.1  christos     summary("P2P LLCP connected")
    979  1.1  christos     global handover
    980  1.1  christos     handover.connected = True
    981  1.1  christos     handover.srv.start()
    982  1.1  christos     if init_on_touch or not no_input:
    983  1.1  christos         handover.client_thread = threading.Thread(target=llcp_worker,
    984  1.1  christos                                                   args=(llc, False))
    985  1.1  christos         handover.client_thread.start()
    986  1.1  christos     return True
    987  1.1  christos 
    988  1.1  christos def llcp_release(llc):
    989  1.1  christos     summary("LLCP release")
    990  1.1  christos     global handover
    991  1.1  christos     handover.close()
    992  1.1  christos     return True
    993  1.1  christos 
    994  1.1  christos def terminate_loop():
    995  1.1  christos     global terminate_now
    996  1.1  christos     return terminate_now
    997  1.1  christos 
    998  1.1  christos def main():
    999  1.1  christos     clf = nfc.ContactlessFrontend()
   1000  1.1  christos 
   1001  1.1  christos     parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for DPP NFC operations')
   1002  1.1  christos     parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
   1003  1.1  christos                         action='store_const', dest='loglevel',
   1004  1.1  christos                         help='verbose debug output')
   1005  1.1  christos     parser.add_argument('-q', const=logging.WARNING, action='store_const',
   1006  1.1  christos                         dest='loglevel', help='be quiet')
   1007  1.1  christos     parser.add_argument('--only-one', '-1', action='store_true',
   1008  1.1  christos                         help='run only one operation and exit')
   1009  1.1  christos     parser.add_argument('--init-on-touch', '-I', action='store_true',
   1010  1.1  christos                         help='initiate handover on touch')
   1011  1.1  christos     parser.add_argument('--no-wait', action='store_true',
   1012  1.1  christos                         help='do not wait for tag to be removed before exiting')
   1013  1.1  christos     parser.add_argument('--ifname', '-i',
   1014  1.1  christos                         help='network interface name')
   1015  1.1  christos     parser.add_argument('--no-input', '-a', action='store_true',
   1016  1.1  christos                         help='do not use stdout input to initiate handover')
   1017  1.1  christos     parser.add_argument('--tag-read-only', '-t', action='store_true',
   1018  1.1  christos                         help='tag read only (do not allow connection handover)')
   1019  1.1  christos     parser.add_argument('--handover-only', action='store_true',
   1020  1.1  christos                         help='connection handover only (do not allow tag read)')
   1021  1.1  christos     parser.add_argument('--enrollee', action='store_true',
   1022  1.1  christos                         help='run as Enrollee-only')
   1023  1.1  christos     parser.add_argument('--configurator', action='store_true',
   1024  1.1  christos                         help='run as Configurator-only')
   1025  1.1  christos     parser.add_argument('--config-params', default='',
   1026  1.1  christos                         help='configurator parameters')
   1027  1.1  christos     parser.add_argument('--ctrl', default='/var/run/wpa_supplicant',
   1028  1.1  christos                         help='wpa_supplicant/hostapd control interface')
   1029  1.1  christos     parser.add_argument('--summary',
   1030  1.1  christos                         help='summary file for writing status updates')
   1031  1.1  christos     parser.add_argument('--success',
   1032  1.1  christos                         help='success file for writing success update')
   1033  1.1  christos     parser.add_argument('--device', default='usb', help='NFC device to open')
   1034  1.1  christos     parser.add_argument('--chan', default=None, help='channel list')
   1035  1.1  christos     parser.add_argument('--altchan', default=None, help='alternative channel list')
   1036  1.1  christos     parser.add_argument('--netrole', default=None, help='netrole for Enrollee')
   1037  1.1  christos     parser.add_argument('--test-uri', default=None,
   1038  1.1  christos                         help='test mode: initial URI')
   1039  1.1  christos     parser.add_argument('--test-alt-uri', default=None,
   1040  1.1  christos                         help='test mode: alternative URI')
   1041  1.1  christos     parser.add_argument('--test-sel-uri', default=None,
   1042  1.1  christos                         help='test mode: handover select URI')
   1043  1.1  christos     parser.add_argument('--test-crn', default=None,
   1044  1.1  christos                         help='test mode: hardcoded crn')
   1045  1.1  christos     parser.add_argument('command', choices=['write-nfc-uri',
   1046  1.1  christos                                             'write-nfc-hs'],
   1047  1.1  christos                         nargs='?')
   1048  1.1  christos     args = parser.parse_args()
   1049  1.1  christos     summary(args)
   1050  1.1  christos 
   1051  1.1  christos     global handover
   1052  1.1  christos     handover = ConnectionHandover()
   1053  1.1  christos 
   1054  1.1  christos     global only_one
   1055  1.1  christos     only_one = args.only_one
   1056  1.1  christos 
   1057  1.1  christos     global no_wait
   1058  1.1  christos     no_wait = args.no_wait
   1059  1.1  christos 
   1060  1.1  christos     global chanlist, netrole, test_uri, test_alt_uri, test_sel_uri
   1061  1.1  christos     global test_crn
   1062  1.1  christos     chanlist = args.chan
   1063  1.1  christos     handover.altchanlist = args.altchan
   1064  1.1  christos     netrole = args.netrole
   1065  1.1  christos     test_uri = args.test_uri
   1066  1.1  christos     test_alt_uri = args.test_alt_uri
   1067  1.1  christos     test_sel_uri = args.test_sel_uri
   1068  1.1  christos     if args.test_crn:
   1069  1.1  christos         test_crn = struct.pack('>H', int(args.test_crn))
   1070  1.1  christos     else:
   1071  1.1  christos         test_crn = None
   1072  1.1  christos 
   1073  1.1  christos     logging.basicConfig(level=args.loglevel)
   1074  1.1  christos     for l in ['nfc.clf.rcs380',
   1075  1.1  christos               'nfc.clf.transport',
   1076  1.1  christos               'nfc.clf.device',
   1077  1.1  christos               'nfc.clf.__init__',
   1078  1.1  christos               'nfc.llcp',
   1079  1.1  christos               'nfc.handover']:
   1080  1.1  christos         log = logging.getLogger(l)
   1081  1.1  christos         log.setLevel(args.loglevel)
   1082  1.1  christos 
   1083  1.1  christos     global init_on_touch
   1084  1.1  christos     init_on_touch = args.init_on_touch
   1085  1.1  christos 
   1086  1.1  christos     global enrollee_only
   1087  1.1  christos     enrollee_only = args.enrollee
   1088  1.1  christos 
   1089  1.1  christos     global configurator_only
   1090  1.1  christos     configurator_only = args.configurator
   1091  1.1  christos 
   1092  1.1  christos     global config_params
   1093  1.1  christos     config_params = args.config_params
   1094  1.1  christos 
   1095  1.1  christos     if args.ifname:
   1096  1.1  christos         global ifname
   1097  1.1  christos         ifname = args.ifname
   1098  1.1  christos         summary("Selected ifname " + ifname)
   1099  1.1  christos 
   1100  1.1  christos     if args.ctrl:
   1101  1.1  christos         global wpas_ctrl
   1102  1.1  christos         wpas_ctrl = args.ctrl
   1103  1.1  christos 
   1104  1.1  christos     if args.summary:
   1105  1.1  christos         global summary_file
   1106  1.1  christos         summary_file = args.summary
   1107  1.1  christos 
   1108  1.1  christos     if args.success:
   1109  1.1  christos         global success_file
   1110  1.1  christos         success_file = args.success
   1111  1.1  christos 
   1112  1.1  christos     if args.no_input:
   1113  1.1  christos         global no_input
   1114  1.1  christos         no_input = True
   1115  1.1  christos 
   1116  1.1  christos     clf = nfc.ContactlessFrontend()
   1117  1.1  christos 
   1118  1.1  christos     try:
   1119  1.1  christos         if not clf.open(args.device):
   1120  1.1  christos             summary("Could not open connection with an NFC device", color=C_RED)
   1121  1.1  christos             raise SystemExit(1)
   1122  1.1  christos 
   1123  1.1  christos         if args.command == "write-nfc-uri":
   1124  1.1  christos             write_nfc_uri(clf, wait_remove=not args.no_wait)
   1125  1.1  christos             if not operation_success:
   1126  1.1  christos                 raise SystemExit(1)
   1127  1.1  christos             raise SystemExit
   1128  1.1  christos 
   1129  1.1  christos         if args.command == "write-nfc-hs":
   1130  1.1  christos             write_nfc_hs(clf, wait_remove=not args.no_wait)
   1131  1.1  christos             if not operation_success:
   1132  1.1  christos                 raise SystemExit(1)
   1133  1.1  christos             raise SystemExit
   1134  1.1  christos 
   1135  1.1  christos         global continue_loop
   1136  1.1  christos         while continue_loop:
   1137  1.1  christos             global in_raw_mode
   1138  1.1  christos             was_in_raw_mode = in_raw_mode
   1139  1.1  christos             clear_raw_mode()
   1140  1.1  christos             if was_in_raw_mode:
   1141  1.1  christos                 print("\r")
   1142  1.1  christos             if args.handover_only:
   1143  1.1  christos                 summary("Waiting a peer to be touched", color=C_MAGENTA)
   1144  1.1  christos             elif args.tag_read_only:
   1145  1.1  christos                 summary("Waiting for a tag to be touched", color=C_BLUE)
   1146  1.1  christos             else:
   1147  1.1  christos                 summary("Waiting for a tag or peer to be touched",
   1148  1.1  christos                         color=C_GREEN)
   1149  1.1  christos             handover.wait_connection = True
   1150  1.1  christos             try:
   1151  1.1  christos                 if args.tag_read_only:
   1152  1.1  christos                     if not clf.connect(rdwr={'on-connect': rdwr_connected}):
   1153  1.1  christos                         break
   1154  1.1  christos                 elif args.handover_only:
   1155  1.1  christos                     if not clf.connect(llcp={'on-startup': llcp_startup,
   1156  1.1  christos                                              'on-connect': llcp_connected,
   1157  1.1  christos                                              'on-release': llcp_release},
   1158  1.1  christos                                        terminate=terminate_loop):
   1159  1.1  christos                         break
   1160  1.1  christos                 else:
   1161  1.1  christos                     if not clf.connect(rdwr={'on-connect': rdwr_connected},
   1162  1.1  christos                                        llcp={'on-startup': llcp_startup,
   1163  1.1  christos                                              'on-connect': llcp_connected,
   1164  1.1  christos                                              'on-release': llcp_release},
   1165  1.1  christos                                        terminate=terminate_loop):
   1166  1.1  christos                         break
   1167  1.1  christos             except Exception as e:
   1168  1.1  christos                 summary("clf.connect failed: " + str(e))
   1169  1.1  christos                 break
   1170  1.1  christos 
   1171  1.1  christos             if only_one and handover.connected:
   1172  1.1  christos                 role = "selector" if handover.i_m_selector else "requestor"
   1173  1.1  christos                 summary("Connection handover result: I'm the %s" % role,
   1174  1.1  christos                         color=C_YELLOW)
   1175  1.1  christos                 if handover.peer_uri:
   1176  1.1  christos                     summary("Peer URI: " + handover.peer_uri, color=C_YELLOW)
   1177  1.1  christos                 if handover.my_uri:
   1178  1.1  christos                     summary("My URI: " + handover.my_uri, color=C_YELLOW)
   1179  1.1  christos                 if not (handover.peer_uri and handover.my_uri):
   1180  1.1  christos                     summary("Negotiated connection handover failed",
   1181  1.1  christos                             color=C_YELLOW)
   1182  1.1  christos                 break
   1183  1.1  christos 
   1184  1.1  christos     except KeyboardInterrupt:
   1185  1.1  christos         raise SystemExit
   1186  1.1  christos     finally:
   1187  1.1  christos         clf.close()
   1188  1.1  christos 
   1189  1.1  christos     raise SystemExit
   1190  1.1  christos 
   1191  1.1  christos if __name__ == '__main__':
   1192  1.1  christos     main()
   1193