├── 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 |
--------------------------------------------------------------------------------