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