peertalk & Usbmux 资料收集与整理

Usbmux - The iPhone Wiki

Usbmux

During normal operations, iTunes communicates with the iPhone using something called “usbmux” – this is a system for multiplexing several “connections” over one USB pipe. Conceptually, it provides a TCP-like system – processes on the host machine open up connections to specific, numbered ports on the mobile device. (This resemblance is more than superficial – on the mobile device, usbmuxd actually makes TCP connections to localhost using the port number you give it.)

On the Mac, this is handled by /System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd, a daemon that is started by launchd (see /System/Library/LaunchDaemons/ com.apple.usbmuxd.plist). It creates a listening UNIX Domain Socket at /var/run/usbmuxd. usbmuxd then watches for iPhone connections via USB; when it detects an iPhone running in normal mode (as opposed to recovery mode), it will connect to it and then start relaying requests that it receives via /var/run/usbmuxd – this is to say, usbmuxd is the only thing that actually speaks USB to the iPhone. This means that third-party applications which wish to talk to the iPhone must either do so through usbmuxd, or usbmuxd must be replaced

对 usbmuxd 的一点研究 · Farlanki

USBMuxd

当 iTunes 和 iPhone 连接时,它们之间会通过 USBMux 进行连接。USBMux 用于在 USB 协议上实现多路 TCP 连接。USBMuxd 能够实现 USB-TCP 协议的转换,将 USB 的端口映射到本机的 TCP 端(基于 Unix Domain Socket),将 USB 通信抽象为 TCP 通信。苹果的 iTunes , XCode 都使用了这个服务。

根据 iphonewiki.com 所说的,USBMuxd 程序存放在 Mac 系统下的 /System/Library/PrivateFrameworks/MobileDevice.framework/Resources路径中。并且由 plist 文件 /System/Library/LaunchDaemons/com.apple.usbmuxd.plist 可知,USBMuxd 是一个开机启动的服务。

peertalk & Usbmux 资料收集与整理_第1张图片

由于 iOS 系统也具有 USBMuxd 服务,所以 iPhone 和 Mac 就能通过 USB 进行 TCP 通信。更进一步的是,只要实现了 USBMuxd,非 OSX 系统也能与iPhone 系统进行通信,例如 Windows 和 Linux。

peertalk & Usbmux 资料收集与整理_第2张图片

github 上也存在一个名为 USBMuxd 的项目,这是一个跨平台的开源项目,实现了 usbmux 的功能,支持 MAC/Linux/Windows 平台。

usbmuxd连接流程

以下是Mac应用程序、App与 USBMuxd 的连接流程和数据收发流程。

peertalk & Usbmux 资料收集与整理_第3张图片

有几个地方是需要留意的:

  • 应用程序需要创建两个 Socket , 一个用于监听 USBMuxd 广播包,以便应用程序得知 iOS 设备的连接与断连;另一个 Socket 用于在得知 iOS 设备连接后请求连接,如果 iOS 设备同意连接,后续的数据传输将通过这个 Socket 进行。
  • App 也需要创建两个 Socket,一个为欢迎 Socket,另一个为连接 Socket,这和普通的服务器端套接字流程一样。对于App,USBMuxd 服务是透明的。
  • USBMuxd 的广播包使用自己的一套协议,需要按照该协议生成数据包来和 USBMuxd 进行数据传输。
  • 当应用程序与 iOS 设备连接后,应用程序和 iOS 进行数据传输的协议由我们自己决定。




GitHub - libimobiledevice/libusbmuxd: A client library to multiplex connections from and to iOS devicesA client library to multiplex connections from and to iOS devices - GitHub - libimobiledevice/libusbmuxd: A client library to multiplex connections from and to iOS devicesicon-default.png?t=N7T8https://github.com/libimobiledevice/libusbmuxd

。。。

GitHub - libimobiledevice-win32/libimobiledevice-vs: Visual Studio solution to build all of libimobiledevice at onceVisual Studio solution to build all of libimobiledevice at once - GitHub - libimobiledevice-win32/libimobiledevice-vs: Visual Studio solution to build all of libimobiledevice at onceicon-default.png?t=N7T8https://github.com/libimobiledevice-win32/libimobiledevice-vs

。。。

使用usbmuxd服务,通过USB连接与PC端、Mac端实现通信,Peertalk的使用 - 简书一、usbmuxd 介绍 usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都...icon-default.png?t=N7T8https://www.jianshu.com/p/eba133891ec6

usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都直接或间接地用到了这个服务。

iTunes使用 usbmux 与 iphone 通信, 它提供了一个USB - TCP的转换服务, 这个服务在Mac端是由/System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd提供的, 当然, 开机自动启动。

它创建了一个Unix Domain Socket 在 /var/run/usbmuxd. usbmuxd服务程序监控iPhone在USB口上的连接, 当它监控到iPhone以用户模式连接到USB, (相对的是recovery模式), usbmuxd服务程序就会连接到这个/var/run/usbmuxd的TCP端口, 并开始成为一个USB - TCP 请求转发器

那么,如果想编写个第三方程序与iphone进行通信,实现类似iTunes的功能, 你的程序可以通过usbmuxd! 建立一个TCP连接到/var/run/usbmuxd端口, 根据协议发送对应的请求包, usbmuxd服务会将请求转发到USB的iPhone上。

peertalk,一个基于usbmuxd服务的开源代码,可以实现 iPhone 与 Mac 通信。

libimobiledevice,在可以PC端提供usbmuxd服务,实现 iPhone 与 windows 通信。

二、Peertalk 的使用:iPhone 与 Mac 通信

iOS 端

1、创建 channel,监听指定端口

    // 创建 channel
    PTChannel *channel = [PTChannel channelWithDelegate:self];
    // 监听指定端口,PTExampleProtocolIPv4PortNumber自定义端口号
    [channel listenOnPort:PTExampleProtocolIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
        if (error) { // 创建监听失败
        
        } else { // 创建监听成功 
        
        }
    }];

2、实现 Channel 的代理方法

@protocol PTChannelDelegate 

@required
// 收到信息
- (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData*)payload;

@optional
// 收到消息调用,如回复NO,则忽略这条消息
- (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize;

// 出错回调
- (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error;

// 连接成功回调
- (void)ioFrameChannel:(PTChannel*)channel didAcceptConnection:(PTChannel*)otherChannel fromAddress:(PTAddress*)address;

@end

3、连接成功后,会发送设备信息

Mac 端

1、监听USB设备的连接/断开

// 开始监听设备的连接与断开
- (void)startListeningForDevices {
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    
    // 监听设备连接
    [nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
        NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
        dispatch_async(notConnectedQueue_, ^{
            if (!connectingToDeviceID_ || ![deviceID isEqualToNumber:connectingToDeviceID_]) {
                // 断开现有连接
                [self disconnectFromCurrentChannel];
                connectingToDeviceID_ = deviceID;
                connectedDeviceProperties_ = [note.userInfo objectForKey:@"Properties"];
                // 重新连接
                [self enqueueConnectToUSBDevice];
            }
        });
    }];
    
    // 监听设备断开
    [nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
        NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
        if ([connectingToDeviceID_ isEqualToNumber:deviceID]) {
            connectedDeviceProperties_ = nil;
            connectingToDeviceID_ = nil;
            if (connectedChannel_) { // 关闭连接通道
                [connectedChannel_ close];
            }
        }
    }];
}

2、连接设备

// 连接设备
- (void)connectToLocalIPv4Port {
    // 创建通道,设置代理
    PTChannel *channel = [PTChannel channelWithDelegate:self];
    channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%d", PTExampleProtocolIPv4PortNumber];
    // 连接指定端口地址,与iOS端设置保持一致
    [channel connectToPort:PTExampleProtocolIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) {
        if (error) { // 连接失败
            
        } else { // 连接成功
            
        }
    }];
}

3、连接成功会,会收发送 ping、pong 心跳数据

三、libimobiledevice、Peertalk 的使用:iPhone 与 windows 通信

1、实现原理

windows端 通过 libimobiledevice 运行 usbmuxd 的多路复用守护进程,该进程的作用是建立本地端口和远程端口的转发,实现usb到tcp的转换服务

2、安装服务

windows端首先要安装苹果公司提供的相关服务,才能实现通信功能。服务名称为:AppleApplicationSupport和AppleMobileDeviceSupport

3、规定协议

首先指定 ip地址和端口,端口号建议大些,以免与苹果系统应用端口重复。如:127.0.0.1:62345。PC端可以通过端口转发实现。
定义相同结构体数据,以便数据的加密、解析。PC端可和Peertalk定义的协议保持一致。如:

