blob: 8e865f3fcd33818e363773888fbfa8fb665f25c6 [file] [log] [blame]
yuezonghe824eb0c2024-06-27 02:32:26 -07001#!/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
10import binascii
11import errno
12import os
13import struct
14import sys
15import time
16import threading
17import argparse
18
19import nfc
20import ndef
21
22import logging
23
24scriptsdir = os.path.dirname(os.path.realpath(sys.modules[__name__].__file__))
25sys.path.append(os.path.join(scriptsdir, '..', '..', 'wpaspy'))
26import wpaspy
27
28wpas_ctrl = '/var/run/wpa_supplicant'
29ifname = None
30init_on_touch = False
31in_raw_mode = False
32prev_tcgetattr = 0
33no_input = False
34continue_loop = True
35terminate_now = False
36summary_file = None
37success_file = None
38netrole = None
39operation_success = False
40mutex = threading.Lock()
41
42C_NORMAL = '\033[0m'
43C_RED = '\033[91m'
44C_GREEN = '\033[92m'
45C_YELLOW = '\033[93m'
46C_BLUE = '\033[94m'
47C_MAGENTA = '\033[95m'
48C_CYAN = '\033[96m'
49
50def 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
60def success_report(txt):
61 summary(txt)
62 if success_file:
63 with open(success_file, 'a') as f:
64 f.write(txt + "\n")
65
66def 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
94def 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
120def 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
143def 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
160def 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
166def 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
172def 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
195def 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
220def 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
253def 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
261def 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
269def 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
276def 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
282class 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
329def 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
525class 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
729def 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
738def 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
755def 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
781def 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
808def 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
826def 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
850def 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
867def 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
919class 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
966def llcp_startup(llc):
967 global handover
968 handover.start_handover_server(llc)
969 return llc
970
971def 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
982def llcp_release(llc):
983 summary("LLCP release")
984 global handover
985 handover.close()
986 return True
987
988def terminate_loop():
989 global terminate_now
990 return terminate_now
991
992def 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
1185if __name__ == '__main__':
1186 main()