/**
*
*/
package samples;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
/**
*
*/
public class ITunesRemotePairing implements Runnable, ServiceListener {
public final static String TOUCH_ABLE_TYPE = "_touch-able._tcp.local.";
public final static String DACP_TYPE = "_dacp._tcp.local.";
public final static String REMOTE_TYPE = "_touch-remote._tcp.local.";
public volatile static boolean _running = true;
protected final Random random = new Random();
public static byte[] PAIRING_RAW = new byte[] { 0x63, 0x6d, 0x70, 0x61, 0x00, 0x00, 0x00, 0x3a, 0x63, 0x6d, 0x70, 0x67, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x63, 0x6d, 0x6e, 0x6d, 0x00, 0x00,
0x00, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, (byte) 0xe2, (byte) 0x80, (byte) 0x99, 0x73, 0x20, 0x69, 0x50, 0x6f, 0x64, 0x63, 0x6d, 0x74, 0x79, 0x00, 0x00, 0x00, 0x04, 0x69, 0x50, 0x6f, 0x64 };
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// Activate these lines to see log messages of JmDNS
boolean log = false;
if (log) {
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.FINEST);
for (Enumeration<String> enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) {
String loggerName = enumerator.nextElement();
Logger logger = Logger.getLogger(loggerName);
logger.addHandler(handler);
logger.setLevel(Level.FINEST);
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new ITunesRemotePairing());
executor.shutdown();
}
/**
*
*/
public ITunesRemotePairing() {
super();
}
@Override
public void run() {
try {
final JmDNS jmdns = JmDNS.create();
jmdns.addServiceListener(TOUCH_ABLE_TYPE, this);
jmdns.addServiceListener(DACP_TYPE, this);
final HashMap<String, String> values = new HashMap<String, String>();
byte[] number = new byte[4];
random.nextBytes(number);
values.put("DvNm", "Android-" + toHex(number));
values.put("RemV", "10000");
values.put("DvTy", "iPod");
values.put("RemN", "Remote");
values.put("txtvers", "1");
byte[] pair = new byte[8];
random.nextBytes(pair);
values.put("Pair", toHex(pair));
while (_running) {
ServerSocket server = new ServerSocket(0);
byte[] name = new byte[20];
random.nextBytes(name);
System.out.println("Requesting pairing for " + toHex(name));
ServiceInfo pairservice = ServiceInfo.create(REMOTE_TYPE, toHex(name), server.getLocalPort(), 0, 0, values);
jmdns.registerService(pairservice);
System.out.println("Waiting for pass code");
final Socket socket = server.accept();
OutputStream output = null;
try {
output = socket.getOutputStream();
// output the contents for debugging
final BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (br.ready()) {
String line = br.readLine();
System.out.println(line);
}
// edit our local PAIRING_RAW to return the correct guid
byte[] code = new byte[8];
random.nextBytes(code);
System.out.println("Device guid: " + toHex(code));
System.arraycopy(code, 0, PAIRING_RAW, 16, 8);
byte[] header = String.format("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n", new Integer(PAIRING_RAW.length)).getBytes();
byte[] reply = new byte[header.length + PAIRING_RAW.length];
System.arraycopy(header, 0, reply, 0, header.length);
System.arraycopy(PAIRING_RAW, 0, reply, header.length, PAIRING_RAW.length);
System.out.println("Response: " + new String(reply));
output.write(reply);
output.flush();
System.out.println("someone paired with me!");
jmdns.unregisterService(pairservice);
} finally {
if (output != null) {
output.close();
}
System.out.println("Closing Socket");
if (!server.isClosed()) {
server.close();
}
_running = false;
}
}
Thread.sleep(6000);
System.out.println("Closing JmDNS");
jmdns.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void serviceAdded(ServiceEvent event) {
System.out.println("Service added : " + event.getName() + "." + event.getType());
}
@Override
public void serviceRemoved(ServiceEvent event) {
System.out.println("Service removed : " + event.getName() + "." + event.getType());
}
@Override
public void serviceResolved(ServiceEvent event) {
System.out.println("Service resolved: " + event.getName() + "." + event.getType() + "\n" + event.getInfo());
}
private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
private static String toHex(byte[] code) {
StringBuilder result = new StringBuilder(2 * code.length);
for (int i = 0; i < code.length; i++) {
int b = code[i] & 0xFF;
result.append(_nibbleToHex[b / 16]);
result.append(_nibbleToHex[b % 16]);
}
return result.toString();
}
}