// 数据头结构体
typedef struct _PTFrame {
  uint32_t version;     // 对应版本
  uint32_t type;        // 数据类型
  uint32_t tag;         // tag标记
  uint32_t payloadSize; // 数据大小
} PTFrame;

// 数据类型
enum {
  PTExampleFrameTypeDeviceInfo = 100,  // 设备信息
  PTExampleFrameTypeTextMessage = 101, // 文本数据
  PTExampleFrameTypePing = 102,        // Ping
  PTExampleFrameTypePong = 103,        // Pong
};
4、联调测试

定好协议后,指定相同ip地址和端口,进行测试。如连接失败,可尝试更换端口号重试。先调试连接,然后再收发数据,最后进行数据处理。

peertalk

目录

Usbmux

USBMuxd

usbmuxd连接流程

二、Peertalk 的使用:iPhone 与 Mac 通信

iOS 端

Mac 端

三、libimobiledevice、Peertalk 的使用:iPhone 与 windows 通信

1、实现原理

2、安装服务

3、规定协议

4、联调测试


https://blog.csdn.net/yxys01/article/details/77718506

https://www.zhaoxiaodan.com/ios/ituns%E4%B8%8Eiphone%E7%9A%84%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEusbmuxd%E8%A7%A3%E6%9E%90.html如何获取iphone已安装的app?如何获取iphone文件列表?iTunes使用一种叫icon-default.png?t=N7T8https://www.zhaoxiaodan.com/ios/ituns%E4%B8%8Eiphone%E7%9A%84%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEusbmuxd%E8%A7%A3%E6%9E%90.html

ituns与iphone的通信协议usbmuxd解析

最开始研究与iphone通信, 都会想当然的google下usb协议, 必经iphone是通过usb线连接到电脑. 其实不然, iTunes是通过TCP协议与iPhone通信的

##usbmuxd

iTunes使用一种叫”usbmux”的东西与iphone通信, 这个东西提供了一个USB - TCP的转换服务.
这个服务在Mac端是由/System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd 提供的, 当然, 开机自动启动.
它创建了一个Unix Domain Socket 在 /var/run/usbmuxd. usbmuxd服务程序监控iPhone在USB口上的连接, 当它监控到iPhone以用户模式连接到USB, (相对的是recovery模式), usbmuxd服务程序就会连接到这个/var/run/usbmuxd的TCP端口, 并开始成为一个USB - TCP 请求转发器

那么,如果想编写个第三方程序与iphone进行通信,实现类似iTunes的功能, 你的程序可以通过usbmuxd! 建立一个TCP连接到/var/run/usbmuxd端口, 根据协议发送对应的请求包, usbmuxd服务会将请求转发到USB的iPhone上

##lockdownd协议

//协议头
struct usbmux_header {
	u32 length;	// 消息长度,包括头部
	u32 version;	// 协议版本号
	u32 type;       // 消息类型,请求,响应,握手,等
	u32 tag;	// 消息编号, 用来对应响应
	char payload;  //请求体
};

//头部中的type类型
enum {
	usbmux_result  = 1,
	usbmux_connect = 2,
	usbmux_hello   = 3,
	usbmux_payload = 8,
};

##监听

知道了iTunes使用的协议, 那么有没有办法看看iTunes都发了些什么包? 有个简单的办法就是使用socat, 类似:

sudo mv /var/run/usbmuxd /var/run/usbmuxx
sudo socat -t100 -x -v UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx

##包示例:

。。。

第一个iTunes向/var/run/usbmuxd发的请求包, 第一行 e3 01 00 00 01 00 00 00 08 00 00 00 02 00 00 00是头部 e3 01 00 00 即0x01e3 = 483, 包长度

头部后面紧跟的是payload, 也就是xml格式的请求内容

阿里最新、最强开源iOS自动化测试神器 - 知乎

前言

一直以来,iOS自动化的实现&执行都依赖 Mac 系统,其主要原因是因为需要通过 xcodebuild 编译&安装 WDA (WebDriverAgent) 到 iOS 设备中,通过WDA实现对被测应用进行操作。

导致想要做iOS自动化 就必须拥有 Mac 设备的现象

  • 常用电脑非 Mac 的同学 想要做 iOS 自动化的时候, 就需要再申请一台Mac设备 ,可能会出现资源利用不充分的情况
  • 云测试平台要搭建 IOS 自动化 服务环境时,也只能批量申请 Mac 设备,经费在燃烧

