usbmux原理以及PeerTalk源码解析

usbmux简介

usbmux是macOS系统上的一个守护进程(Daemon),当通过USB将iPhone连接到MAC时,usbmux协议已经在背后为你默默工作了

相关文件:

  • /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd
  • /var/run/usbmuxd (在开机启动时创建,该文件其实是一个Unix Domain Socket, 例如itune通过这个和usbmuxd通信)

lockdown服务

lockdown作为一个网关,用于协调macOS进程和iOS其他服务之间的通信,lockdown服务启动以后,会创建一个TCP listen socket,端口号为62078,地址为本机地址:127.0.0.1

相关应用

  • 三方软件读取ios设备相册, iTunes备份iPhone,Xcode真机调试, Xcode若要执行真机调试,首先需要和lockdown服务通信,发出启动调试请求,lockdown收到请求以后,启动iOS端对应的调试服务(debugserver),然后Xcode便与debugserver之间建立了通信连接
  • PerfDog,实现跨平台获取非越狱设备的性能数据,实现和xcode中开发功能通信,方便自动化获取instrument数据,原理通过DTXMessage 数据流格式,并对其进行编解码
  • py-ios-device
  • lookin
  • peertalk

连接流程

连接流程和标准的tcp对比


peertalk源码解析

peertalk 的实现主要依赖 GCD 和 Socket, 通过 dispatch_data 转换各种自定义数据,通过 dispatch_io 写入或者读出 Socket。
可以指定执行传输任务的 dispatch_queue

监听usbmuxd广播包soket创建代码

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;
}
Listen
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命令发送成功以后,每当有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);
        });
    });
}
  • MessageType: Attached表明有设备连接的Mac,而Detached则表明断开连接;
  • DeviceID: 唯一标识当前插入的设备,后续与设备建立连接时会用到。

Connect

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);
}
  • DeviceID: 设备id,在Linten时获得;
  • MessageType: 消息类型,Connect表示需要建立连接;
  • PortNumber: 端口号,对应iOS端目标连接服务的端口,以lockdown为例,为62078

发送Connect命令之后,如果连接成功,此时便会收到usbmuxd返回的数据,Number为0表示连接成功

发表评论

邮箱地址不会被公开。 必填项已用*标注