├── src ├── main │ └── java │ │ └── com │ │ └── mogaleaf │ │ ├── usbmuxd │ │ ├── api │ │ │ ├── model │ │ │ │ ├── Device.java │ │ │ │ ├── DeviceConnectionMessage.java │ │ │ │ └── UsbMuxdConnection.java │ │ │ ├── exception │ │ │ │ ├── UsbMuxdConnectException.java │ │ │ │ └── UsbMuxdException.java │ │ │ ├── UsbMuxdFactory.java │ │ │ └── IUsbMuxd.java │ │ └── protocol │ │ │ ├── ConnectedMessage.java │ │ │ ├── win │ │ │ └── UsbMuxdWindows.java │ │ │ ├── linux │ │ │ └── UsbMuxdLinux.java │ │ │ ├── DeviceConnecter.java │ │ │ ├── DeviceListener.java │ │ │ ├── PlistMessageService.java │ │ │ └── UsbMuxdImpl.java │ │ └── Main.java └── test │ └── java │ └── com │ └── mogaleaf │ └── usbmuxd │ └── protocol │ ├── ConnectedMessageTest.java │ ├── DeviceConnecterTest.java │ ├── DeviceListenerTest.java │ └── UsbMuxdImplTest.java ├── README.md ├── usbmuxd.iml └── pom.xml /src/main/java/com/mogaleaf/usbmuxd/api/model/Device.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api.model; 2 | 3 | /* 4 | * Device Information 5 | */ 6 | public class Device { 7 | public String serialNumber; 8 | public String productId; 9 | public String locationId; 10 | public Integer deviceId; 11 | public String connectionType; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/model/DeviceConnectionMessage.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api.model; 2 | 3 | /* 4 | * Connection or Disconnection message for a Device. 5 | */ 6 | public class DeviceConnectionMessage { 7 | public Device device; 8 | public Type type; 9 | 10 | public enum Type { 11 | Add, Remove 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/exception/UsbMuxdConnectException.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api.exception; 2 | 3 | /* 4 | * Exception happening when attempting to connect a Device. 5 | */ 6 | public class UsbMuxdConnectException extends UsbMuxdException { 7 | public UsbMuxdConnectException(String s) { 8 | super(s); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/exception/UsbMuxdException.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api.exception; 2 | 3 | /* 4 | * Global Exception happening on UsbMuxd. 5 | */ 6 | public class UsbMuxdException extends Exception { 7 | public UsbMuxdException(String s) { 8 | super(s); 9 | } 10 | public UsbMuxdException(Exception e) { 11 | super(e); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/model/UsbMuxdConnection.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api.model; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | 6 | /* 7 | * I/O of the established connection and device information. 8 | */ 9 | public class UsbMuxdConnection { 10 | public InputStream inputStream; 11 | public OutputStream outputStream; 12 | public Device device; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/UsbMuxdFactory.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api; 2 | 3 | import com.mogaleaf.usbmuxd.protocol.linux.UsbMuxdLinux; 4 | import com.mogaleaf.usbmuxd.protocol.win.UsbMuxdWindows; 5 | 6 | public class UsbMuxdFactory { 7 | 8 | public static IUsbMuxd getInstance() { 9 | String property = System.getProperty("os.name"); 10 | if (property.startsWith("Window")) { 11 | return new UsbMuxdWindows(); 12 | } else { 13 | return new UsbMuxdLinux(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/ConnectedMessage.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdConnectException; 4 | 5 | public class ConnectedMessage { 6 | public int result; 7 | 8 | public void throwException() throws UsbMuxdConnectException { 9 | switch(result){ 10 | case 2: 11 | throw new UsbMuxdConnectException("Device not connected"); 12 | case 3: 13 | throw new UsbMuxdConnectException("Connection refused"); 14 | case 0: 15 | return; 16 | default: 17 | throw new UsbMuxdConnectException("Not connected for unknown reason"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/win/UsbMuxdWindows.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol.win; 2 | 3 | import com.mogaleaf.usbmuxd.protocol.UsbMuxdImpl; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.net.Socket; 8 | import java.net.SocketAddress; 9 | 10 | 11 | public class UsbMuxdWindows extends UsbMuxdImpl { 12 | 13 | private static int DEFAULT_PORT_NUMBER = 27015; 14 | 15 | 16 | @Override 17 | public SocketAddress getAddress() { 18 | return new InetSocketAddress("127.0.0.1", DEFAULT_PORT_NUMBER); 19 | } 20 | 21 | @Override 22 | protected Socket getSocketImpl() throws IOException { 23 | return new Socket(); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/linux/UsbMuxdLinux.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol.linux; 2 | 3 | import com.mogaleaf.usbmuxd.protocol.UsbMuxdImpl; 4 | import org.newsclub.net.unix.AFUNIXSocket; 5 | import org.newsclub.net.unix.AFUNIXSocketAddress; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.net.Socket; 10 | import java.net.SocketAddress; 11 | 12 | public class UsbMuxdLinux extends UsbMuxdImpl { 13 | @Override 14 | protected SocketAddress getAddress() throws IOException { 15 | return new AFUNIXSocketAddress(new File("/var/run/usbmuxd")); 16 | } 17 | 18 | @Override 19 | protected Socket getSocketImpl() throws IOException { 20 | return AFUNIXSocket.newInstance(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Detect and communicate with your iphone/ipad. 2 | 3 | - API to use usbmuxd in java. 4 | - If using windows, itunes must have been installed in order to have usbmuxd drivers. 5 | 6 | ### Easy to use : 7 | 8 | **Connect to the first device :** 9 | 10 | ``` IUsbMuxd usbMuxdDriver = UsbMuxdFactory.getInstance(); 11 | UsbMuxdConnection usbMuxdConnection = usbMuxdDriver.connectToFirstDevice(62078); 12 | usbMuxdConnection.outputStream.write(/*anyByte*/0); 13 | ``` 14 | 15 | **Listen to devices connection :** 16 | 17 | ``` 18 | usbMuxdDriver.registerDeviceConnectionListener(m->{ 19 | switch (m.type){ 20 | case Add: 21 | System.out.println("add " + m.device.deviceId); 22 | break; 23 | case Remove: 24 | System.out.println("remove " + m.device.deviceId); 25 | break; 26 | } 27 | }); 28 | usbMuxdDriver.startListening(); 29 | ``` 30 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/DeviceConnecter.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.dd.plist.NSDictionary; 4 | import com.dd.plist.NSNumber; 5 | 6 | import java.io.InputStream; 7 | 8 | public class DeviceConnecter { 9 | 10 | 11 | public ConnectedMessage getConnectionResult(InputStream inputStream) { 12 | try { 13 | int size = PlistMessageService.getSize(inputStream); 14 | if (size > 0) { 15 | NSDictionary dico = PlistMessageService.getNsDictionary(inputStream, size); 16 | PlistMessageService.ResultType messageTypeEnum = PlistMessageService.retrieveMsgType(dico); 17 | switch (messageTypeEnum) { 18 | case Result: 19 | NSNumber statusS = (NSNumber) dico.get("Number"); 20 | int status = statusS.intValue(); 21 | ConnectedMessage connectedMessage = new ConnectedMessage(); 22 | connectedMessage.result = status; 23 | return connectedMessage; 24 | } 25 | } 26 | } catch (Exception e) { 27 | e.printStackTrace(); 28 | } 29 | return getConnectionResult(inputStream); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/mogaleaf/usbmuxd/protocol/ConnectedMessageTest.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdConnectException; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static org.junit.Assert.fail; 8 | 9 | public class ConnectedMessageTest { 10 | 11 | ConnectedMessage instance = new ConnectedMessage(); 12 | 13 | @Test 14 | public void testRes0(){ 15 | instance.result = 0; 16 | try { 17 | instance.throwException(); 18 | } catch (UsbMuxdConnectException e) { 19 | fail(); 20 | } 21 | } 22 | 23 | @Test 24 | public void testRes1(){ 25 | instance.result = 1; 26 | try { 27 | instance.throwException(); 28 | } catch (UsbMuxdConnectException e) { 29 | assertThat(e.getMessage()).isEqualTo("Not connected for unknown reason"); 30 | } 31 | } 32 | 33 | @Test 34 | public void testRes2(){ 35 | instance.result = 2; 36 | try { 37 | instance.throwException(); 38 | } catch (UsbMuxdConnectException e) { 39 | assertThat(e.getMessage()).isEqualTo("Device not connected"); 40 | } 41 | } 42 | 43 | @Test 44 | public void testRes3(){ 45 | instance.result = 3; 46 | try { 47 | instance.throwException(); 48 | } catch (UsbMuxdConnectException e) { 49 | assertThat(e.getMessage()).isEqualTo("Connection refused"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/mogaleaf/usbmuxd/protocol/DeviceConnecterTest.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.dd.plist.NSDictionary; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.mockito.Mock; 7 | import org.mockito.MockitoAnnotations; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.nio.ByteBuffer; 12 | import java.nio.charset.Charset; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | import static org.mockito.Matchers.any; 16 | import static org.mockito.Mockito.when; 17 | 18 | public class DeviceConnecterTest { 19 | 20 | DeviceConnecter instance; 21 | 22 | @Mock 23 | InputStream inputStream; 24 | 25 | @Before 26 | public void setup() throws IOException { 27 | MockitoAnnotations.initMocks(this); 28 | instance = new DeviceConnecter(); 29 | } 30 | 31 | @Test 32 | public void testgetConnectionResult() throws IOException { 33 | NSDictionary root = new NSDictionary(); 34 | root.put("MessageType", "Result"); 35 | root.put("Number", 0); 36 | String s = root.toXMLPropertyList(); 37 | byte[] bytes = s.getBytes(Charset.forName("UTF-8")); 38 | ByteBuffer msg = PlistMessageService.buildByteMsg(bytes); 39 | byte[] first = new byte[16]; 40 | byte[] seconde = new byte[bytes.length]; 41 | msg.get(first); 42 | msg.get(seconde); 43 | when(inputStream.read(any(byte[].class))).then(arg -> { 44 | byte[] argumentAt = arg.getArgumentAt(0, byte[].class); 45 | byte[] cpy = null; 46 | if(argumentAt.length == first.length){ 47 | cpy = first; 48 | } else if(argumentAt.length == seconde.length){ 49 | cpy = seconde; 50 | } 51 | if(cpy != null){ 52 | int i = 0; 53 | for (byte b : cpy) { 54 | argumentAt[i++] = b; 55 | } 56 | } 57 | return 1; 58 | }); 59 | 60 | ConnectedMessage connectionResult = instance.getConnectionResult(inputStream); 61 | assertThat(connectionResult.result).isEqualTo(0); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /usbmuxd.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/api/IUsbMuxd.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.api; 2 | 3 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdException; 4 | import com.mogaleaf.usbmuxd.api.model.Device; 5 | import com.mogaleaf.usbmuxd.api.model.DeviceConnectionMessage; 6 | import com.mogaleaf.usbmuxd.api.model.UsbMuxdConnection; 7 | 8 | import java.util.Collection; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.function.Consumer; 11 | 12 | /* 13 | * 14 | * Communication on Usbmuxd. 15 | * 16 | */ 17 | public interface IUsbMuxd { 18 | /** 19 | * Get a Copy of All connected Devices. 20 | * 21 | * @return A collection of devices connected via USB. 22 | */ 23 | Collection connectedDevices(); 24 | 25 | /** 26 | * Connect to the first find device, stop if too long. 27 | * 28 | * @return I/O and Device . 29 | */ 30 | UsbMuxdConnection connectToFirstDevice(int port, long time, TimeUnit timeUnit) throws UsbMuxdException; 31 | 32 | /** 33 | * Connect to the first find device. 34 | * 35 | * @return I/O and Device . 36 | */ 37 | UsbMuxdConnection connectToFirstDevice(int port) throws UsbMuxdException; 38 | 39 | /** 40 | * Connect to a device. 41 | * 42 | * @return I/O and Device . 43 | */ 44 | UsbMuxdConnection connectToDevice(int port, Device device) throws UsbMuxdException; 45 | 46 | /** 47 | * Try to Connect to a device before the timeout 48 | * 49 | * @return I/O and Device . 50 | */ 51 | UsbMuxdConnection connectToDevice(int port, Device device, long time, TimeUnit timeUnit) throws UsbMuxdException; 52 | 53 | /** 54 | * start listening on incoming device connection/disconnection. 55 | */ 56 | void startListening() throws UsbMuxdException; 57 | 58 | /** 59 | * stop listening on incoming device connection/disconnection 60 | */ 61 | void stopListening() throws UsbMuxdException; 62 | 63 | /** 64 | * Register a new DeviceConnection consumer 65 | * 66 | * @return unique id on the listner 67 | */ 68 | String registerDeviceConnectionListener(Consumer consumer); 69 | 70 | /** 71 | * Unegister a new DeviceConnection consumer 72 | * 73 | */ 74 | void unRegisterDeviceConnectionListener(String uid); 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/mogaleaf/usbmuxd/protocol/DeviceListenerTest.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.dd.plist.NSDictionary; 4 | import com.mogaleaf.usbmuxd.api.model.Device; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.nio.ByteBuffer; 13 | import java.nio.charset.Charset; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.mockito.Matchers.any; 17 | import static org.mockito.Mockito.when; 18 | 19 | public class DeviceListenerTest { 20 | 21 | DeviceListener instance; 22 | 23 | @Mock 24 | InputStream inputStream; 25 | 26 | 27 | @Before 28 | public void setup() throws IOException { 29 | MockitoAnnotations.initMocks(this); 30 | instance = new DeviceListener(); 31 | } 32 | 33 | 34 | @Test 35 | public void testAttached() throws IOException, InterruptedException { 36 | 37 | NSDictionary root = new NSDictionary(); 38 | root.put("MessageType", "Attached"); 39 | NSDictionary props = new NSDictionary(); 40 | root.put("Properties", props); 41 | props.put("SerialNumber", "123"); 42 | props.put("ConnectionType", "usb"); 43 | props.put("DeviceID", "1"); 44 | props.put("LocationID", "lo"); 45 | props.put("ProductID", "po"); 46 | String s = root.toXMLPropertyList(); 47 | byte[] bytes = s.getBytes(Charset.forName("UTF-8")); 48 | ByteBuffer msg = PlistMessageService.buildByteMsg(bytes); 49 | byte[] first = new byte[16]; 50 | byte[] seconde = new byte[bytes.length]; 51 | msg.get(first); 52 | msg.get(seconde); 53 | 54 | when(inputStream.read(any(byte[].class))).then(arg -> { 55 | byte[] argumentAt = arg.getArgumentAt(0, byte[].class); 56 | byte[] cpy = null; 57 | if(argumentAt.length == first.length){ 58 | cpy = first; 59 | } else if(argumentAt.length == seconde.length){ 60 | cpy = seconde; 61 | } 62 | if(cpy != null){ 63 | int i = 0; 64 | for (byte b : cpy) { 65 | argumentAt[i++] = b; 66 | } 67 | } 68 | return 1; 69 | }); 70 | instance.start(inputStream); 71 | instance.register(d -> { 72 | Device device = d.device; 73 | assertThat(device.deviceId).isEqualTo(1); 74 | assertThat(device.connectionType).isEqualTo("usb"); 75 | assertThat(device.serialNumber).isEqualTo("123"); 76 | assertThat(device.locationId).isEqualTo("lo"); 77 | assertThat(device.productId).isEqualTo("po"); 78 | instance.stop(); 79 | }); 80 | instance.run(); 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/Main.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf; 2 | 3 | import com.dd.plist.NSObject; 4 | import com.dd.plist.PropertyListParser; 5 | import com.mogaleaf.usbmuxd.api.IUsbMuxd; 6 | import com.mogaleaf.usbmuxd.api.UsbMuxdFactory; 7 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdException; 8 | import com.mogaleaf.usbmuxd.api.model.UsbMuxdConnection; 9 | import com.mogaleaf.usbmuxd.protocol.PlistMessageService; 10 | 11 | import java.io.IOException; 12 | import java.nio.ByteBuffer; 13 | import java.nio.ByteOrder; 14 | 15 | 16 | public class Main { 17 | static IUsbMuxd usbMuxdDriver = UsbMuxdFactory.getInstance(); 18 | 19 | public static void main(String args[]) throws IOException, InterruptedException { 20 | try { 21 | 22 | usbMuxdDriver.registerDeviceConnectionListener(m -> { 23 | switch (m.type) { 24 | case Add: 25 | System.out.println("add " + m.device.deviceId); 26 | break; 27 | case Remove: 28 | System.out.println("remove " + m.device.deviceId); 29 | break; 30 | } 31 | }); 32 | 33 | UsbMuxdConnection usbMuxdConnection = usbMuxdDriver.connectToFirstDevice(62078); 34 | 35 | new Thread(() -> { 36 | byte[] res = new byte[4]; 37 | try { 38 | 39 | usbMuxdConnection.inputStream.read(res); 40 | ByteBuffer readB = ByteBuffer.allocate(res.length); 41 | readB.order(ByteOrder.BIG_ENDIAN); 42 | readB.put(res); 43 | int aInt = readB.getInt(0); 44 | byte[] body = new byte[aInt]; 45 | usbMuxdConnection.inputStream.read(body); 46 | NSObject parse = PropertyListParser.parse(body); 47 | System.out.println(parse.toXMLPropertyList()); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | 53 | }).start(); 54 | 55 | byte[] bytes = PlistMessageService.tryLockDown(); 56 | 57 | ByteBuffer buffer = ByteBuffer.allocate( 58 | 4); 59 | buffer.order(ByteOrder.BIG_ENDIAN); 60 | buffer.putInt(bytes.length); 61 | usbMuxdConnection.outputStream.write(buffer.array()); 62 | 63 | 64 | buffer = ByteBuffer.allocate( 65 | bytes.length); 66 | buffer.order(ByteOrder.BIG_ENDIAN); 67 | buffer.put(bytes); 68 | usbMuxdConnection.outputStream.write(bytes); 69 | 70 | 71 | //Thread.sleep(10000);wri 72 | //usbMuxdDriver.stopListening(); 73 | /* 74 | UsbMuxdConnection usbMuxdConnection = usbMuxdDriver.connectToFirstDevice(62078); 75 | System.out.println("connected to Device [" + usbMuxdConnection.device.deviceId + "/" + usbMuxdConnection.device.serialNumber + "]"); 76 | Collection devices = usbMuxdDriver.connectedDevices(); 77 | devices.stream().forEach(d-> System.out.println(d.serialNumber));*/ 78 | } catch (UsbMuxdException e) { 79 | e.printStackTrace(); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/DeviceListener.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.dd.plist.NSDictionary; 4 | import com.mogaleaf.usbmuxd.api.model.Device; 5 | import com.mogaleaf.usbmuxd.api.model.DeviceConnectionMessage; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | import java.util.function.Consumer; 14 | 15 | public class DeviceListener implements Runnable { 16 | 17 | 18 | private InputStream inputStream; 19 | 20 | private boolean running = false; 21 | 22 | private final Map> consumers = Collections.synchronizedMap(new HashMap<>()); 23 | 24 | 25 | public String register(Consumer deviceConnectionListener) { 26 | String uid = UUID.randomUUID().toString(); 27 | consumers.put(uid, deviceConnectionListener); 28 | return uid; 29 | } 30 | 31 | public void unregister(String uid) { 32 | consumers.remove(uid); 33 | } 34 | 35 | public void start(InputStream inputStream) { 36 | this.inputStream = inputStream; 37 | running = true; 38 | } 39 | 40 | 41 | @Override 42 | public void run() { 43 | while (running) { 44 | try { 45 | int size = PlistMessageService.getSize(inputStream); 46 | if (size > 0) { 47 | NSDictionary dico = PlistMessageService.getNsDictionary(inputStream, size); 48 | PlistMessageService.ResultType messageTypeEnum = PlistMessageService.retrieveMsgType(dico); 49 | DeviceConnectionMessage deviceConnectionMessage = new DeviceConnectionMessage(); 50 | switch (messageTypeEnum) { 51 | case Attached: 52 | Device deviceAttachMessage = buildDevice(dico); 53 | deviceConnectionMessage.device = deviceAttachMessage; 54 | deviceConnectionMessage.type = DeviceConnectionMessage.Type.Add; 55 | notify(deviceConnectionMessage); 56 | break; 57 | case Detached: 58 | Device deviceDetachMessage = new Device(); 59 | deviceDetachMessage.deviceId = Integer.valueOf(dico.get("DeviceID").toString()); 60 | deviceConnectionMessage.device = deviceDetachMessage; 61 | deviceConnectionMessage.type = DeviceConnectionMessage.Type.Remove; 62 | notify(deviceConnectionMessage); 63 | } 64 | } 65 | } catch (Exception e) { 66 | stop(); 67 | e.printStackTrace(); 68 | } 69 | } 70 | } 71 | 72 | private void notify(DeviceConnectionMessage deviceMsg) { 73 | synchronized (consumers) { 74 | consumers.values().forEach(c -> c.accept(deviceMsg)); 75 | } 76 | } 77 | 78 | 79 | private Device buildDevice(NSDictionary dico) { 80 | Device deviceAttachMessage = new Device(); 81 | NSDictionary properties = (NSDictionary) dico.get("Properties"); 82 | if (properties != null) { 83 | deviceAttachMessage.serialNumber = properties.get("SerialNumber").toString(); 84 | deviceAttachMessage.connectionType = properties.get("ConnectionType").toString(); 85 | deviceAttachMessage.deviceId = Integer.valueOf(properties.get("DeviceID").toString()); 86 | deviceAttachMessage.locationId = properties.get("LocationID").toString(); 87 | deviceAttachMessage.productId = properties.get("ProductID").toString(); 88 | } 89 | return deviceAttachMessage; 90 | } 91 | 92 | 93 | public void stop() { 94 | if (running) { 95 | try { 96 | inputStream.close(); 97 | } catch (IOException e) { 98 | inputStream = null; 99 | } 100 | } 101 | running = false; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/PlistMessageService.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.dd.plist.NSDictionary; 4 | import com.dd.plist.NSNumber; 5 | import com.dd.plist.NSObject; 6 | import com.dd.plist.NSString; 7 | import com.dd.plist.PropertyListFormatException; 8 | import com.dd.plist.PropertyListParser; 9 | import org.xml.sax.SAXException; 10 | 11 | import javax.xml.parsers.ParserConfigurationException; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.nio.ByteBuffer; 15 | import java.nio.ByteOrder; 16 | import java.nio.charset.Charset; 17 | import java.text.ParseException; 18 | 19 | public class PlistMessageService { 20 | 21 | public static byte[] buildListenConnectionMsg() { 22 | byte[] bytes = buildPlistConnectionMsg(); 23 | return buildByteMsg(bytes).array(); 24 | } 25 | 26 | public static byte[] buildConnectMsg(int deviceId,int port) { 27 | byte[] bytes = buildDeviceConnectMsg(deviceId,port); 28 | return buildByteMsg(bytes).array(); 29 | } 30 | 31 | 32 | protected static byte[] buildDeviceConnectMsg(int deviceId, int port) { 33 | NSDictionary root = new NSDictionary(); 34 | root.put("MessageType", "Connect"); 35 | root.put("ClientVersionString", "mogaleaf-usbmux-driver"); 36 | root.put("ProgName", "mogaleaf-usbmux-driver"); 37 | root.put("DeviceID", new NSNumber(deviceId)); 38 | root.put("PortNumber", new NSNumber(swapPortNumber(port))); 39 | String s = root.toXMLPropertyList(); 40 | //System.out.println(s); 41 | return s.getBytes(Charset.forName("UTF-8")); 42 | } 43 | 44 | protected static int swapPortNumber(int port) { 45 | return ((port << 8) & 0xFF00) | (port >> 8); 46 | } 47 | 48 | protected static byte[] buildPlistConnectionMsg(){ 49 | NSDictionary root = new NSDictionary(); 50 | root.put("MessageType", "Listen"); 51 | root.put("ClientVersionString", "mogaleaf-usbmux-driver"); 52 | root.put("ProgName", "mogaleaf-usbmux-driver"); 53 | String s = root.toXMLPropertyList(); 54 | //System.out.println(s); 55 | return s.getBytes(Charset.forName("UTF-8")); 56 | } 57 | 58 | protected static ByteBuffer buildByteMsg(byte[] bytes) { 59 | int len = (16 + bytes.length); 60 | int version = 1; 61 | int request = 8; 62 | int tag = 1; 63 | ByteBuffer buffer = ByteBuffer.allocate(len); 64 | buffer.order(ByteOrder.LITTLE_ENDIAN); 65 | buffer.putInt(0, len); 66 | buffer.putInt(4, version); 67 | buffer.putInt(8, request); 68 | buffer.putInt(12, tag); 69 | int i = 16; 70 | for (byte aByte : bytes) { 71 | buffer.put(i++, aByte); 72 | } 73 | return buffer; 74 | } 75 | 76 | public static NSDictionary getNsDictionary(InputStream input, int size) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException { 77 | byte[] body = new byte[size]; 78 | input.read(body); 79 | NSObject parse = PropertyListParser.parse(body); 80 | return (NSDictionary) parse; 81 | } 82 | 83 | public static int getSize(InputStream input) throws IOException { 84 | byte[] header = new byte[16]; 85 | input.read(header); 86 | ByteBuffer buffer = ByteBuffer.allocate(16); 87 | buffer.order(ByteOrder.LITTLE_ENDIAN); 88 | buffer.put(header); 89 | return buffer.getInt(0) - 16; 90 | } 91 | 92 | 93 | public static ResultType retrieveMsgType(NSDictionary dico) { 94 | NSString messageType = (NSString) dico.get("MessageType"); 95 | return ResultType.valueOf(messageType.getContent()); 96 | } 97 | 98 | public enum ResultType { 99 | Attached, Detached, Paired, Result, Error 100 | } 101 | 102 | public static byte[] tryLockDown() { 103 | NSDictionary root = new NSDictionary(); 104 | root.put("Label", "iTunesHelper"); 105 | root.put("Request", "QueryType"); 106 | String s = root.toXMLPropertyList(); 107 | System.out.println(s); 108 | return s.getBytes(Charset.forName("UTF-8")); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/mogaleaf/usbmuxd/protocol/UsbMuxdImplTest.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdException; 4 | import com.mogaleaf.usbmuxd.api.model.Device; 5 | import com.mogaleaf.usbmuxd.api.model.DeviceConnectionMessage; 6 | import com.mogaleaf.usbmuxd.api.model.UsbMuxdConnection; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.ArgumentCaptor; 10 | import org.mockito.Mock; 11 | import org.mockito.MockitoAnnotations; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | import java.net.Socket; 17 | import java.net.SocketAddress; 18 | import java.util.Collection; 19 | import java.util.function.Consumer; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static org.assertj.core.api.Assertions.fail; 23 | import static org.mockito.Matchers.any; 24 | import static org.mockito.Matchers.anyObject; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | public class UsbMuxdImplTest { 29 | 30 | UsbMuxdImpl instance; 31 | 32 | @Mock 33 | SocketAddress mockSocketAddress; 34 | 35 | @Mock 36 | Socket mockSocket; 37 | 38 | @Mock 39 | DeviceListener deviceListener; 40 | 41 | @Mock 42 | DeviceConnecter deviceConnecter; 43 | 44 | @Mock 45 | OutputStream outputStream; 46 | 47 | @Mock 48 | InputStream inputStream; 49 | 50 | 51 | @Before 52 | public void setup() throws IOException { 53 | MockitoAnnotations.initMocks(this); 54 | when(mockSocket.getOutputStream()).thenReturn(outputStream); 55 | when(mockSocket.getInputStream()).thenReturn(inputStream); 56 | instance = new UsbMuxdImpl() { 57 | @Override 58 | protected SocketAddress getAddress() throws IOException { 59 | return mockSocketAddress; 60 | } 61 | 62 | @Override 63 | protected Socket getSocketImpl() throws IOException { 64 | return mockSocket; 65 | } 66 | }; 67 | instance.deviceListener = deviceListener; 68 | instance.deviceConnecter = deviceConnecter; 69 | } 70 | 71 | @Test 72 | public void testconnectToFirstDevice() throws UsbMuxdException, InterruptedException { 73 | ConnectedMessage connectedMessage = new ConnectedMessage(); 74 | connectedMessage.result = 0; 75 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Consumer.class); 76 | when(deviceListener.register(argumentCaptor.capture())).thenReturn("test"); 77 | when(deviceConnecter.getConnectionResult(anyObject())).thenReturn(connectedMessage); 78 | instance.isStarted = true; 79 | DeviceConnectionMessage fakeMessage = new DeviceConnectionMessage(); 80 | fakeMessage.type = DeviceConnectionMessage.Type.Add; 81 | fakeMessage.device = new Device(); 82 | fakeMessage.device.deviceId = 2; 83 | 84 | Thread producerThread = new Thread(() -> { 85 | int size = argumentCaptor.getAllValues().size(); 86 | while (size == 0) { 87 | size = argumentCaptor.getAllValues().size(); 88 | } 89 | argumentCaptor.getValue().accept(fakeMessage); 90 | }); 91 | producerThread.start(); 92 | 93 | Thread consumerThread = new Thread(() -> { 94 | try { 95 | UsbMuxdConnection usbMuxdConnection = instance.connectToFirstDevice(2044); 96 | verify(deviceListener).unregister("test"); 97 | assertThat(usbMuxdConnection.device).isSameAs(fakeMessage.device); 98 | 99 | } catch (UsbMuxdException e) { 100 | fail(e.getMessage()); 101 | } 102 | }); 103 | consumerThread.start(); 104 | 105 | producerThread.join(1000); 106 | consumerThread.join(1000); 107 | 108 | 109 | } 110 | 111 | @Test 112 | public void testStartListening() { 113 | try { 114 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Consumer.class); 115 | instance.startListening(); 116 | verify(deviceListener).register(argumentCaptor.capture()); 117 | verify(deviceListener).start(inputStream); 118 | verify(outputStream).write(any()); 119 | assertThat(instance.isStarted).isTrue(); 120 | 121 | } catch (UsbMuxdException e) { 122 | fail(e.getMessage()); 123 | } catch (IOException e) { 124 | fail(e.getMessage()); 125 | } 126 | } 127 | 128 | @Test 129 | public void stopListening() { 130 | try { 131 | when(deviceListener.register(any())).thenReturn("test"); 132 | instance.startListening(); 133 | instance.stopListening(); 134 | verify(deviceListener).unregister("test"); 135 | verify(deviceListener).stop(); 136 | assertThat(instance.isStarted).isFalse(); 137 | } catch (UsbMuxdException e) { 138 | fail(e.getMessage()); 139 | } 140 | } 141 | 142 | @Test 143 | public void testConnectedDevices(){ 144 | Collection devices = instance.connectedDevices(); 145 | assertThat(devices).isNotSameAs(instance.connectedDevices()); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/mogaleaf/usbmuxd/protocol/UsbMuxdImpl.java: -------------------------------------------------------------------------------- 1 | package com.mogaleaf.usbmuxd.protocol; 2 | 3 | import com.mogaleaf.usbmuxd.api.IUsbMuxd; 4 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdConnectException; 5 | import com.mogaleaf.usbmuxd.api.exception.UsbMuxdException; 6 | import com.mogaleaf.usbmuxd.api.model.Device; 7 | import com.mogaleaf.usbmuxd.api.model.DeviceConnectionMessage; 8 | import com.mogaleaf.usbmuxd.api.model.UsbMuxdConnection; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.Socket; 13 | import java.net.SocketAddress; 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | import java.util.LinkedHashMap; 18 | import java.util.Map; 19 | import java.util.concurrent.CountDownLatch; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.Future; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.function.Consumer; 25 | 26 | public abstract class UsbMuxdImpl implements IUsbMuxd { 27 | 28 | private ExecutorService executorService = Executors.newFixedThreadPool(5); 29 | protected DeviceListener deviceListener = new DeviceListener(); 30 | protected DeviceConnecter deviceConnecter = new DeviceConnecter(); 31 | private Map connectedDevices = Collections.synchronizedMap(new LinkedHashMap<>()); 32 | protected boolean isStarted = false; 33 | private Socket connectionListeningSocket; 34 | private String registerUid; 35 | 36 | protected abstract SocketAddress getAddress() throws IOException; 37 | protected abstract Socket getSocketImpl() throws IOException; 38 | 39 | @Override 40 | public Collection connectedDevices() { 41 | ArrayList copy = new ArrayList<>(); 42 | copy.addAll(connectedDevices.values()); 43 | return copy; 44 | } 45 | 46 | @Override 47 | public UsbMuxdConnection connectToFirstDevice(int port, long time, TimeUnit timeUnit) throws UsbMuxdException { 48 | try { 49 | if (isStarted && !connectedDevices.isEmpty()) { 50 | return connectToDevice(port, connectedDevices.values().iterator().next()); 51 | } 52 | CountDownLatch countDownLatch = new CountDownLatch(1); 53 | Device[] singletonDevice = new Device[1]; 54 | String register = deviceListener.register(m -> { 55 | singletonDevice[0] = m.device; 56 | countDownLatch.countDown(); 57 | }); 58 | if (!isStarted) { 59 | startListening(); 60 | } 61 | if (time > 0) { 62 | countDownLatch.await(time, timeUnit); 63 | } else { 64 | countDownLatch.await(); 65 | } 66 | deviceListener.unregister(register); 67 | return connectToDevice(port, singletonDevice[0]); 68 | } catch (UsbMuxdConnectException e) { 69 | throw e; 70 | } catch (Exception e) { 71 | throw new UsbMuxdException(e); 72 | } 73 | } 74 | 75 | @Override 76 | public UsbMuxdConnection connectToFirstDevice(int port) throws UsbMuxdException { 77 | return connectToFirstDevice(port, -1, null); 78 | } 79 | 80 | @Override 81 | public UsbMuxdConnection connectToDevice(int port, Device device) throws UsbMuxdException { 82 | return connectToDevice(port, device, -1, null); 83 | } 84 | 85 | @Override 86 | public UsbMuxdConnection connectToDevice(int port, Device device, long time, TimeUnit timeUnit) throws UsbMuxdException { 87 | try { 88 | Socket connectionSocket = getSocketImpl(); 89 | connectionSocket.connect(getAddress()); 90 | byte[] connectByteMessage = PlistMessageService.buildConnectMsg(device.deviceId, port); 91 | InputStream inputStream = connectionSocket.getInputStream(); 92 | Future submit = executorService.submit(() -> deviceConnecter.getConnectionResult(inputStream)); 93 | connectionSocket.getOutputStream().write(connectByteMessage); 94 | ConnectedMessage connectedMessage; 95 | if (time > 0) { 96 | connectedMessage = submit.get(time, timeUnit); 97 | } else { 98 | connectedMessage = submit.get(); 99 | } 100 | if (connectedMessage.result != 0) { 101 | connectedMessage.throwException(); 102 | } 103 | UsbMuxdConnection usbMuxdConnection = new UsbMuxdConnection(); 104 | usbMuxdConnection.inputStream = inputStream; 105 | usbMuxdConnection.outputStream = connectionSocket.getOutputStream(); 106 | usbMuxdConnection.device = device; 107 | return usbMuxdConnection; 108 | } catch (UsbMuxdConnectException e) { 109 | throw e; 110 | } catch (Exception e) { 111 | throw new UsbMuxdException(e); 112 | } 113 | } 114 | 115 | @Override 116 | public void startListening() throws UsbMuxdException { 117 | try { 118 | connectionListeningSocket = getSocketImpl(); 119 | connectionListeningSocket.connect(getAddress()); 120 | byte[] connectByteMessage = PlistMessageService.buildListenConnectionMsg(); 121 | InputStream inputStream = connectionListeningSocket.getInputStream(); 122 | deviceListener.start(inputStream); 123 | isStarted = true; 124 | registerUid = deviceListener.register(m -> { 125 | switch (m.type) { 126 | case Add: 127 | connectedDevices.put(m.device.deviceId, m.device); 128 | break; 129 | case Remove: 130 | connectedDevices.remove(m.device.deviceId); 131 | break; 132 | } 133 | }); 134 | connectionListeningSocket.getOutputStream().write(connectByteMessage); 135 | executorService.execute(deviceListener); 136 | } catch (Exception e) { 137 | throw new UsbMuxdException(e); 138 | } 139 | } 140 | 141 | @Override 142 | public void stopListening() throws UsbMuxdException { 143 | if (isStarted) { 144 | deviceListener.stop(); 145 | deviceListener.unregister(registerUid); 146 | try { 147 | connectionListeningSocket.close(); 148 | } catch (IOException e) { 149 | connectionListeningSocket = null; 150 | } 151 | } 152 | isStarted = false; 153 | } 154 | 155 | @Override 156 | public String registerDeviceConnectionListener(Consumer consumer) { 157 | return deviceListener.register(consumer); 158 | } 159 | 160 | @Override 161 | public void unRegisterDeviceConnectionListener(String uid) { 162 | deviceListener.unregister(uid); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | 7 | org.apache.maven.plugins 8 | maven-compiler-plugin 9 | 2.3.2 10 | 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | org.sonatype.plugins 17 | nexus-staging-maven-plugin 18 | 1.6.7 19 | true 20 | 21 | ossrh 22 | https://oss.sonatype.org/ 23 | true 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-source-plugin 30 | 2.2.1 31 | 32 | 33 | attach-sources 34 | 35 | jar-no-fork 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-javadoc-plugin 43 | 3.0.0-M1 44 | 45 | 46 | attach-javadocs 47 | 48 | jar 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-gpg-plugin 56 | 1.5 57 | 58 | 59 | sign-artifacts 60 | verify 61 | 62 | sign 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-release-plugin 70 | 2.5.3 71 | 72 | true 73 | false 74 | release 75 | deploy 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ossrh 84 | https://oss.sonatype.org/content/repositories/snapshots 85 | 86 | 87 | ossrh 88 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 89 | 90 | 91 | 92 | 93 | 1.8 94 | UTF-8 95 | 96 | 97 | com.mogaleaf 98 | usbmuxd 99 | 1.1-SNAPSHOT 100 | 101 | usbmuxd 102 | Lib to communicate via usbmuxd to an iphone/ipad 103 | https://github.com/mogaleaf/java-usbmuxd 104 | 105 | 106 | 107 | The Apache License, Version 2.0 108 | http://www.apache.org/licenses/LICENSE-2.0.txt 109 | 110 | 111 | 112 | 113 | 114 | cecile thiebaut 115 | cecile.gomes@mogaleaf.com 116 | mogaleaf 117 | http://www.mogaleaf.com 118 | 119 | 120 | 121 | https://github.com/mogaleaf/java-usbmuxd 122 | scm:git:https://github.com/mogaleaf/java-usbmuxd.git 123 | https://github.com/mogaleaf/java-usbmuxd/tree/master 124 | HEAD 125 | 126 | 127 | 128 | 129 | 130 | com.googlecode.plist 131 | dd-plist 132 | 1.19 133 | 134 | 135 | junit 136 | junit 137 | 4.12 138 | test 139 | 140 | 141 | 142 | org.mockito 143 | mockito-all 144 | 2.0.2-beta 145 | test 146 | 147 | 148 | 149 | org.assertj 150 | assertj-core-java8 151 | 1.0.0m1 152 | test 153 | 154 | 155 | 156 | 157 | com.kohlschutter.junixsocket 158 | junixsocket-common 159 | 2.0.4 160 | 161 | 162 | 163 | 164 | com.kohlschutter.junixsocket 165 | junixsocket-native-common 166 | 2.0.4 167 | 168 | 169 | 175 | 176 | 177 | 178 | 179 | --------------------------------------------------------------------------------