开源神器: tidevice

一个月前,阿里团队开源了一个内部使用的 iOS自动化工具 :tidevice (

https://github.com/alibaba/taobao-iphone-device

) ,让我们可以更方便、简单的脱离Mac的限制。

tidevice 能做什么

  • 设备信息获取
  • 应用安装、卸载、启动、停止、查看应用信息、已安装应用列表
  • 启动 WebDriverAgent (不依赖 xcodebuild , 跨平台)
  • 运行 XcTest UITest
  • 性能数据采集
  • 设备截图、设备日志 ...

tidevice 核心原理

usbmux通信协议:

实现 Mac/Windows/Linux 与 iOS设备服务间的通信

  • Mac
    • usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都直接或间接地用到了这个服务。
  • Linux / Windows
    • 本身是没有 usbmux的,不过都有开源项目的实现,可以直接使用/参考
  • Windows 另外依赖 AppleApplicationSupport和AppleMobileDeviceSupport 两个服务,安装Itunes 环境即可安装对应服务

usbmux 本身是socket 套接字,通过截获、破解等手段,结合开源界的成果,用python 进行模拟,从而实现了当前工具已有的所有功能

usbmux协议分析 - Lazy Eval

usbmux协议分析

  

  • usbmuxd
  •  Aug 20, 2019

usbmux协议介绍

初闻usbmux协议时,可能会让人感觉比较陌生,但其实如果你是MAC用户的话,你可能每天都在和它打交道,只是不知道而已。当通过USB将iPhone连接到MAC时,usbmux协议已经在背后为你默默工作了。

usbmux是苹果的私有协议,苹果设计该协议的原因是为了自家的macOS APP能够和iDevice进行通信,从而实现诸如iTunes备份iPhone、Xcode真机调试等功能。只是后来该协议被开发者破解了,于是为众人所知。

usbmuxd是对usbmux协议在macOS平台的上实现,也是macOS系统上的一个守护进程(Daemon),它随着系统的启动而启动。在macOS上,与usbmuxd相关的文件主要有3个:

  1. /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd
  2. /System/Library/LaunchDaemons/com.apple.usbmuxd.plist
  3. /var/run/usbmuxd

其中/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd为可执行文件。

/System/Library/LaunchDaemons/com.apple.usbmuxd.plist为usbmuxd服务的开机启动配置文件,如图所示。launchd在系统启动时会根据该文件的配置信息,启动usbmuxd服务,创建对应的socket(也即/var/run/usbmuxd)。

peertalk & Usbmux 资料收集与整理_第4张图片

/var/run/usbmuxd在开机启动时创建,该文件其实是一个Unix Domain Socket,用于usbmuxd进程和其他进程——比如Xcode、iTunes等——之间的进程间通信(IPC)。

➜  zyqhi.github.io git:(master) ✗ file /var/run/usbmuxd
/var/run/usbmuxd: socket

基于usbmux协议的设备间通信

前文也提到,苹果设计usbmux协议是为了解决macOS APP和iOS设备之间的通信问题。如果只是普通的协议的话,倒也没什么可说的,只是该协议呢,提供了一种类似TCP socket的API,使得macOS和iOS设备之间的通信,如同是网络上的两个主机之间的通信。

During normal operations, iTunes communicates with the iPhone using something called “usbmux” – this is a system for multiplexing several “connections” over one USB pipe. Conceptually, it provides a TCP-like system – processes on the host machine open up connections to specific, numbered ports on the mobile device. (This resemblance is more than superficial – on the mobile device, usbmuxd actually makes TCP connections to localhost using the port number you give it.)

FROM: https://www.theiphonewiki.com/wiki/Usbmux

说完usbmux协议的用途,我们再来看看usbmux协议是怎样工作的。这里以iTunes和iOS通信为例,来看下整个通信架构,如图所示:

peertalk & Usbmux 资料收集与整理_第5张图片

从图中可以看出,整个通信架构和经典的C/S架构非常类似。其中iOS端的服务(lockdown)的角色是服务端,macOS端的程序(iTunes)则为客户端。

因为没有源码,所以此处的通信架构图只是根据网上前人的工作,结合个人的测试,推测得出的。如有错误,还请指正。

lockdown服务

先来看下iOS端:

