xref: /netbsd-src/external/bsd/wpa/dist/wpa_supplicant/wpa_gui-qt4/peers.cpp (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*
2  * wpa_gui - Peers class
3  * Copyright (c) 2009, Atheros Communications
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14 
15 #include <cstdio>
16 #include <QImageReader>
17 #include <QMessageBox>
18 
19 #include "common/wpa_ctrl.h"
20 #include "wpagui.h"
21 #include "stringquery.h"
22 #include "peers.h"
23 
24 
25 enum {
26 	peer_role_address = Qt::UserRole + 1,
27 	peer_role_type,
28 	peer_role_uuid,
29 	peer_role_details,
30 	peer_role_pri_dev_type,
31 	peer_role_ssid,
32 	peer_role_config_methods,
33 	peer_role_dev_passwd_id,
34 	peer_role_bss_id
35 };
36 
37 /*
38  * TODO:
39  * - add current AP info (e.g., from WPS) in station mode
40  */
41 
42 enum peer_type {
43 	PEER_TYPE_ASSOCIATED_STATION,
44 	PEER_TYPE_AP,
45 	PEER_TYPE_AP_WPS,
46 	PEER_TYPE_WPS_PIN_NEEDED,
47 	PEER_TYPE_WPS_ER_AP,
48 	PEER_TYPE_WPS_ER_AP_UNCONFIGURED,
49 	PEER_TYPE_WPS_ER_ENROLLEE,
50 	PEER_TYPE_WPS_ENROLLEE
51 };
52 
53 
54 Peers::Peers(QWidget *parent, const char *, bool, Qt::WFlags)
55 	: QDialog(parent)
56 {
57 	setupUi(this);
58 
59 	if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
60 	{
61 		default_icon = new QIcon(":/icons/wpa_gui.svg");
62 		ap_icon = new QIcon(":/icons/ap.svg");
63 		laptop_icon = new QIcon(":/icons/laptop.svg");
64 	} else {
65 		default_icon = new QIcon(":/icons/wpa_gui.png");
66 		ap_icon = new QIcon(":/icons/ap.png");
67 		laptop_icon = new QIcon(":/icons/laptop.png");
68 	}
69 
70 	peers->setModel(&model);
71 	peers->setResizeMode(QListView::Adjust);
72 
73 	peers->setContextMenuPolicy(Qt::CustomContextMenu);
74 	connect(peers, SIGNAL(customContextMenuRequested(const QPoint &)),
75 		this, SLOT(context_menu(const QPoint &)));
76 
77 	wpagui = NULL;
78 }
79 
80 
81 void Peers::setWpaGui(WpaGui *_wpagui)
82 {
83 	wpagui = _wpagui;
84 	update_peers();
85 }
86 
87 
88 Peers::~Peers()
89 {
90 	delete default_icon;
91 	delete ap_icon;
92 	delete laptop_icon;
93 }
94 
95 
96 void Peers::languageChange()
97 {
98 	retranslateUi(this);
99 }
100 
101 
102 QString Peers::ItemType(int type)
103 {
104 	QString title;
105 	switch (type) {
106 	case PEER_TYPE_ASSOCIATED_STATION:
107 		title = tr("Associated station");
108 		break;
109 	case PEER_TYPE_AP:
110 		title = tr("AP");
111 		break;
112 	case PEER_TYPE_AP_WPS:
113 		title = tr("WPS AP");
114 		break;
115 	case PEER_TYPE_WPS_PIN_NEEDED:
116 		title = tr("WPS PIN needed");
117 		break;
118 	case PEER_TYPE_WPS_ER_AP:
119 		title = tr("ER: WPS AP");
120 		break;
121 	case PEER_TYPE_WPS_ER_AP_UNCONFIGURED:
122 		title = tr("ER: WPS AP (Unconfigured)");
123 		break;
124 	case PEER_TYPE_WPS_ER_ENROLLEE:
125 		title = tr("ER: WPS Enrollee");
126 		break;
127 	case PEER_TYPE_WPS_ENROLLEE:
128 		title = tr("WPS Enrollee");
129 		break;
130 	}
131 	return title;
132 }
133 
134 
135 void Peers::context_menu(const QPoint &pos)
136 {
137 	QMenu *menu = new QMenu;
138 	if (menu == NULL)
139 		return;
140 
141 	QModelIndex idx = peers->indexAt(pos);
142 	if (idx.isValid()) {
143 		ctx_item = model.itemFromIndex(idx);
144 		int type = ctx_item->data(peer_role_type).toInt();
145 		menu->addAction(Peers::ItemType(type))->setEnabled(false);
146 		menu->addSeparator();
147 
148 		int config_methods = -1;
149 		QVariant var = ctx_item->data(peer_role_config_methods);
150 		if (var.isValid())
151 			config_methods = var.toInt();
152 
153 		if ((type == PEER_TYPE_ASSOCIATED_STATION ||
154 		     type == PEER_TYPE_AP_WPS ||
155 		     type == PEER_TYPE_WPS_PIN_NEEDED ||
156 		     type == PEER_TYPE_WPS_ER_ENROLLEE ||
157 		     type == PEER_TYPE_WPS_ENROLLEE) &&
158 		    (config_methods == -1 || (config_methods & 0x010c))) {
159 			menu->addAction(tr("Enter WPS PIN"), this,
160 					SLOT(enter_pin()));
161 		}
162 
163 		if (type == PEER_TYPE_AP_WPS) {
164 			menu->addAction(tr("Connect (PBC)"), this,
165 					SLOT(connect_pbc()));
166 		}
167 
168 		if ((type == PEER_TYPE_ASSOCIATED_STATION ||
169 		     type == PEER_TYPE_WPS_ER_ENROLLEE ||
170 		     type == PEER_TYPE_WPS_ENROLLEE) &&
171 		    config_methods >= 0 && (config_methods & 0x0080)) {
172 			menu->addAction(tr("Enroll (PBC)"), this,
173 					SLOT(connect_pbc()));
174 		}
175 
176 		if (type == PEER_TYPE_WPS_ER_AP) {
177 			menu->addAction(tr("Learn Configuration"), this,
178 					SLOT(learn_ap_config()));
179 		}
180 
181 		menu->addAction(tr("Properties"), this, SLOT(properties()));
182 	} else {
183 		ctx_item = NULL;
184 		menu->addAction(QString(tr("Refresh")), this,
185 				SLOT(ctx_refresh()));
186 	}
187 
188 	menu->exec(peers->mapToGlobal(pos));
189 }
190 
191 
192 void Peers::enter_pin()
193 {
194 	if (ctx_item == NULL)
195 		return;
196 
197 	int peer_type = ctx_item->data(peer_role_type).toInt();
198 	QString uuid;
199 	QString addr;
200 	if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE)
201 		uuid = ctx_item->data(peer_role_uuid).toString();
202 	else
203 		addr = ctx_item->data(peer_role_address).toString();
204 
205 	StringQuery input(tr("PIN:"));
206 	input.setWindowTitle(tr("PIN for ") + ctx_item->text());
207 	if (input.exec() != QDialog::Accepted)
208 		return;
209 
210 	char cmd[100];
211 	char reply[100];
212 	size_t reply_len;
213 
214 	if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
215 		snprintf(cmd, sizeof(cmd), "WPS_ER_PIN %s %s",
216 			 uuid.toAscii().constData(),
217 			 input.get_string().toAscii().constData());
218 	} else {
219 		snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
220 			 addr.toAscii().constData(),
221 			 input.get_string().toAscii().constData());
222 	}
223 	reply_len = sizeof(reply) - 1;
224 	if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
225 		QMessageBox msg;
226 		msg.setIcon(QMessageBox::Warning);
227 		msg.setText(tr("Failed to set the WPS PIN."));
228 		msg.exec();
229 	}
230 }
231 
232 
233 void Peers::ctx_refresh()
234 {
235 	update_peers();
236 }
237 
238 
239 void Peers::add_station(QString info)
240 {
241 	QStringList lines = info.split(QRegExp("\\n"));
242 	QString name;
243 
244 	for (QStringList::Iterator it = lines.begin();
245 	     it != lines.end(); it++) {
246 		int pos = (*it).indexOf('=') + 1;
247 		if (pos < 1)
248 			continue;
249 
250 		if ((*it).startsWith("wpsDeviceName="))
251 			name = (*it).mid(pos);
252 	}
253 
254 	if (name.isEmpty())
255 		name = lines[0];
256 
257 	QStandardItem *item = new QStandardItem(*laptop_icon, name);
258 	if (item) {
259 		item->setData(lines[0], peer_role_address);
260 		item->setData(PEER_TYPE_ASSOCIATED_STATION,
261 			      peer_role_type);
262 		item->setData(info, peer_role_details);
263 		item->setToolTip(ItemType(PEER_TYPE_ASSOCIATED_STATION));
264 		model.appendRow(item);
265 	}
266 }
267 
268 
269 void Peers::add_stations()
270 {
271 	char reply[2048];
272 	size_t reply_len;
273 	char cmd[30];
274 	int res;
275 
276 	reply_len = sizeof(reply) - 1;
277 	if (wpagui->ctrlRequest("STA-FIRST", reply, &reply_len) < 0)
278 		return;
279 
280 	do {
281 		reply[reply_len] = '\0';
282 		QString info(reply);
283 		char *txt = reply;
284 		while (*txt != '\0' && *txt != '\n')
285 			txt++;
286 		*txt++ = '\0';
287 		if (strncmp(reply, "FAIL", 4) == 0 ||
288 		    strncmp(reply, "UNKNOWN", 7) == 0)
289 			break;
290 
291 		add_station(info);
292 
293 		reply_len = sizeof(reply) - 1;
294 		snprintf(cmd, sizeof(cmd), "STA-NEXT %s", reply);
295 		res = wpagui->ctrlRequest(cmd, reply, &reply_len);
296 	} while (res >= 0);
297 }
298 
299 
300 void Peers::add_single_station(const char *addr)
301 {
302 	char reply[2048];
303 	size_t reply_len;
304 	char cmd[30];
305 
306 	reply_len = sizeof(reply) - 1;
307 	snprintf(cmd, sizeof(cmd), "STA %s", addr);
308 	if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
309 		return;
310 
311 	reply[reply_len] = '\0';
312 	QString info(reply);
313 	char *txt = reply;
314 	while (*txt != '\0' && *txt != '\n')
315 		txt++;
316 	*txt++ = '\0';
317 	if (strncmp(reply, "FAIL", 4) == 0 ||
318 	    strncmp(reply, "UNKNOWN", 7) == 0)
319 		return;
320 
321 	add_station(info);
322 }
323 
324 
325 void Peers::remove_bss(int id)
326 {
327 	if (model.rowCount() == 0)
328 		return;
329 
330 	QModelIndexList lst = model.match(model.index(0, 0), peer_role_bss_id,
331 					  id);
332 	if (lst.size() == 0)
333 		return;
334 	model.removeRow(lst[0].row());
335 }
336 
337 
338 bool Peers::add_bss(const char *cmd)
339 {
340 	char reply[2048];
341 	size_t reply_len;
342 
343 	reply_len = sizeof(reply) - 1;
344 	if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
345 		return false;
346 	reply[reply_len] = '\0';
347 
348 	QString bss(reply);
349 	if (bss.isEmpty() || bss.startsWith("FAIL"))
350 		return false;
351 
352 	QString ssid, bssid, flags, wps_name, pri_dev_type;
353 	int id = -1;
354 
355 	QStringList lines = bss.split(QRegExp("\\n"));
356 	for (QStringList::Iterator it = lines.begin();
357 	     it != lines.end(); it++) {
358 		int pos = (*it).indexOf('=') + 1;
359 		if (pos < 1)
360 			continue;
361 
362 		if ((*it).startsWith("bssid="))
363 			bssid = (*it).mid(pos);
364 		else if ((*it).startsWith("id="))
365 			id = (*it).mid(pos).toInt();
366 		else if ((*it).startsWith("flags="))
367 			flags = (*it).mid(pos);
368 		else if ((*it).startsWith("ssid="))
369 			ssid = (*it).mid(pos);
370 		else if ((*it).startsWith("wps_device_name="))
371 			wps_name = (*it).mid(pos);
372 		else if ((*it).startsWith("wps_primary_device_type="))
373 			pri_dev_type = (*it).mid(pos);
374 	}
375 
376 	QString name = wps_name;
377 	if (name.isEmpty())
378 		name = ssid + "\n" + bssid;
379 
380 	QStandardItem *item = new QStandardItem(*ap_icon, name);
381 	if (item) {
382 		item->setData(bssid, peer_role_address);
383 		if (id >= 0)
384 			item->setData(id, peer_role_bss_id);
385 		int type;
386 		if (flags.contains("[WPS"))
387 			type = PEER_TYPE_AP_WPS;
388 		else
389 			type = PEER_TYPE_AP;
390 		item->setData(type, peer_role_type);
391 
392 		for (int i = 0; i < lines.size(); i++) {
393 			if (lines[i].length() > 60) {
394 				lines[i].remove(60, lines[i].length());
395 				lines[i] += "..";
396 			}
397 		}
398 		item->setToolTip(ItemType(type));
399 		item->setData(lines.join("\n"), peer_role_details);
400 		if (!pri_dev_type.isEmpty())
401 			item->setData(pri_dev_type,
402 				      peer_role_pri_dev_type);
403 		if (!ssid.isEmpty())
404 			item->setData(ssid, peer_role_ssid);
405 		model.appendRow(item);
406 	}
407 
408 	return true;
409 }
410 
411 
412 void Peers::add_scan_results()
413 {
414 	int index;
415 	char cmd[20];
416 
417 	index = 0;
418 	while (wpagui) {
419 		snprintf(cmd, sizeof(cmd), "BSS %d", index++);
420 		if (index > 1000)
421 			break;
422 
423 		if (!add_bss(cmd))
424 			break;
425 	}
426 }
427 
428 
429 void Peers::update_peers()
430 {
431 	model.clear();
432 	if (wpagui == NULL)
433 		return;
434 
435 	char reply[20];
436 	size_t replylen = sizeof(reply) - 1;
437 	wpagui->ctrlRequest("WPS_ER_START", reply, &replylen);
438 
439 	add_stations();
440 	add_scan_results();
441 }
442 
443 
444 QStandardItem * Peers::find_addr(QString addr)
445 {
446 	if (model.rowCount() == 0)
447 		return NULL;
448 
449 	QModelIndexList lst = model.match(model.index(0, 0), peer_role_address,
450 					  addr);
451 	if (lst.size() == 0)
452 		return NULL;
453 	return model.itemFromIndex(lst[0]);
454 }
455 
456 
457 QStandardItem * Peers::find_uuid(QString uuid)
458 {
459 	if (model.rowCount() == 0)
460 		return NULL;
461 
462 	QModelIndexList lst = model.match(model.index(0, 0), peer_role_uuid,
463 					  uuid);
464 	if (lst.size() == 0)
465 		return NULL;
466 	return model.itemFromIndex(lst[0]);
467 }
468 
469 
470 void Peers::event_notify(WpaMsg msg)
471 {
472 	QString text = msg.getMsg();
473 
474 	if (text.startsWith(WPS_EVENT_PIN_NEEDED)) {
475 		/*
476 		 * WPS-PIN-NEEDED 5a02a5fa-9199-5e7c-bc46-e183d3cb32f7
477 		 * 02:2a:c4:18:5b:f3
478 		 * [Wireless Client|Company|cmodel|123|12345|1-0050F204-1]
479 		 */
480 		QStringList items = text.split(' ');
481 		QString uuid = items[1];
482 		QString addr = items[2];
483 		QString name = "";
484 
485 		QStandardItem *item = find_addr(addr);
486 		if (item)
487 			return;
488 
489 		int pos = text.indexOf('[');
490 		if (pos >= 0) {
491 			int pos2 = text.lastIndexOf(']');
492 			if (pos2 >= pos) {
493 				items = text.mid(pos + 1, pos2 - pos - 1).
494 					split('|');
495 				name = items[0];
496 				items.append(addr);
497 			}
498 		}
499 
500 		item = new QStandardItem(*laptop_icon, name);
501 		if (item) {
502 			item->setData(addr, peer_role_address);
503 			item->setData(PEER_TYPE_WPS_PIN_NEEDED,
504 				      peer_role_type);
505 			item->setToolTip(ItemType(PEER_TYPE_WPS_PIN_NEEDED));
506 			item->setData(items.join("\n"), peer_role_details);
507 			item->setData(items[5], peer_role_pri_dev_type);
508 			model.appendRow(item);
509 		}
510 		return;
511 	}
512 
513 	if (text.startsWith(AP_STA_CONNECTED)) {
514 		/* AP-STA-CONNECTED 02:2a:c4:18:5b:f3 */
515 		QStringList items = text.split(' ');
516 		QString addr = items[1];
517 		QStandardItem *item = find_addr(addr);
518 		if (item == NULL || item->data(peer_role_type).toInt() !=
519 		    PEER_TYPE_ASSOCIATED_STATION)
520 			add_single_station(addr.toAscii().constData());
521 		return;
522 	}
523 
524 	if (text.startsWith(AP_STA_DISCONNECTED)) {
525 		/* AP-STA-DISCONNECTED 02:2a:c4:18:5b:f3 */
526 		QStringList items = text.split(' ');
527 		QString addr = items[1];
528 
529 		if (model.rowCount() == 0)
530 			return;
531 
532 		QModelIndexList lst = model.match(model.index(0, 0),
533 						  peer_role_address, addr);
534 		for (int i = 0; i < lst.size(); i++) {
535 			QStandardItem *item = model.itemFromIndex(lst[i]);
536 			if (item && item->data(peer_role_type).toInt() ==
537 			    PEER_TYPE_ASSOCIATED_STATION)
538 				model.removeRow(lst[i].row());
539 		}
540 		return;
541 	}
542 
543 	if (text.startsWith(WPS_EVENT_ER_AP_ADD)) {
544 		/*
545 		 * WPS-ER-AP-ADD 87654321-9abc-def0-1234-56789abc0002
546 		 * 02:11:22:33:44:55 pri_dev_type=6-0050F204-1 wps_state=1
547 		 * |Very friendly name|Company|Long description of the model|
548 		 * WAP|http://w1.fi/|http://w1.fi/hostapd/
549 		 */
550 		QStringList items = text.split(' ');
551 		if (items.size() < 5)
552 			return;
553 		QString uuid = items[1];
554 		QString addr = items[2];
555 		QString pri_dev_type = items[3].mid(13);
556 		int wps_state = items[4].mid(10).toInt();
557 
558 		int pos = text.indexOf('|');
559 		if (pos < 0)
560 			return;
561 		items = text.mid(pos + 1).split('|');
562 		if (items.size() < 1)
563 			return;
564 
565 		QStandardItem *item = find_uuid(uuid);
566 		if (item)
567 			return;
568 
569 		item = new QStandardItem(*ap_icon, items[0]);
570 		if (item) {
571 			item->setData(uuid, peer_role_uuid);
572 			item->setData(addr, peer_role_address);
573 			int type = wps_state == 2 ? PEER_TYPE_WPS_ER_AP:
574 				PEER_TYPE_WPS_ER_AP_UNCONFIGURED;
575 			item->setData(type, peer_role_type);
576 			item->setToolTip(ItemType(type));
577 			item->setData(pri_dev_type, peer_role_pri_dev_type);
578 			item->setData(items.join(QString("\n")),
579 				      peer_role_details);
580 			model.appendRow(item);
581 		}
582 
583 		return;
584 	}
585 
586 	if (text.startsWith(WPS_EVENT_ER_AP_REMOVE)) {
587 		/* WPS-ER-AP-REMOVE 87654321-9abc-def0-1234-56789abc0002 */
588 		QStringList items = text.split(' ');
589 		if (items.size() < 2)
590 			return;
591 		if (model.rowCount() == 0)
592 			return;
593 
594 		QModelIndexList lst = model.match(model.index(0, 0),
595 						  peer_role_uuid, items[1]);
596 		for (int i = 0; i < lst.size(); i++) {
597 			QStandardItem *item = model.itemFromIndex(lst[i]);
598 			if (item &&
599 			    (item->data(peer_role_type).toInt() ==
600 			     PEER_TYPE_WPS_ER_AP ||
601 			     item->data(peer_role_type).toInt() ==
602 			     PEER_TYPE_WPS_ER_AP_UNCONFIGURED))
603 				model.removeRow(lst[i].row());
604 		}
605 		return;
606 	}
607 
608 	if (text.startsWith(WPS_EVENT_ER_ENROLLEE_ADD)) {
609 		/*
610 		 * WPS-ER-ENROLLEE-ADD 2b7093f1-d6fb-5108-adbb-bea66bb87333
611 		 * 02:66:a0:ee:17:27 M1=1 config_methods=0x14d dev_passwd_id=0
612 		 * pri_dev_type=1-0050F204-1
613 		 * |Wireless Client|Company|cmodel|123|12345|
614 		 */
615 		QStringList items = text.split(' ');
616 		if (items.size() < 3)
617 			return;
618 		QString uuid = items[1];
619 		QString addr = items[2];
620 		QString pri_dev_type = items[6].mid(13);
621 		int config_methods = -1;
622 		int dev_passwd_id = -1;
623 
624 		for (int i = 3; i < items.size(); i++) {
625 			int pos = items[i].indexOf('=') + 1;
626 			if (pos < 1)
627 				continue;
628 			QString val = items[i].mid(pos);
629 			if (items[i].startsWith("config_methods=")) {
630 				config_methods = val.toInt(0, 0);
631 			} else if (items[i].startsWith("dev_passwd_id=")) {
632 				dev_passwd_id = val.toInt();
633 			}
634 		}
635 
636 		int pos = text.indexOf('|');
637 		if (pos < 0)
638 			return;
639 		items = text.mid(pos + 1).split('|');
640 		if (items.size() < 1)
641 			return;
642 		QString name = items[0];
643 		if (name.length() == 0)
644 			name = addr;
645 
646 		remove_enrollee_uuid(uuid);
647 
648 		QStandardItem *item;
649 		item = new QStandardItem(*laptop_icon, name);
650 		if (item) {
651 			item->setData(uuid, peer_role_uuid);
652 			item->setData(addr, peer_role_address);
653 			item->setData(PEER_TYPE_WPS_ER_ENROLLEE,
654 				      peer_role_type);
655 			item->setToolTip(ItemType(PEER_TYPE_WPS_ER_ENROLLEE));
656 			item->setData(items.join(QString("\n")),
657 				      peer_role_details);
658 			item->setData(pri_dev_type, peer_role_pri_dev_type);
659 			if (config_methods >= 0)
660 				item->setData(config_methods,
661 					      peer_role_config_methods);
662 			if (dev_passwd_id >= 0)
663 				item->setData(dev_passwd_id,
664 					      peer_role_dev_passwd_id);
665 			model.appendRow(item);
666 		}
667 
668 		return;
669 	}
670 
671 	if (text.startsWith(WPS_EVENT_ER_ENROLLEE_REMOVE)) {
672 		/*
673 		 * WPS-ER-ENROLLEE-REMOVE 2b7093f1-d6fb-5108-adbb-bea66bb87333
674 		 * 02:66:a0:ee:17:27
675 		 */
676 		QStringList items = text.split(' ');
677 		if (items.size() < 2)
678 			return;
679 		remove_enrollee_uuid(items[1]);
680 		return;
681 	}
682 
683 	if (text.startsWith(WPS_EVENT_ENROLLEE_SEEN)) {
684 		/* TODO: need to time out this somehow or remove on successful
685 		 * WPS run, etc. */
686 		/*
687 		 * WPS-ENROLLEE-SEEN 02:00:00:00:01:00
688 		 * 572cf82f-c957-5653-9b16-b5cfb298abf1 1-0050F204-1 0x80 4 1
689 		 * [Wireless Client]
690 		 * (MAC addr, UUID-E, pri dev type, config methods,
691 		 * dev passwd id, request type, [dev name])
692 		 */
693 		QStringList items = text.split(' ');
694 		if (items.size() < 7)
695 			return;
696 		QString addr = items[1];
697 		QString uuid = items[2];
698 		QString pri_dev_type = items[3];
699 		int config_methods = items[4].toInt(0, 0);
700 		int dev_passwd_id = items[5].toInt();
701 		QString name;
702 
703 		int pos = text.indexOf('[');
704 		if (pos >= 0) {
705 			int pos2 = text.lastIndexOf(']');
706 			if (pos2 >= pos) {
707 				QStringList items2 =
708 					text.mid(pos + 1, pos2 - pos - 1).
709 					split('|');
710 				name = items2[0];
711 			}
712 		}
713 		if (name.isEmpty())
714 			name = addr;
715 
716 		QStandardItem *item;
717 
718 		item = find_uuid(uuid);
719 		if (item) {
720 			QVariant var = item->data(peer_role_config_methods);
721 			QVariant var2 = item->data(peer_role_dev_passwd_id);
722 			if ((var.isValid() && config_methods != var.toInt()) ||
723 			    (var2.isValid() && dev_passwd_id != var2.toInt()))
724 				remove_enrollee_uuid(uuid);
725 			else
726 				return;
727 		}
728 
729 		item = new QStandardItem(*laptop_icon, name);
730 		if (item) {
731 			item->setData(uuid, peer_role_uuid);
732 			item->setData(addr, peer_role_address);
733 			item->setData(PEER_TYPE_WPS_ENROLLEE,
734 				      peer_role_type);
735 			item->setToolTip(ItemType(PEER_TYPE_WPS_ENROLLEE));
736 			item->setData(items.join(QString("\n")),
737 				      peer_role_details);
738 			item->setData(pri_dev_type, peer_role_pri_dev_type);
739 			item->setData(config_methods,
740 				      peer_role_config_methods);
741 			item->setData(dev_passwd_id, peer_role_dev_passwd_id);
742 			model.appendRow(item);
743 		}
744 
745 		return;
746 	}
747 
748 	if (text.startsWith(WPA_EVENT_BSS_ADDED)) {
749 		/* CTRL-EVENT-BSS-ADDED 34 00:11:22:33:44:55 */
750 		QStringList items = text.split(' ');
751 		if (items.size() < 2)
752 			return;
753 		char cmd[20];
754 		snprintf(cmd, sizeof(cmd), "BSS ID-%d", items[1].toInt());
755 		add_bss(cmd);
756 		return;
757 	}
758 
759 	if (text.startsWith(WPA_EVENT_BSS_REMOVED)) {
760 		/* CTRL-EVENT-BSS-REMOVED 34 00:11:22:33:44:55 */
761 		QStringList items = text.split(' ');
762 		if (items.size() < 2)
763 			return;
764 		remove_bss(items[1].toInt());
765 		return;
766 	}
767 }
768 
769 
770 void Peers::closeEvent(QCloseEvent *)
771 {
772 	if (wpagui) {
773 		char reply[20];
774 		size_t replylen = sizeof(reply) - 1;
775 		wpagui->ctrlRequest("WPS_ER_STOP", reply, &replylen);
776 	}
777 }
778 
779 
780 void Peers::done(int r)
781 {
782 	QDialog::done(r);
783 	close();
784 }
785 
786 
787 void Peers::remove_enrollee_uuid(QString uuid)
788 {
789 	if (model.rowCount() == 0)
790 		return;
791 
792 	QModelIndexList lst = model.match(model.index(0, 0),
793 					  peer_role_uuid, uuid);
794 	for (int i = 0; i < lst.size(); i++) {
795 		QStandardItem *item = model.itemFromIndex(lst[i]);
796 		if (item == NULL)
797 			continue;
798 		int type = item->data(peer_role_type).toInt();
799 		if (type == PEER_TYPE_WPS_ER_ENROLLEE ||
800 		    type == PEER_TYPE_WPS_ENROLLEE)
801 			model.removeRow(lst[i].row());
802 	}
803 }
804 
805 
806 void Peers::properties()
807 {
808 	if (ctx_item == NULL)
809 		return;
810 
811 	QMessageBox msg(this);
812 	msg.setStandardButtons(QMessageBox::Ok);
813 	msg.setDefaultButton(QMessageBox::Ok);
814 	msg.setEscapeButton(QMessageBox::Ok);
815 	msg.setWindowTitle(tr("Peer Properties"));
816 
817 	int type = ctx_item->data(peer_role_type).toInt();
818 	QString title = Peers::ItemType(type);
819 
820 	msg.setText(title + QString("\n") + tr("Name: ") + ctx_item->text());
821 
822 	QVariant var;
823 	QString info;
824 
825 	var = ctx_item->data(peer_role_address);
826 	if (var.isValid())
827 		info += tr("Address: ") + var.toString() + QString("\n");
828 
829 	var = ctx_item->data(peer_role_uuid);
830 	if (var.isValid())
831 		info += tr("UUID: ") + var.toString() + QString("\n");
832 
833 	var = ctx_item->data(peer_role_pri_dev_type);
834 	if (var.isValid())
835 		info += tr("Primary Device Type: ") + var.toString() +
836 			QString("\n");
837 
838 	var = ctx_item->data(peer_role_ssid);
839 	if (var.isValid())
840 		info += tr("SSID: ") + var.toString() + QString("\n");
841 
842 	var = ctx_item->data(peer_role_config_methods);
843 	if (var.isValid()) {
844 		int methods = var.toInt();
845 		info += tr("Configuration Methods: ");
846 		if (methods & 0x0001)
847 			info += tr("[USBA]");
848 		if (methods & 0x0002)
849 			info += tr("[Ethernet]");
850 		if (methods & 0x0004)
851 			info += tr("[Label]");
852 		if (methods & 0x0008)
853 			info += tr("[Display]");
854 		if (methods & 0x0010)
855 			info += tr("[Ext. NFC Token]");
856 		if (methods & 0x0020)
857 			info += tr("[Int. NFC Token]");
858 		if (methods & 0x0040)
859 			info += tr("[NFC Interface]");
860 		if (methods & 0x0080)
861 			info += tr("[Push Button]");
862 		if (methods & 0x0100)
863 			info += tr("[Keypad]");
864 		info += "\n";
865 	}
866 
867 	var = ctx_item->data(peer_role_dev_passwd_id);
868 	if (var.isValid()) {
869 		info += tr("Device Password ID: ") + var.toString();
870 		switch (var.toInt()) {
871 		case 0:
872 			info += tr(" (Default PIN)");
873 			break;
874 		case 1:
875 			info += tr(" (User-specified PIN)");
876 			break;
877 		case 2:
878 			info += tr(" (Machine-specified PIN)");
879 			break;
880 		case 3:
881 			info += tr(" (Rekey)");
882 			break;
883 		case 4:
884 			info += tr(" (Push Button)");
885 			break;
886 		case 5:
887 			info += tr(" (Registrar-specified)");
888 			break;
889 		}
890 		info += "\n";
891 	}
892 
893 	msg.setInformativeText(info);
894 
895 	var = ctx_item->data(peer_role_details);
896 	if (var.isValid())
897 		msg.setDetailedText(var.toString());
898 
899 	msg.exec();
900 }
901 
902 
903 void Peers::connect_pbc()
904 {
905 	if (ctx_item == NULL)
906 		return;
907 
908 	char cmd[100];
909 	char reply[100];
910 	size_t reply_len;
911 
912 	int peer_type = ctx_item->data(peer_role_type).toInt();
913 	if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
914 		snprintf(cmd, sizeof(cmd), "WPS_ER_PBC %s",
915 			 ctx_item->data(peer_role_uuid).toString().toAscii().
916 			 constData());
917 	} else {
918 		snprintf(cmd, sizeof(cmd), "WPS_PBC");
919 	}
920 	reply_len = sizeof(reply) - 1;
921 	if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
922 		QMessageBox msg;
923 		msg.setIcon(QMessageBox::Warning);
924 		msg.setText(tr("Failed to start WPS PBC."));
925 		msg.exec();
926 	}
927 }
928 
929 
930 void Peers::learn_ap_config()
931 {
932 	if (ctx_item == NULL)
933 		return;
934 
935 	QString uuid = ctx_item->data(peer_role_uuid).toString();
936 
937 	StringQuery input(tr("AP PIN:"));
938 	input.setWindowTitle(tr("AP PIN for ") + ctx_item->text());
939 	if (input.exec() != QDialog::Accepted)
940 		return;
941 
942 	char cmd[100];
943 	char reply[100];
944 	size_t reply_len;
945 
946 	snprintf(cmd, sizeof(cmd), "WPS_ER_LEARN %s %s",
947 		 uuid.toAscii().constData(),
948 		 input.get_string().toAscii().constData());
949 	reply_len = sizeof(reply) - 1;
950 	if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
951 		QMessageBox msg;
952 		msg.setIcon(QMessageBox::Warning);
953 		msg.setText(tr("Failed to start learning AP configuration."));
954 		msg.exec();
955 	}
956 }
957