1*#!/usr/bin/python3
2*#
3*# Example nfcpy to wpa_supplicant wrapper for DPP NFC operations
4*# Copyright (c) 2012-2013, Jouni Malinen <j@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")
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")
386*        return
387*
388*    summary("Sending handover request")
389*
390*    handover.my_crn_ready = True
391*
392*    if not client.send_records(message):
393*        handover.my_crn_ready = False
394*        summary("Failed to send handover request", color=C_RED)
395*        run_client_alt(handover, alt)
396*        return
397*
398*    handover.my_crn, = struct.unpack('>H', crn)
399*
400*    summary("Receiving handover response")
401*    try:
402*        start = time.time()
403*        message = client.recv_records(timeout=3.0)
404*        end = time.time()
405*        summary("Received {} record(s) in {} seconds".format(len(message) if message is not None else -1, end - start))
406*    except Exception as e:
407*        # This is fine if we are the handover selector
408*        if handover.hs_sent:
409*            summary("Client receive failed as expected since I'm the handover server: %s" % str(e))
410*        elif handover.alt_proposal_used and not alt:
411*            summary("Client received failed for initial proposal as expected since alternative proposal was also used: %s" % str(e))
412*        else:
413*            summary("Client receive failed: %s" % str(e), color=C_RED)
414*        message = None
415*    if message is None:
416*        if handover.hs_sent:
417*            summary("No response received as expected since I'm the handover server")
418*        elif handover.alt_proposal_used and not alt:
419*            summary("No response received for initial proposal as expected since alternative proposal was also used")
420*        elif handover.try_own and not alt:
421*            summary("No response received for initial proposal as expected since alternative proposal will also be sent")
422*        else:
423*            summary("No response received", color=C_RED)
424*        run_client_alt(handover, alt)
425*        return
426*    summary("Received message: " + str(message))
427*    if len(message) < 1 or \
428*       not isinstance(message[0], ndef.HandoverSelectRecord):
429*        summary("Response was not Hs - received: " + message.type)
430*        return
431*
432*    summary("Received handover select message")
433*    summary("alternative carriers: " + str(message[0].alternative_carriers))
434*    if handover.i_m_selector:
435*        summary("Ignore the received select since I'm the handover selector")
436*        run_client_alt(handover, alt)
437*        return
438*
439*    if handover.alt_proposal_used and not alt:
440*        summary("Ignore received handover select for the initial proposal since alternative proposal was sent")
441*        client.close()
442*        return
443*
444*    dpp_found = False
445*    for carrier in message:
446*        if isinstance(carrier, ndef.HandoverSelectRecord):
447*            continue
448*        summary("Remote carrier type: " + carrier.type)
449*        if carrier.type == "application/vnd.wfa.dpp":
450*            if len(carrier.data) == 0 or carrier.data[0] != 0:
451*                summary("URI Identifier Code 'None' not seen", color=C_RED)
452*                continue
453*            summary("DPP carrier type match - send to wpa_supplicant")
454*            dpp_found = True
455*            uri = carrier.data[1:].decode("utf-8")
456*            summary("DPP URI: " + uri)
457*            handover.peer_uri = uri
458*            if test_uri:
459*                summary("TEST MODE: Fake processing")
460*                break
461*            res = wpas_report_handover_sel(uri)
462*            if res is None or "FAIL" in res:
463*                summary("DPP handover report rejected", color=C_RED)
464*                break
465*
466*            success_report("DPP handover reported successfully (initiator)")
467*            summary("peer_id=" + res)
468*            peer_id = int(res)
469*            wpas = wpas_connect()
470*            if wpas is None:
471*                break
472*
473*            global enrollee_only
474*            global config_params
475*            if enrollee_only:
476*                extra = " role=enrollee"
477*            elif config_params:
478*                extra = " role=configurator " + config_params
479*            else:
480*                # TODO: Single Configurator instance
481*                res = wpas.request("DPP_CONFIGURATOR_ADD")
482*                if "FAIL" in res:
483*                    summary("Failed to initiate Configurator", color=C_RED)
484*                    break
485*                conf_id = int(res)
486*                extra = " conf=sta-dpp configurator=%d" % conf_id
487*            global own_id
488*            summary("Initiate DPP authentication")
489*            cmd = "DPP_AUTH_INIT peer=%d own=%d" % (peer_id, own_id)
490*            cmd += extra
491*            res = wpas.request(cmd)
492*            if "FAIL" in res:
493*                summary("Failed to initiate DPP authentication", color=C_RED)
494*            break
495*
496*    if not dpp_found and handover.no_alt_proposal:
497*        summary("DPP carrier not seen in response - do not allow alternative proposal anymore")
498*    elif not dpp_found:
499*        summary("DPP carrier not seen in response - allow peer to initiate a new handover with different parameters")
500*        handover.alt_proposal = True
501*        handover.my_crn_ready = False
502*        handover.my_crn = None
503*        handover.peer_crn = None
504*        handover.hs_sent = False
505*        summary("Returning from dpp_handover_client")
506*        return
507*
508*    summary("Remove peer")
509*    handover.close()
510*    summary("Done with handover")
511*    global only_one
512*    if only_one:
513*        print("only_one -> stop loop")
514*        global continue_loop
515*        continue_loop = False
516*
517*    global no_wait
518*    if no_wait or only_one:
519*        summary("Trying to exit..")
520*        global terminate_now
521*        terminate_now = True
522*
523*    summary("Returning from dpp_handover_client")
524*
525*class HandoverServer(nfc.handover.HandoverServer):
526*    def __init__(self, handover, llc):
527*        super(HandoverServer, self).__init__(llc)
528*        self.sent_carrier = None
529*        self.ho_server_processing = False
530*        self.success = False
531*        self.llc = llc
532*        self.handover = handover
533*
534*    def serve(self, socket):
535*        peer_sap = socket.getpeername()
536*        summary("Serving handover client on remote sap {0}".format(peer_sap))
537*        send_miu = socket.getsockopt(nfc.llcp.SO_SNDMIU)
538*        try:
539*            while socket.poll("recv"):
540*                req = bytearray()
541*                while socket.poll("recv"):
542*                    r = socket.recv()
543*                    if r is None:
544*                        return None
545*                    summary("Received %d octets" % len(r))
546*                    req += r
547*                    if len(req) == 0:
548*                        continue
549*                    try:
550*                        list(ndef.message_decoder(req, 'strict', {}))
551*                    except ndef.DecodeError:
552*                        continue
553*                    summary("Full message received")
554*                    resp = self._process_request_data(req)
555*                    if resp is None or len(resp) == 0:
556*                        summary("No handover select to send out - wait for a possible alternative handover request")
557*                        handover.alt_proposal = True
558*                        req = bytearray()
559*                        continue
560*
561*                    for offset in range(0, len(resp), send_miu):
562*                        if not socket.send(resp[offset:offset + send_miu]):
563*                            summary("Failed to send handover select - connection closed")
564*                            return
565*                    summary("Sent out full handover select")
566*                    if handover.terminate_on_hs_send_completion:
567*                        handover.delayed_exit()
568*
569*        except nfc.llcp.Error as e:
570*            global terminate_now
571*            summary("HandoverServer exception: %s" % e,
572*                    color=None if e.errno == errno.EPIPE or terminate_now else C_RED)
573*        finally:
574*            socket.close()
575*            summary("Handover serve thread exiting")
576*
577*    def process_handover_request_message(self, records):
578*        handover = self.handover
579*        self.ho_server_processing = True
580*        global in_raw_mode
581*        was_in_raw_mode = in_raw_mode
582*        clear_raw_mode()
583*        if was_in_raw_mode:
584*            print("\n")
585*        summary("HandoverServer - request received: " + str(records))
586*
587*        for carrier in records:
588*            if not isinstance(carrier, ndef.HandoverRequestRecord):
589*                continue
590*            if carrier.collision_resolution_number:
591*                handover.peer_crn = carrier.collision_resolution_number
592*                summary("peer_crn: %d" % handover.peer_crn)
593*
594*        if handover.my_crn is None and handover.my_crn_ready:
595*            summary("Still trying to send own handover request - wait a moment to see if that succeeds before checking crn values")
596*            for i in range(10):
597*                if handover.my_crn is not None:
598*                    break
599*                time.sleep(0.01)
600*        if handover.my_crn is not None:
601*            summary("my_crn: %d" % handover.my_crn)
602*
603*        if handover.my_crn is not None and handover.peer_crn is not None:
604*            if handover.my_crn == handover.peer_crn:
605*                summary("Same crn used - automatic collision resolution failed")
606*                # TODO: Should generate a new Handover Request message
607*                return ''
608*            if ((handover.my_crn & 1) == (handover.peer_crn & 1) and \
609*                handover.my_crn > handover.peer_crn) or \
610*               ((handover.my_crn & 1) != (handover.peer_crn & 1) and \
611*                handover.my_crn < handover.peer_crn):
612*                summary("I'm the Handover Selector Device")
613*                handover.i_m_selector = True
614*            else:
615*                summary("Peer is the Handover Selector device")
616*                summary("Ignore the received request.")
617*                return ''
618*
619*        hs = ndef.HandoverSelectRecord('1.4')
620*        sel = [hs]
621*
622*        found = False
623*
624*        for carrier in records:
625*            if isinstance(carrier, ndef.HandoverRequestRecord):
626*                continue
627*            summary("Remote carrier type: " + carrier.type)
628*            if carrier.type == "application/vnd.wfa.dpp":
629*                summary("DPP carrier type match - add DPP carrier record")
630*                if len(carrier.data) == 0 or carrier.data[0] != 0:
631*                    summary("URI Identifier Code 'None' not seen", color=C_RED)
632*                    continue
633*                uri = carrier.data[1:].decode("utf-8")
634*                summary("Received DPP URI: " + uri)
635*
636*                global test_uri, test_alt_uri
637*                if test_uri:
638*                    summary("TEST MODE: Using specified URI")
639*                    data = test_sel_uri if test_sel_uri else test_uri
640*                elif handover.alt_proposal and handover.altchanlist:
641*                    summary("Use alternative channel list while processing alternative proposal from peer")
642*                    data = wpas_get_nfc_uri(start_listen=False,
643*                                            chan_override=handover.altchanlist,
644*                                            pick_channel=True)
645*                else:
646*                    data = wpas_get_nfc_uri(start_listen=False,
647*                                            pick_channel=True)
648*                summary("Own URI (pre-processing): %s" % data)
649*
650*                if test_uri:
651*                    summary("TEST MODE: Fake processing")
652*                    res = "OK"
653*                    data += " [%s]" % uri
654*                else:
655*                    res = wpas_report_handover_req(uri)
656*                if res is None or "FAIL" in res:
657*                    summary("DPP handover request processing failed",
658*                            color=C_RED)
659*                    if handover.altchanlist:
660*                        data = wpas_get_nfc_uri(start_listen=False,
661*                                                chan_override=handover.altchanlist)
662*                        summary("Own URI (try another channel list): %s" % data)
663*                    continue
664*
665*                if test_alt_uri:
666*                    summary("TEST MODE: Reject initial proposal")
667*                    continue
668*
669*                found = True
670*
671*                if not test_uri:
672*                    wpas = wpas_connect()
673*                    if wpas is None:
674*                        continue
675*                    global own_id
676*                    data = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip()
677*                    if "FAIL" in data:
678*                        continue
679*                summary("Own URI (post-processing): %s" % data)
680*                handover.my_uri = data
681*                handover.peer_uri = uri
682*                uri = ndef.UriRecord(data)
683*                summary("Own bootstrapping NFC URI record: " + str(uri))
684*
685*                if not test_uri:
686*                    info = wpas.request("DPP_BOOTSTRAP_INFO %d" % own_id)
687*                    freq = None
688*                    for line in info.splitlines():
689*                        if line.startswith("use_freq="):
690*                            freq = int(line.split('=')[1])
691*                    if freq is None or freq == 0:
692*                        summary("No channel negotiated over NFC - use channel 6")
693*                        freq = 2437
694*                    else:
695*                        summary("Negotiated channel: %d MHz" % freq)
696*                    if not dpp_start_listen(wpas, freq):
697*                        break
698*
699*                carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data)
700*                summary("Own DPP carrier record: " + str(carrier))
701*                hs.add_alternative_carrier('active', carrier.name)
702*                sel = [hs, carrier]
703*                break
704*
705*        summary("Sending handover select: " + str(sel))
706*        if found:
707*            summary("Handover completed successfully")
708*            handover.terminate_on_hs_send_completion = True
709*            self.success = True
710*            handover.hs_sent = True
711*            handover.i_m_selector = True
712*        elif handover.no_alt_proposal:
713*            summary("Do not try alternative proposal anymore - handover failed",
714*                    color=C_RED)
715*            handover.hs_sent = True
716*        else:
717*            summary("Try to initiate with alternative parameters")
718*            handover.try_own = True
719*            handover.hs_sent = False
720*            handover.no_alt_proposal = True
721*            if handover.client_thread:
722*                handover.start_client_alt = True
723*            else:
724*                handover.client_thread = threading.Thread(target=llcp_worker,
725*                                                          args=(self.llc, True))
726*                handover.client_thread.start()
727*        return sel
728*
729*def clear_raw_mode():
730*    import sys, tty, termios
731*    global prev_tcgetattr, in_raw_mode
732*    if not in_raw_mode:
733*        return
734*    fd = sys.stdin.fileno()
735*    termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
736*    in_raw_mode = False
737*
738*def getch():
739*    import sys, tty, termios, select
740*    global prev_tcgetattr, in_raw_mode
741*    fd = sys.stdin.fileno()
742*    prev_tcgetattr = termios.tcgetattr(fd)
743*    ch = None
744*    try:
745*        tty.setraw(fd)
746*        in_raw_mode = True
747*        [i, o, e] = select.select([fd], [], [], 0.05)
748*        if i:
749*            ch = sys.stdin.read(1)
750*    finally:
751*        termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
752*        in_raw_mode = False
753*    return ch
754*
755*def dpp_tag_read(tag):
756*    success = False
757*    for record in tag.ndef.records:
758*        summary(record)
759*        summary("record type " + record.type)
760*        if record.type == "application/vnd.wfa.dpp":
761*            summary("DPP HS tag - send to wpa_supplicant")
762*            success = dpp_hs_tag_read(record)
763*            break
764*        if isinstance(record, ndef.UriRecord):
765*            summary("URI record: uri=" + record.uri)
766*            summary("URI record: iri=" + record.iri)
767*            if record.iri.startswith("DPP:"):
768*                summary("DPP URI")
769*                if not dpp_nfc_uri_process(record.iri):
770*                    break
771*                success = True
772*            else:
773*                summary("Ignore unknown URI")
774*            break
775*
776*    if success:
777*        success_report("Tag read succeeded")
778*
779*    return success
780*
781*def rdwr_connected_write_tag(tag):
782*    summary("Tag found - writing - " + str(tag))
783*    if not tag.ndef:
784*        summary("Not a formatted NDEF tag", color=C_RED)
785*        return
786*    if not tag.ndef.is_writeable:
787*        summary("Not a writable tag", color=C_RED)
788*        return
789*    global dpp_tag_data
790*    if tag.ndef.capacity < len(dpp_tag_data):
791*        summary("Not enough room for the message")
792*        return
793*    try:
794*        tag.ndef.records = dpp_tag_data
795*    except ValueError as e:
796*        summary("Writing the tag failed: %s" % str(e), color=C_RED)
797*        return
798*    success_report("Tag write succeeded")
799*    summary("Tag writing completed - remove tag", color=C_GREEN)
800*    global only_one, operation_success
801*    operation_success = True
802*    if only_one:
803*        global continue_loop
804*        continue_loop = False
805*    global dpp_sel_wait_remove
806*    return dpp_sel_wait_remove
807*
808*def write_nfc_uri(clf, wait_remove=True):
809*    summary("Write NFC URI record")
810*    data = wpas_get_nfc_uri()
811*    if data is None:
812*        summary("Could not get NFC URI from wpa_supplicant", color=C_RED)
813*        return
814*
815*    global dpp_sel_wait_remove
816*    dpp_sel_wait_remove = wait_remove
817*    summary("URI: %s" % data)
818*    uri = ndef.UriRecord(data)
819*    summary(uri)
820*
821*    summary("Touch an NFC tag to write URI record", color=C_CYAN)
822*    global dpp_tag_data
823*    dpp_tag_data = [uri]
824*    clf.connect(rdwr={'on-connect': rdwr_connected_write_tag})
825*
826*def write_nfc_hs(clf, wait_remove=True):
827*    summary("Write NFC Handover Select record on a tag")
828*    data = wpas_get_nfc_uri()
829*    if data is None:
830*        summary("Could not get NFC URI from wpa_supplicant", color=C_RED)
831*        return
832*
833*    global dpp_sel_wait_remove
834*    dpp_sel_wait_remove = wait_remove
835*    summary("URI: %s" % data)
836*    uri = ndef.UriRecord(data)
837*    summary(uri)
838*    carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data)
839*    hs = ndef.HandoverSelectRecord('1.4')
840*    hs.add_alternative_carrier('active', carrier.name)
841*    summary(hs)
842*    summary(carrier)
843*
844*    summary("Touch an NFC tag to write HS record", color=C_CYAN)
845*    global dpp_tag_data
846*    dpp_tag_data = [hs, carrier]
847*    summary(dpp_tag_data)
848*    clf.connect(rdwr={'on-connect': rdwr_connected_write_tag})
849*
850*def rdwr_connected(tag):
851*    global only_one, no_wait
852*    summary("Tag connected: " + str(tag))
853*
854*    if tag.ndef:
855*        summary("NDEF tag: " + tag.type)
856*        summary(tag.ndef.records)
857*        success = dpp_tag_read(tag)
858*        if only_one and success:
859*            global continue_loop
860*            continue_loop = False
861*    else:
862*        summary("Not an NDEF tag - remove tag", color=C_RED)
863*        return True
864*
865*    return not no_wait
866*
867*def llcp_worker(llc, try_alt):
868*    global handover
869*    print("Start of llcp_worker()")
870*    if try_alt:
871*        summary("Starting handover client (try_alt)")
872*        dpp_handover_client(handover, alt=True)
873*        summary("Exiting llcp_worker thread (try_alt)")
874*        return
875*    global init_on_touch
876*    if init_on_touch:
877*        summary("Starting handover client (init_on_touch)")
878*        dpp_handover_client(handover)
879*        summary("Exiting llcp_worker thread (init_on_touch)")
880*        return
881*
882*    global no_input
883*    if no_input:
884*        summary("Wait for handover to complete")
885*    else:
886*        print("Wait for handover to complete - press 'i' to initiate")
887*    while not handover.wait_connection and handover.srv.sent_carrier is None:
888*        if handover.try_own:
889*            handover.try_own = False
890*            summary("Try to initiate another handover with own parameters")
891*            handover.my_crn_ready = False
892*            handover.my_crn = None
893*            handover.peer_crn = None
894*            handover.hs_sent = False
895*            dpp_handover_client(handover, alt=True)
896*            summary("Exiting llcp_worker thread (retry with own parameters)")
897*            return
898*        if handover.srv.ho_server_processing:
899*            time.sleep(0.025)
900*        elif no_input:
901*            time.sleep(0.5)
902*        else:
903*            res = getch()
904*            if res != 'i':
905*                continue
906*            clear_raw_mode()
907*            summary("Starting handover client")
908*            dpp_handover_client(handover)
909*            summary("Exiting llcp_worker thread (manual init)")
910*            return
911*
912*    global in_raw_mode
913*    was_in_raw_mode = in_raw_mode
914*    clear_raw_mode()
915*    if was_in_raw_mode:
916*        print("\r")
917*    summary("Exiting llcp_worker thread")
918*
919*class ConnectionHandover():
920*    def __init__(self):
921*        self.client = None
922*        self.client_thread = None
923*        self.reset()
924*        self.exit_thread = None
925*
926*    def reset(self):
927*        self.wait_connection = False
928*        self.my_crn_ready = False
929*        self.my_crn = None
930*        self.peer_crn = None
931*        self.hs_sent = False
932*        self.no_alt_proposal = False
933*        self.alt_proposal_used = False
934*        self.i_m_selector = False
935*        self.start_client_alt = False
936*        self.terminate_on_hs_send_completion = False
937*        self.try_own = False
938*        self.my_uri = None
939*        self.peer_uri = None
940*        self.connected = False
941*        self.alt_proposal = False
942*
943*    def start_handover_server(self, llc):
944*        summary("Start handover server")
945*        self.llc = llc
946*        self.srv = HandoverServer(self, llc)
947*
948*    def close(self):
949*        if self.client:
950*            self.client.close()
951*            self.client = None
952*
953*    def run_delayed_exit(self):
954*        summary("Trying to exit (delayed)..")
955*        time.sleep(0.25)
956*        summary("Trying to exit (after wait)..")
957*        global terminate_now
958*        terminate_now = True
959*
960*    def delayed_exit(self):
961*        global only_one
962*        if only_one:
963*            self.exit_thread = threading.Thread(target=self.run_delayed_exit)
964*            self.exit_thread.start()
965*
966*def llcp_startup(llc):
967*    global handover
968*    handover.start_handover_server(llc)
969*    return llc
970*
971*def llcp_connected(llc):
972*    summary("P2P LLCP connected")
973*    global handover
974*    handover.connected = True
975*    handover.srv.start()
976*    if init_on_touch or not no_input:
977*        handover.client_thread = threading.Thread(target=llcp_worker,
978*                                                  args=(llc, False))
979*        handover.client_thread.start()
980*    return True
981*
982*def llcp_release(llc):
983*    summary("LLCP release")
984*    global handover
985*    handover.close()
986*    return True
987*
988*def terminate_loop():
989*    global terminate_now
990*    return terminate_now
991*
992*def main():
993*    clf = nfc.ContactlessFrontend()
994*
995*    parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for DPP NFC operations')
996*    parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
997*                        action='store_const', dest='loglevel',
998*                        help='verbose debug output')
999*    parser.add_argument('-q', const=logging.WARNING, action='store_const',
1000*                        dest='loglevel', help='be quiet')
1001*    parser.add_argument('--only-one', '-1', action='store_true',
1002*                        help='run only one operation and exit')
1003*    parser.add_argument('--init-on-touch', '-I', action='store_true',
1004*                        help='initiate handover on touch')
1005*    parser.add_argument('--no-wait', action='store_true',
1006*                        help='do not wait for tag to be removed before exiting')
1007*    parser.add_argument('--ifname', '-i',
1008*                        help='network interface name')
1009*    parser.add_argument('--no-input', '-a', action='store_true',
1010*                        help='do not use stdout input to initiate handover')
1011*    parser.add_argument('--tag-read-only', '-t', action='store_true',
1012*                        help='tag read only (do not allow connection handover)')
1013*    parser.add_argument('--handover-only', action='store_true',
1014*                        help='connection handover only (do not allow tag read)')
1015*    parser.add_argument('--enrollee', action='store_true',
1016*                        help='run as Enrollee-only')
1017*    parser.add_argument('--configurator', action='store_true',
1018*                        help='run as Configurator-only')
1019*    parser.add_argument('--config-params', default='',
1020*                        help='configurator parameters')
1021*    parser.add_argument('--ctrl', default='/var/run/wpa_supplicant',
1022*                        help='wpa_supplicant/hostapd control interface')
1023*    parser.add_argument('--summary',
1024*                        help='summary file for writing status updates')
1025*    parser.add_argument('--success',
1026*                        help='success file for writing success update')
1027*    parser.add_argument('--device', default='usb', help='NFC device to open')
1028*    parser.add_argument('--chan', default=None, help='channel list')
1029*    parser.add_argument('--altchan', default=None, help='alternative channel list')
1030*    parser.add_argument('--netrole', default=None, help='netrole for Enrollee')
1031*    parser.add_argument('--test-uri', default=None,
1032*                        help='test mode: initial URI')
1033*    parser.add_argument('--test-alt-uri', default=None,
1034*                        help='test mode: alternative URI')
1035*    parser.add_argument('--test-sel-uri', default=None,
1036*                        help='test mode: handover select URI')
1037*    parser.add_argument('--test-crn', default=None,
1038*                        help='test mode: hardcoded crn')
1039*    parser.add_argument('command', choices=['write-nfc-uri',
1040*                                            'write-nfc-hs'],
1041*                        nargs='?')
1042*    args = parser.parse_args()
1043*    summary(args)
1044*
1045*    global handover
1046*    handover = ConnectionHandover()
1047*
1048*    global only_one
1049*    only_one = args.only_one
1050*
1051*    global no_wait
1052*    no_wait = args.no_wait
1053*
1054*    global chanlist, netrole, test_uri, test_alt_uri, test_sel_uri
1055*    global test_crn
1056*    chanlist = args.chan
1057*    handover.altchanlist = args.altchan
1058*    netrole = args.netrole
1059*    test_uri = args.test_uri
1060*    test_alt_uri = args.test_alt_uri
1061*    test_sel_uri = args.test_sel_uri
1062*    if args.test_crn:
1063*        test_crn = struct.pack('>H', int(args.test_crn))
1064*    else:
1065*        test_crn = None
1066*
1067*    logging.basicConfig(level=args.loglevel)
1068*    for l in ['nfc.clf.rcs380',
1069*              'nfc.clf.transport',
1070*              'nfc.clf.device',
1071*              'nfc.clf.__init__',
1072*              'nfc.llcp',
1073*              'nfc.handover']:
1074*        log = logging.getLogger(l)
1075*        log.setLevel(args.loglevel)
1076*
1077*    global init_on_touch
1078*    init_on_touch = args.init_on_touch
1079*
1080*    global enrollee_only
1081*    enrollee_only = args.enrollee
1082*
1083*    global configurator_only
1084*    configurator_only = args.configurator
1085*
1086*    global config_params
1087*    config_params = args.config_params
1088*
1089*    if args.ifname:
1090*        global ifname
1091*        ifname = args.ifname
1092*        summary("Selected ifname " + ifname)
1093*
1094*    if args.ctrl:
1095*        global wpas_ctrl
1096*        wpas_ctrl = args.ctrl
1097*
1098*    if args.summary:
1099*        global summary_file
1100*        summary_file = args.summary
1101*
1102*    if args.success:
1103*        global success_file
1104*        success_file = args.success
1105*
1106*    if args.no_input:
1107*        global no_input
1108*        no_input = True
1109*
1110*    clf = nfc.ContactlessFrontend()
1111*
1112*    try:
1113*        if not clf.open(args.device):
1114*            summary("Could not open connection with an NFC device", color=C_RED)
1115*            raise SystemExit(1)
1116*
1117*        if args.command == "write-nfc-uri":
1118*            write_nfc_uri(clf, wait_remove=not args.no_wait)
1119*            if not operation_success:
1120*                raise SystemExit(1)
1121*            raise SystemExit
1122*
1123*        if args.command == "write-nfc-hs":
1124*            write_nfc_hs(clf, wait_remove=not args.no_wait)
1125*            if not operation_success:
1126*                raise SystemExit(1)
1127*            raise SystemExit
1128*
1129*        global continue_loop
1130*        while continue_loop:
1131*            global in_raw_mode
1132*            was_in_raw_mode = in_raw_mode
1133*            clear_raw_mode()
1134*            if was_in_raw_mode:
1135*                print("\r")
1136*            if args.handover_only:
1137*                summary("Waiting a peer to be touched", color=C_MAGENTA)
1138*            elif args.tag_read_only:
1139*                summary("Waiting for a tag to be touched", color=C_BLUE)
1140*            else:
1141*                summary("Waiting for a tag or peer to be touched",
1142*                        color=C_GREEN)
1143*            handover.wait_connection = True
1144*            try:
1145*                if args.tag_read_only:
1146*                    if not clf.connect(rdwr={'on-connect': rdwr_connected}):
1147*                        break
1148*                elif args.handover_only:
1149*                    if not clf.connect(llcp={'on-startup': llcp_startup,
1150*                                             'on-connect': llcp_connected,
1151*                                             'on-release': llcp_release},
1152*                                       terminate=terminate_loop):
1153*                        break
1154*                else:
1155*                    if not clf.connect(rdwr={'on-connect': rdwr_connected},
1156*                                       llcp={'on-startup': llcp_startup,
1157*                                             'on-connect': llcp_connected,
1158*                                             'on-release': llcp_release},
1159*                                       terminate=terminate_loop):
1160*                        break
1161*            except Exception as e:
1162*                summary("clf.connect failed: " + str(e))
1163*                break
1164*
1165*            if only_one and handover.connected:
1166*                role = "selector" if handover.i_m_selector else "requestor"
1167*                summary("Connection handover result: I'm the %s" % role,
1168*                        color=C_YELLOW)
1169*                if handover.peer_uri:
1170*                    summary("Peer URI: " + handover.peer_uri, color=C_YELLOW)
1171*                if handover.my_uri:
1172*                    summary("My URI: " + handover.my_uri, color=C_YELLOW)
1173*                if not (handover.peer_uri and handover.my_uri):
1174*                    summary("Negotiated connection handover failed",
1175*                            color=C_YELLOW)
1176*                break
1177*
1178*    except KeyboardInterrupt:
1179*        raise SystemExit
1180*    finally:
1181*        clf.close()
1182*
1183*    raise SystemExit
1184*
1185*if __name__ == '__main__':
1186*    main()
1187*