图中的lockdown是iOS端的系统服务,它在iOS系统启动时,由launchd进程启动。lockdown服务对应的启动配置文件在iOS系统上的完整路径为:/System/Library/LaunchDaemons/com.apple.mobile.lockdown.plist

peertalk & Usbmux 资料收集与整理_第6张图片

lockdown作为一个网关,用于协调macOS进程和iOS其他服务之间的通信。像我们平时用到的iTunes备份iPhone,Xcode真机调试等功能,在iOS侧,都是需要lockdown服务进行中转处理的。举个例子,Xcode若要执行真机调试,首先需要和lockdown服务通信,发出启动调试请求,lockdown收到请求以后,启动iOS端对应的调试服务(debugserver),然后Xcode便与debugserver之间建立了通信连接。

lockdown服务启动以后,会创建一个TCP listen socket,端口号为62078,地址为本机地址:127.0.0.1。这一点我们可以在越狱机器上得到验证:

peertalk & Usbmux 资料收集与整理_第7张图片

如果从TCP/IP网络编程的角度来看lockdown的话,会发现它和普通的TCP Server没有差别。

关于lockdown服务的更多内容,可以参考这篇文章:Understanding usbmux and the iOS lockdown service

usbmuxd服务

再来看macOS端:

前文讲过,usbmuxd是macOS的系统服务,该服务为macOS端的通信代理,当其他应用——比如iTunes——需要与iOS设备通信时,只需要与usbmuxd服务通信即可,usbmuxd负责将iTunes的通信请求转发到iOS端。usbmuxd服务与iOS设备通信基于USB通信协议。

iTunes与usbmuxd服务之间通过Unix Domain Socket进行通信,Unix Domain Socket的API和TCP Socket非常类似。来看下与usbmuxd协议之间建立连接的代码:

int connect_to_usbmuxd() {
    // Create Unix domain socket
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    // prevent SIGPIPE
    int on = 1;
    setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));

    // Connect socket
    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    // 这个路径就相当于ip地址,为了理解这个过程,可以和TCP进行对比
    strcpy(addr.sun_path, "/var/run/usbmuxd");
    socklen_t socklen = sizeof(addr);

    if (connect(fd, (struct sockaddr *)&addr, socklen) == -1) {
        printf("Connect failure, fd is: %d.\n", fd);
    } else {
        printf("Connect successifully, fd is: %d.\n", fd);
    }

    return fd;
}

和TCP的差别有以下几点:

  1. domian参数为AF_UNIX,而非熟悉的AF_NET
  2. 通信地址不是IP地址,而是路径/var/run/usbmuxd
  3. 不需要指定端口。

除此之外,整个API的使用方式则几乎一样。iTunes在建立和usbmuxd之间的连接时,应该使用了类似的代码。

从一端到另一端

前文中提到,对于macOS端的进程而言,如果要与iOS上的进程通信,只需要和usbmuxd服务通信即可。而在上一节我们介绍了进程和usbmuxd服务建立连接的方式,那么假设iTunes通过上述方式建立了与usbmuxd之间的连接,那么接下来iTunes是不是就可以直接与位于iOS端的lockdown服务通信了呢?

答案是NO。

在回答为什么是NO之前,先来回忆一下基于socket的TCP通信过程。我们知道在TCP/IP通信协议体系里,IP抽象了网络上两台主机之间的通信,而TCP抽象了两台主机上的进程之间的通信。IP地址标识了网络中的一台主机,而端口号则标识了一台主机上的特定进程。

而在上一节,在建立进程(iTunes)与代理(usbmuxd)之间的连接时,并未指定端口号。未指定端口号,那么usbmuxd服务便不知道进程要和iOS端的哪个进程进行通信。

比如iTunes要和lockdown服务建立连接,lockdown服务的端口号是62078,而iTunes在建立连接时并未指定该端口号,因此对于usbmuxd而言,根本不知道iTunes要和lockdown服务通信。建立连接所需的信息是不完备的。

其实,如果用上一节中的提到的建立连接,只是建立了macOS进程与代理之间的通信连接,该连接建立以后,进程能够和代理进行通信,但若要和iOS上的目标进程通信的话,我们还需要对代理进行配置,让代理和iOS端的进程也建立连接,整个通信链路才算打通。

下面我们便来看下如何『配置』代理,从而打通整个通信链路。

Listen

