Skip to content
Snippets Groups Projects
Commit df17fe39 authored by goapunk's avatar goapunk
Browse files

wfd1


Signed-off-by: default avatargoapunk <noobie@goapunks.net>
parent 5d84c41a
Branches 39-keyagreement-wfd
No related tags found
No related merge requests found
Pipeline #
......@@ -15,7 +15,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tcp.WFDPluginFactory;
import org.briarproject.bramble.plugin.wfd.WFDPluginFactory;
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
import java.security.SecureRandom;
......
package org.briarproject.bramble.plugin.tcp;
package org.briarproject.bramble.plugin.wfd;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
......@@ -13,11 +12,13 @@ import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.os.Build;
import android.os.Bundle;
import android.net.wifi.p2p.WifiP2pManager.ChannelListener;
import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Looper;
import android.widget.Toast;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
......@@ -25,68 +26,93 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.plugin.tcp.TcpTransportConnection;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Observable;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.net.wifi.p2p.WifiP2pDevice.*;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_NETWORK_INFO;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_P2P_DEVICE_LIST;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_WIFI_P2P_GROUP;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_WIFI_P2P_DEVICE;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_WIFI_P2P_INFO;
import static android.net.wifi.p2p.WifiP2pManager.EXTRA_WIFI_STATE;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static java.util.concurrent.TimeUnit.*;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_WFD;
import static org.briarproject.bramble.api.plugin.WFDConstants.ERROR_CODES;
import static org.briarproject.bramble.api.plugin.WFDConstants.GO_ADDRESS;
import static org.briarproject.bramble.api.plugin.WFDConstants.ID;
import static org.briarproject.bramble.api.plugin.WFDConstants.MAC_ADDRESS;
import static org.briarproject.bramble.api.plugin.WFDConstants.SOCKET_PORT;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
class WFDPlugin extends AndroidLanTcpPlugin
implements WifiP2pManager.ChannelListener,
WifiP2pManager.ActionListener, WifiP2pManager.GroupInfoListener {
class WFDPlugin implements DuplexPlugin, ChannelListener {
private static final Logger LOG =
Logger.getLogger(WFDPlugin.class.getName());
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running;
private final AtomicBoolean connected = new AtomicBoolean(false);
private volatile boolean groupOwner = false;
private final Executor ioExecutor;
private final Backoff backoff;
private final Context appContext;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final int maxIdleTime;
private final int socketTimeout;
private volatile boolean running = false;
private volatile String macAddress = null;
private volatile WifiP2pDeviceList currentDevices = null;
private volatile ServerSocket serverSocket;
private volatile CountDownLatch contactExchangeLatch;
private volatile Object deviceListRefresh = new Object();
private Channel channel;
private WifiP2pManager wfdManager;
private volatile WifiP2pGroup group;
private volatile WifiP2pInfo wifiP2pInfo;
private volatile Object groupInfoReady = new Object();
private volatile Object connected = new Object();
private volatile boolean isConnected = false;
private volatile ServerSocket serverSocket;
private volatile boolean onFailure = false;
private volatile boolean hasConnectionInitiated = false;
private NetworkStateReceiver networkStateReceiver;
private BroadcastReceiver networkStateReceiver;
private int state;
private boolean connectionInitiated = false;
WFDPlugin(Executor ioExecutor,
Backoff backoff, Context appContext,
DuplexPluginCallback callback, int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, appContext, callback, maxLatency,
maxIdleTime);
this.ioExecutor = ioExecutor;
this.backoff = backoff;
this.appContext = appContext;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
}
@Override
......@@ -112,15 +138,28 @@ class WFDPlugin extends AndroidLanTcpPlugin
.getSystemService(Context.WIFI_P2P_SERVICE);
channel =
wfdManager.initialize(appContext, Looper.getMainLooper(), this);
// Try to load our MAC address from settings
Settings settings = callback.getSettings();
macAddress = settings.get(MAC_ADDRESS);
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(WIFI_P2P_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_CONNECTION_CHANGED_ACTION);
filter.addAction(WIFI_P2P_PEERS_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
}
@Override
public void stop() {
running = false;
tryToClose(socket);
if (networkStateReceiver != null)
appContext.unregisterReceiver(networkStateReceiver);
// Probably not necessary
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
wfdManager.stopPeerDiscovery(channel, null);
}
}
@Override
......@@ -154,97 +193,122 @@ class WFDPlugin extends AndroidLanTcpPlugin
return true;
}
@Nullable
@Override
public KeyAgreementListener createKeyAgreementListener(
byte[] localCommitment) {
if (!isRunning()) return null;
IntentFilter filter =
new IntentFilter(WIFI_P2P_CONNECTION_CHANGED_ACTION);
IntentFilter filter1 = new IntentFilter(WIFI_P2P_PEERS_CHANGED_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
appContext.registerReceiver(networkStateReceiver, filter1);
wfdManager.createGroup(channel, this);
onFailure = false;
synchronized (groupInfoReady) {
try {
groupInfoReady.wait();
if (onFailure) {
return null;
class MacCallable implements Callable<String>, GroupInfoListener {
private volatile String mac = null;
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public String call() {
wfdManager.createGroup(channel, new ActionListener() {
@Override
public void onSuccess() {
wfdManager.requestGroupInfo(channel, MacCallable.this);
}
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
synchronized (this) {
@Override
public void onFailure(int reason) {
LOG.info("Failed to create wfd group:" +
ERROR_CODES[reason]);
latch.countDown();
}
});
try {
wait(1000);
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return mac;
}
wfdManager.requestGroupInfo(channel, this);
synchronized (groupInfoReady) {
try {
groupInfoReady.wait();
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
mac = group.getOwner().deviceAddress;
latch.countDown();
}
if (group == null) {
throw new IllegalStateException();
}
private void updateMacAddress(String mac) {
if (macAddress == null)
macAddress = mac;
else if (mac != null && !macAddress.equals(mac)) {
// TODO: scrub
LOG.info("Mac address differs:" + macAddress + " <-> " + mac);
macAddress = mac;
}
wfdManager.removeGroup(channel, this);
synchronized (groupInfoReady) {
try {
groupInfoReady.wait();
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
private Future<String> getMacAddress() {
return new FutureTask<>(new MacCallable());
}
private boolean isConnectedTo(String mac) {
synchronized (deviceListRefresh) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
return currentDevices.get(mac).status == CONNECTED;
} else {
for (WifiP2pDevice d : currentDevices.getDeviceList()) {
if (d.deviceAddress.equals(mac))
return d.status == CONNECTED;
}
}
return false;
}
wfdManager.discoverPeers(channel, null);
synchronized (this) {
}
@Nullable
@Override
public KeyAgreementListener createKeyAgreementListener(
byte[] localCommitment) {
if (!isRunning()) return null;
connectionInitiated = false;
if (macAddress == null) {
try {
wait(1000);
macAddress = getMacAddress().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/*
synchronized (connected) {
try {
if(group == null)
connected.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
return null;
if (macAddress == null)
throw new IllegalStateException();
// TODO: Can be done on plugin start ?
// connect() only works if we discovered the device already
wfdManager.discoverPeers(channel, new ActionListener() {
@Override
public void onSuccess() {
LOG.info("Initiated peer discovery");
}
}
*/
@Override
public void onFailure(int reason) {
LOG.info("Initiating peer discovery failed:" +
ERROR_CODES[reason]);
}
});
try {
serverSocket = new ServerSocket();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_WFD);
descriptor.add(StringUtils.toUtf8(group.getOwner().deviceAddress));
//descriptor.add(StringUtils.toUtf8(group.getPassphrase()));
descriptor.add(StringUtils.toUtf8(macAddress));
return new WFDKeyAgreementListener(descriptor, serverSocket, connected);
}
class WFDKeyAgreementListener extends LanKeyAgreementListener {
class WFDKeyAgreementListener extends KeyAgreementListener {
Object connected;
final AtomicBoolean connected;
final ServerSocket ss;
private volatile boolean close = false;
WFDKeyAgreementListener(BdfList descriptor, ServerSocket ss,
Object connected) {
super(descriptor, ss);
AtomicBoolean connected) {
super(descriptor);
this.ss = ss;
this.connected = connected;
}
......@@ -252,20 +316,35 @@ class WFDPlugin extends AndroidLanTcpPlugin
public Callable<KeyAgreementConnection> listen() {
return () -> {
synchronized (connected) {
LOG.info("ServerSocket: isBound:" + ss.isBound() +
"isClosed:" + ss.isClosed());
connected.wait();
if (!connected.get())
connected.wait(CONNECTION_TIMEOUT);
if (close)
throw new InterruptedException();
}
LOG.info(
"ServerSocket1: isBound:" + ss.isBound() + "isClosed:" +
ss.isClosed());
// Fails if we are not the GO.
serverSocket.bind(new InetSocketAddress(GO_ADDRESS,
SOCKET_PORT));
Socket s = ss.accept();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new TcpTransportConnection(WFDPlugin.this, s), ID);
new TcpTransportConnection(WFDPlugin.this, s),
ID);
};
}
@Override
public void close() {
try {
close = true;
synchronized (connected) {
connected.notifyAll();
}
ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
......@@ -274,32 +353,29 @@ class WFDPlugin extends AndroidLanTcpPlugin
public DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
if (!hasConnectionInitiated) {
if (!connectionInitiated) {
WifiP2pConfig config = new WifiP2pConfig();
try {
config.deviceAddress = parseMACAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid MAC address in key agreement descriptor");
LOG.info(
"Invalid MAC address in key agreement descriptor");
return null;
}
config.wps.setup = WpsInfo.PBC;
config.groupOwnerIntent = 0;
LOG.info("Connecting to Wifi P2P: " + config.deviceAddress);
isConnected = false;
wfdManager.connect(channel, config, this);
hasConnectionInitiated = true;
// TODO: Pass listener for error handling
wfdManager.connect(channel, config, null);
connectionInitiated = true;
}
// Not connected yet or GO. The GO doesn't know the addr of the
// other device.
if (!connected.get() || groupOwner)
return null;
InetSocketAddress remote =
new InetSocketAddress("192.168.49.1",
new InetSocketAddress(GO_ADDRESS,
SOCKET_PORT);
/*
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " );
}
return null;
}*/
Socket s = new Socket();
try {
if (LOG.isLoggable(INFO))
......@@ -318,7 +394,8 @@ class WFDPlugin extends AndroidLanTcpPlugin
}
private String parseMACAddress(BdfList descriptor) throws FormatException {
private static String parseMACAddress(BdfList descriptor) throws
FormatException {
String mac;
mac = StringUtils.fromUtf8(descriptor.getRaw(1));
if (mac.isEmpty())
......@@ -332,82 +409,60 @@ class WFDPlugin extends AndroidLanTcpPlugin
LOG.info("Channel disconnected");
}
@Override
public void onSuccess() {
//TODO
LOG.info("onSuccess");
synchronized (groupInfoReady) {
groupInfoReady.notify();
}
}
@Override
public void onFailure(int reason) {
//TODO
LOG.info("onFailure:" + reason);
onFailure = true;
synchronized (groupInfoReady) {
groupInfoReady.notify();
}
}
@Override
public void onGroupInfoAvailable(WifiP2pGroup group) {
if (group == null) {
LOG.info("GroupInfo is null");
} else
LOG.info("GroupInfo available");
this.group = group;
synchronized (groupInfoReady) {
groupInfoReady.notify();
}
}
private class NetworkStateReceiver extends BroadcastReceiver {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
public void onReceive(Context context, Intent intent) {
if (!running) return;
if (intent.getAction().compareTo(WIFI_P2P_PEERS_CHANGED_ACTION) ==
0) {
WifiP2pDeviceList list =
intent.getParcelableExtra(EXTRA_P2P_DEVICE_LIST);
String peers = "";
for (WifiP2pDevice d : list.getDeviceList()) {
peers = peers + d.deviceAddress + "\n";
if (intent.getAction().equals(WIFI_P2P_STATE_CHANGED_ACTION)) {
state = intent.getIntExtra(EXTRA_WIFI_STATE, 1);
if (state == WIFI_P2P_STATE_ENABLED) {
if (LOG.isLoggable(INFO))
LOG.info("Wifi Direct enabled");
} else {
if (LOG.isLoggable(INFO))
LOG.info("Wifi Direct disabled");
}
LOG.info("P2P peers:\n" + peers);
} else {
NetworkInfo info =
} else if (intent.getAction()
.equals(WIFI_P2P_PEERS_CHANGED_ACTION)) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
synchronized (deviceListRefresh) {
currentDevices =
intent.getParcelableExtra(
WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
deviceListRefresh.notifyAll();
}
}
} else if (intent.getAction()
.equals(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) {
WifiP2pDevice device =
intent.getParcelableExtra(EXTRA_WIFI_P2P_DEVICE);
updateMacAddress(device.deviceAddress);
} else if (intent.getAction()
.equals(WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
// Get the relevant Extras
WifiP2pInfo wifiP2pInfo =
intent.getParcelableExtra(EXTRA_WIFI_P2P_INFO);
NetworkInfo networkInfo =
intent.getParcelableExtra(EXTRA_NETWORK_INFO);
wifiP2pInfo = intent.getParcelableExtra(EXTRA_WIFI_P2P_INFO);
WifiP2pGroup groupt =
intent.getParcelableExtra(EXTRA_WIFI_P2P_GROUP);
String clients = "";
for (WifiP2pDevice d : groupt.getClientList()) {
clients = clients + d.deviceAddress + "\n";
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
WifiP2pGroup group = intent.getParcelableExtra(
WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
if (group.isGroupOwner())
updateMacAddress(group.getOwner().deviceAddress);
}
LOG.info("Connected clients:\n" + clients);
if (info.isConnected()) {
if (serverSocket != null && (!serverSocket.isBound() ||
serverSocket.isClosed()))
try {
serverSocket.bind(new InetSocketAddress(
wifiP2pInfo.groupOwnerAddress
.getHostAddress(), SOCKET_PORT));
LOG.info("ServerSocket bound");
} catch (IOException e) {
e.printStackTrace();
}
LOG.info("Connected to WFD device (Group formed:" +
wifiP2pInfo.groupFormed + " Group owner:" +
wifiP2pInfo.isGroupOwner + " Owneraddr:" +
wifiP2pInfo.groupOwnerAddress);
if (!networkInfo.isConnected() || !wifiP2pInfo.groupFormed) {
connected.set(false);
}
if (networkInfo.isConnected() && wifiP2pInfo.groupFormed) {
LOG.info("WifiP2P connection established");
// Signal we are connected. The GO should listen now.
synchronized (connected) {
isConnected = true;
connected.notify();
connected.set(true);
groupOwner = wifiP2pInfo.isGroupOwner;
if (groupOwner) {
connected.notifyAll();
}
}
}
}
......
package org.briarproject.bramble.plugin.tcp;
package org.briarproject.bramble.plugin.wfd;
import android.content.Context;
......
......@@ -5,4 +5,10 @@ public interface WFDConstants {
TransportId ID = new TransportId("org.briarproject.bramble.wfd");
int SOCKET_PORT = 54321;
}
String MAC_ADDRESS = "wfd_mac_address";
String GO_ADDRESS = "192.168.49.1";
String[] ERROR_CODES = {"ERROR", "P2P_UNSUPPORTED", "BUSY"};
}
\ No newline at end of file
......@@ -14,11 +14,11 @@ import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
class TcpTransportConnection extends AbstractDuplexTransportConnection {
public class TcpTransportConnection extends AbstractDuplexTransportConnection {
private final Socket socket;
TcpTransportConnection(Plugin plugin, Socket socket) {
public TcpTransportConnection(Plugin plugin, Socket socket) {
super(plugin);
this.socket = socket;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment