|
@@ -31,6 +31,7 @@ import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
|
|
|
import java.net.InetAddress;
|
|
|
+import java.net.InetSocketAddress;
|
|
|
import java.net.MalformedURLException;
|
|
|
import java.net.UnknownHostException;
|
|
|
import java.net.URL;
|
|
@@ -49,7 +50,10 @@ import java.security.cert.CertificateException;
|
|
|
import java.security.cert.X509Certificate;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
import javax.jmdns.JmDNS;
|
|
|
import javax.jmdns.ServiceInfo;
|
|
@@ -67,13 +71,26 @@ import com.google.anymote.device.MessageReceiver;
|
|
|
|
|
|
import com.google.polo.exception.PoloException;
|
|
|
|
|
|
+import com.google.polo.pairing.ClientPairingSession;
|
|
|
import com.google.polo.pairing.PairingContext;
|
|
|
+import com.google.polo.pairing.PairingListener;
|
|
|
+import com.google.polo.pairing.PairingSession;
|
|
|
+
|
|
|
+import com.google.polo.pairing.message.EncodingOption;
|
|
|
|
|
|
import com.google.polo.ssl.DummySSLSocketFactory;
|
|
|
import com.google.polo.ssl.SslUtil;
|
|
|
|
|
|
+import com.google.polo.wire.WireFormat;
|
|
|
+
|
|
|
public class Remote implements Closeable
|
|
|
{
|
|
|
+ public static interface Pairing
|
|
|
+ {
|
|
|
+ public void prompt();
|
|
|
+ public void interrupted(InterruptedException exception);
|
|
|
+ }
|
|
|
+
|
|
|
private static final String STORE = "bigscreenbot.keystore";
|
|
|
private static final char[] PASSWORD = "b1GsSC33Nb0T".toCharArray();
|
|
|
private static final char[] NULL = new char[]{};
|
|
@@ -81,18 +98,52 @@ public class Remote implements Closeable
|
|
|
private static final String LOCAL_ALIAS = "anymote-remote";
|
|
|
private static final String REMOTE_ALIAS = "anymote-server-%1$X";
|
|
|
|
|
|
+ private static class Secret
|
|
|
+ {
|
|
|
+ private String secret;
|
|
|
+ private Thread thread;
|
|
|
+
|
|
|
+ public Secret()
|
|
|
+ {
|
|
|
+ thread = Thread.currentThread();
|
|
|
+ }
|
|
|
+
|
|
|
+ public synchronized void set(String secret)
|
|
|
+ {
|
|
|
+ this.secret = secret;
|
|
|
+
|
|
|
+ notify();
|
|
|
+ }
|
|
|
+
|
|
|
+ public synchronized String get() throws InterruptedException
|
|
|
+ {
|
|
|
+ while (secret == null)
|
|
|
+ wait();
|
|
|
+
|
|
|
+ return secret;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void interrupt()
|
|
|
+ {
|
|
|
+ thread.interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
static
|
|
|
{
|
|
|
new Fixer();
|
|
|
}
|
|
|
|
|
|
+ private boolean verbose;
|
|
|
private Settings settings;
|
|
|
private JmDNS mdns;
|
|
|
private KeyStore store;
|
|
|
private SSLSocketFactory factory;
|
|
|
+ private Map<InetSocketAddress, Secret> secrets = Collections.synchronizedMap(new HashMap<InetSocketAddress, Secret>());
|
|
|
|
|
|
- public Remote(Settings settings) throws UnknownHostException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, GeneralSecurityException
|
|
|
+ public Remote(boolean verbose, Settings settings) throws UnknownHostException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, GeneralSecurityException
|
|
|
{
|
|
|
+ this.verbose = verbose;
|
|
|
this.settings = settings;
|
|
|
|
|
|
String interfaze = settings.getProperty("interface");
|
|
@@ -156,34 +207,84 @@ public class Remote implements Closeable
|
|
|
return devices;
|
|
|
}
|
|
|
|
|
|
- public void startPairDevice(String device) throws MalformedURLException, UnknownHostException, IOException, PoloException
|
|
|
+ public boolean startPairDevice(String device, final Pairing pairing) throws MalformedURLException, UnknownHostException, IOException, PoloException
|
|
|
{
|
|
|
- ServiceInfo info = mdns.getServiceInfo(TYPE, device);
|
|
|
- InetAddress address;
|
|
|
- int port;
|
|
|
+ SSLSocket socket = (SSLSocket)factory.createSocket();
|
|
|
+ final InetSocketAddress address = getDevice(device);
|
|
|
|
|
|
- if (info != null)
|
|
|
- {
|
|
|
- address = info.getInetAddresses()[0];
|
|
|
- port = info.getPort();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- URL url = new URL("http://" + device);
|
|
|
+ socket.connect(address);
|
|
|
|
|
|
- address = InetAddress.getByName(url.getHost());
|
|
|
- port = url.getPort();
|
|
|
+ PairingContext context = PairingContext.fromSslSocket(socket, false);
|
|
|
+ ClientPairingSession session = new ClientPairingSession(WireFormat.PROTOCOL_BUFFERS.getWireInterface(context), context, "AnyMote", "Big Screen Bot");
|
|
|
+ EncodingOption option = new EncodingOption(EncodingOption.EncodingType.ENCODING_HEXADECIMAL, 4);
|
|
|
|
|
|
- if (port == -1)
|
|
|
- port = 9551;
|
|
|
- }
|
|
|
+ session.addInputEncoding(option);
|
|
|
+ session.addOutputEncoding(option);
|
|
|
|
|
|
- SSLSocket socket = (SSLSocket)factory.createSocket(address, port);
|
|
|
- PairingContext context = PairingContext.fromSslSocket(socket, false);
|
|
|
+ return session.doPair(new PairingListener()
|
|
|
+ {
|
|
|
+ @Override
|
|
|
+ public void onLogMessage(LogLevel level, String message)
|
|
|
+ {
|
|
|
+ if (verbose)
|
|
|
+ System.out.println(level + ": " + message);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPerformInputDeviceRole(PairingSession session)
|
|
|
+ {
|
|
|
+ pairing.prompt();
|
|
|
+
|
|
|
+ Secret secret = new Secret(), oldSecret = secrets.put(address, secret);
|
|
|
+
|
|
|
+ if (oldSecret != null)
|
|
|
+ oldSecret.interrupt();
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ session.setSecret(session.getEncoder().decodeToBytes(secret.get()));
|
|
|
+ }
|
|
|
+ catch (InterruptedException exception)
|
|
|
+ {
|
|
|
+ session.teardown();
|
|
|
+ pairing.interrupted(exception);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPerformOutputDeviceRole(PairingSession session, byte[] gamma) throws PoloException
|
|
|
+ {
|
|
|
+ throw new PoloException("This should not happen!");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onSessionCreated(PairingSession session)
|
|
|
+ {
|
|
|
+ if (verbose)
|
|
|
+ System.out.println(session + " created.");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onSessionEnded(PairingSession session)
|
|
|
+ {
|
|
|
+ if (verbose)
|
|
|
+ System.out.println(session + " ended.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // TODO: store server certificate
|
|
|
}
|
|
|
|
|
|
- public void finishPairDevice(String device, String code)
|
|
|
+ public boolean finishPairDevice(String device, String code) throws MalformedURLException
|
|
|
{
|
|
|
+ Secret secret = secrets.remove(getDevice(device));
|
|
|
+
|
|
|
+ if (secret != null)
|
|
|
+ secret.set(code);
|
|
|
+ else
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
public void fling(String url)
|
|
@@ -194,6 +295,21 @@ public class Remote implements Closeable
|
|
|
{
|
|
|
}
|
|
|
|
|
|
+ private InetSocketAddress getDevice(String device) throws MalformedURLException
|
|
|
+ {
|
|
|
+ ServiceInfo info = mdns.getServiceInfo(TYPE, device);
|
|
|
+
|
|
|
+ if (info != null)
|
|
|
+ return new InetSocketAddress(info.getInetAddresses()[0], info.getPort());
|
|
|
+ else
|
|
|
+ {
|
|
|
+ URL url = new URL("http://" + device);
|
|
|
+ int port = url.getPort();
|
|
|
+
|
|
|
+ return new InetSocketAddress(url.getHost(), port == -1 ? 9551 : port);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void store() throws FileNotFoundException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException
|
|
|
{
|
|
|
FileOutputStream stream = null;
|