在讲具体的连接过程之前,我们先来想下,从通信的角度讲,如果要usbmuxd服务和iOS端进程之间建立连接,必须要满足哪些条件:

  1. 一台Mac可以连接多台iOS设备,连接时需要知道和哪一台iOS设备建立连接,因此需要device id来唯一标识一台设备;
  2. 建立连接时必须要知道当前Mac是否连接到iOS设备,也即需要监听iOS设备的插拔;
  3. usbmuxd不会主动去连接iOS端上的进程,必须macOS进程驱使其完成,也即对其进行配置,此时macOS进程和usbmuxd之间需要一套通信协议;
  4. 必须指定需要连接到的目标进程的端口号,这样才能唯一标识一个进程。

先看问题3,苹果定义了一套配置协议,该协议的数据传输格式为plist,并被破解,协议的具体细节会在下文中阐释数据收发过程中讲到。而对于4,后文中会给出解答,现在先忽略。

而对于问题1和2,usbmuxd已经具备该功能,但是如果要『看到』该功能,我们需要向usbmuxd发送命令,这便是本节中要讲的Listen命令。

所谓发送Listen命令,便是macOS进程调用上一节中提到的connect_to_usbmuxd函数,创建的socket,然后向对应的socket文件描述符中写入一段数据(控制命令),然后读一段数据(usbmuxd的返回值)。发送逻辑的代码如下:

void send_listen_packet() {
    listen_channel = connect_to_usbmuxd_channel(); // 该函数调用 connect_to_usbmuxd 创建socket,并根据socket文件描述符创建一个dispatch I/O对象
    
    // 1. Listen指令对应的键值对
    NSDictionary *packet = @{
                             @"ClientVersionString": @"1",
                             @"MessageType": @"Listen",
                             @"ProgName": @"Peertalk Example"
                             };
    NSLog(@"send listen packet: %@", packet);
    send_packet(packet, 0, listen_channel);
}

void send_packet(NSDictionary *packetDict, int tag, dispatch_io_t channel) {
    NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packetDict format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
    
    int protocol = USBMuxPacketProtocolPlist;
    int type = USBMuxPacketTypePlistPayload;
    
    // 2. 封装成usbmux协议要求的格式
    usbmux_packet_t *upacket = usbmux_packet_create(
                                                    protocol,
                                                    type,
                                                    tag,
                                                    plistData ? plistData.bytes : nil,
                                                    (uint32_t)(plistData.length)
                                                    );
    
    dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, usbmuxd_io_queue, ^{
        usbmux_packet_free(upacket);
    });
    
    dispatch_io_write(channel, 0, data, usbmuxd_io_queue, ^(bool done, dispatch_data_t data, int _errno) {
        NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, _errno);
        if (!done) { return; }
    });
}

此处的listen_channel是一个dispatch I/O对象,所谓的dispatch I/O对象,就是一个封装了文件描述符,以及作用于该文件描述符上的异步I/O策略。本文中可以将其和文件描述符作为等价的概念来理解。

这部分代码核心就2处,代码中已标出:

  1. 以plist格式封装Listen指令;
  2. 是将封装后的Listen报文发送到usbmuxd。

对应的报文格式如下图所示:

peertalk & Usbmux 资料收集与整理_第8张图片

Listen命令发送成功以后,每当有iOS设备插拔,便会收到usbmuxd服务返回的数据。读取逻辑如下:

void read_packet_on_channle(dispatch_io_t channel) {
    // 1. Read the header
    usbmux_packet_t ref_upacket;
    dispatch_io_read(channel, 0, sizeof(ref_upacket.size), usbmuxd_io_queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
        
        if (!done) { return; }
        
        // Read size of incoming usbmux_packet_t
        uint32_t upacket_len = 0;
        char *buffer = NULL;
        size_t buffer_size = 0;
        // data 是读取到的数据,这一步获取到读取到的data的长度,并将buffer指向对应的缓冲区
        dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
        memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size);
        
        // Allocate a new usbmux_packet_t for the expected size
        uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t);
        usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength);
        
        // 2. Read rest of the incoming usbmux_packet_t
        off_t offset = sizeof(ref_upacket.size);
        dispatch_io_read(channel, offset, upacket->size - offset, usbmuxd_io_queue, ^(bool done, dispatch_data_t data, int error) {
            NSLog(@"dispatch_io_read %lld,%lld: done=%d data=%p error=%d", offset, upacket->size - offset, done, data, error);
            
            if (!done) { return; }
            
            // Copy read bytes onto our usbmux_packet_t
            char *buffer = NULL;
            size_t buffer_size = 0;
            dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
            memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size);
            NSLog(@"package protocol is: %u, type is: %u", upacket->protocol, upacket->type);
            
            // 3. Try to decode any payload as plist
            NSError *err = nil;
            NSDictionary *dict = nil;
            if (usbmux_packet_payload_size(upacket)) {
                dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err];
            }
            
            // 4. Read next
            read_packet_on_channle(channel);
            
            usbmux_packet_free(upacket);
        });
    });
}

