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