读逻辑中我删除了校验和容错逻辑,只保留了比较关键的部分,Listen命令的读操作说明如下:

  1. 先读取报文头,usbmuxd返回的报文格式也遵循usbmux协议,头部中记录了报文载荷的长度,所以必须先读出报文头之后,才能知道载荷的边界到哪里;
  2. 从头部解析出载荷长度之后,读取报文载荷数据;
  3. 头部和载荷读取完成之后,构成一个完整的报文,此时以plist格式解析载荷,获取返回内容;
  4. 读取下一个报文,读操作采用的I/O模式是阻塞模式,一直监听是否有数据,当有数据时便进行读取解析。

peertalk & Usbmux 资料收集与整理_第9张图片

如图所示,是当iOS设备通过USB连接到(Attached)Mac时,usbmuxd服务返回的数据。其中比较关键的字段有两个:

  1. MessageType: Attached表明有设备连接的Mac,而Detached则表明断开连接;
  2. DeviceID: 唯一标识当前插入的设备,后续与设备建立连接时会用到。

iTunes监听设备插拔时,便是通过类似的方式。

Connect

说完Listen,再来看下Connect。在上一节中,我们抛出了usbmuxd服务和iOS端进程之间建立连接的4点要求,其中前3个在上一节都已经得到解决,而第4个问题——端口号——其实在lockdown的启动配置文件时就已经知道了。此时我们已经具备建立连接的所有条件:

  1. Listen时得到DeviceID;
  2. lockdown启动配置文件中得知lockdown的端口号。

在讲Listen时,我们知道可以通过向usbmuxd服务发送Listen命令监听iOS设备插拔,类似地,也可以通过向usbmuxd服务发送Connect命令对其配置,使其建立和iOS端应用程序的连接。

整个过程如下,我们需要再调用connect_to_usbmuxd函数再创建一个socket,用作Connect命令,和Listen命令的发送方式一样,发送Connect命令也是向socket文件描述符中写入数据:

void send_connect_usb_packet() {
    print_empty_lines();
    
    connect_channel = connect_to_usbmuxd_channel();
    

    port = ((port<<8) & 0xFF00) | (port>>8);
    NSDictionary *packet = @{
                             @"ClientVersionString" : @"1",
                             @"DeviceID" : deviceID,
                             @"MessageType" : @"Connect",
                             @"PortNumber" : [NSNumber numberWithInt:port],
                             @"ProgName" : @"Peertalk Example"
                             };
    
    NSLog(@"send connect to usb packet: %@", packet);
    send_packet(packet, 1, connect_channel);
    read_packet_on_channle(connect_channel);
}

其中关键的字段有:

  1. DeviceID: 设备id,在Linten时获得;
  2. MessageType: 消息类型,Connect表示需要建立连接;
  3. PortNumber: 端口号,对应iOS端目标连接服务的端口,以lockdown为例,为62078。

对应的报文格式为:

peertalk & Usbmux 资料收集与整理_第10张图片

注:实际发送过程中此处的PortNumber会以网络字节序的形式传输。

发送Connect命令之后,如果连接成功,此时便会收到usbmuxd返回的数据(读取Connect返回的数据和之前的读取Listen命令返回的数据方式相同):

peertalk & Usbmux 资料收集与整理_第11张图片

Number为0表示连接成功。

为了容易理解,我们来和标准的TCP通信做个对比,其中右侧的lockdown,就是标准的TCP Server,而左侧的iTunes,建立与lockdown的连接时不是像标准的TCP Client一样,调用connect函数,而是向usbmuxd服务发送Connect命令。然而,虽然方法不同,但是二者在通信意义上是等价的。

peertalk & Usbmux 资料收集与整理_第12张图片

并且,当在Connect命令发送后,在iOS侧,lockdown的accept方法会执行,随后双方便可以像标准的TCP通信一样发送和接收数据。

iTunes发送数据的方式如下:

void send_msg(NSString *msg) {
    dispatch_data_t payload = create_payload(msg);
    dispatch_data_t frame = create_frame(101, 0, payload);

    // send through connect channel, not tcp_channel
    dispatch_io_write(connect_channel, 0, frame, usbmuxd_io_queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
        NSLog(@"error is: %d", error);
    });
}

其中connect_channel是我们在发送Connect命令时创建的文件描述符。

注意Listen和Connect使用不同的文件描述符。

经历以上过程,macOS程序便实现了通过usbmuxd和iOS通信。总结下来:

  1. macOS端进程通过Unix Domain Socket以类似TCP的方式和usbmuxd服务进行通信;
  2. macOS端进程通过向usbmuxd服务发送Connect命令,建立与iOS端目标进程之间的通信连接,端到端的通信基于端口号;
  3. iOS端进程以标准的TCP Server形式存在;
  4. usbmuxd作为中间代理,封装了USB协议的通信细节,对上提供类TCP的接口;
  5. 基于usbmux协议和usbmuxd服务,实现了macOS进程和iOS端进程之间简单、高效的通信。

简单在于协议的API基于socket,为大众熟知,而在通信效率方面,基于USB协议的通信效率显然比基于TCP/IP网络要快得多。

usbmux应用

如果是单纯分析macOS进程和iOS进程之间的通信原理的话,那大可不必如此大费周章,usbmuxd的魔力不仅在于它可以用于苹果自带应用和iOS程序之间的通信,而且,我是说而且,它可以用于为我们所用,基于usbmuxd服务,我们可以利用usbmux协议构建我们自己的应用。

我们可以基于usbmuxd服务,利用usbmux协议构建我们自己的应用。

重新审视lockdown

我们回过头来,重新看下lockdown做了哪些事情,从前文中lockdown的启动配置文件可以看出,它在开机时创建了一个listen socket,监听本地端口:62078,然后像普通的TCP Server那样,bind、accept阻塞等待客户端的连接。而当macOS端向usbmuxd发送Connect命令时,lockdown的accept结束阻塞,连接建立。

lookdown的源码没有,但是从启动配置文件和nc端口测试,结合网络知识,我们基本可以确认lockdown的内在逻辑。

那么,如果我们模拟lockdown,新建一个iOS APP,指定端口号,比如2345,然后通过调用socket、bind、listen、accept建立我们自己的TCP Server监听127.0.0.1:2345。

同时新建一个macOS应用,像iTunes一样连接usbmuxd服务,以端口号2345向其发送Connect命令,那么会发生什么呢?

答案是,如我们所愿,利用usbmuxd协议,我们建立了自己的macOS应用和iOS APP之间的通信连接,随后便可在此协议的基础之上,实现自建iOS APP和macOS应用程序之间的高效通信。整个通信框架如下:

peertalk & Usbmux 资料收集与整理_第13张图片

前文中花了大量的篇幅来阐述usbmux协议的原理,最终的目的就是这么简单,我们想在自己的产品中应用usbmux协议。

usbmux应用举例

usbmux协议由于通信效率较高,在数据量较大的通信场景中,体验非常突出,毕竟当传输的数据量较大时,效率就是体验。目前基于usbmux有非常多的有意思的应用,常见的有:

  • PeerTalk: PeerTalk基于usbmux实现了一个简单的IM,可用于macOS端和和iOS端之间聊天。本文的代码就是参照PeerTalk,如果你想基于usbmux协议构建自己的应用,而又不想深入了解usbmux协议的的话,可以直接搬运PeerTalk的代码。
  • lookin: 腾讯开发的iOS界面调试利器,简直比Reveal还好用,而且免费。

参考

  • usbmux_demo: 本文中的示例代码。
  • libimobiledevice: 跨平台的协议库,用于iOS设备通信
  • usbmux
  • launchd
  • Understanding usbmux and the iOS lockdown service
  • Unix domain socket 简介
  • Dispath I/O

peertalk 实验

peertalk & Usbmux 资料收集与整理_第14张图片

macos example  & ios example

peertalk & Usbmux 资料收集与整理_第15张图片

你可能感兴趣的:(DIY,mac,mac)