blob: 58bf00fdffb83e7e29fef26d844f6abbfd604754 [file] [log] [blame] [edit]
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "Device/Sources/EDOUSBMuxUtil.h"
// The types of usbmux message.
NSString *const kEDOPlistPacketTypeConnect = @"Connect";
NSString *const kEDOPlistPacketTypeListen = @"Listen";
NSString *const kEDOPlistPacketTypeResult = @"Result";
NSString *const kEDOMessageDeviceIDKey = @"DeviceID";
NSString *const kEDOMessagePropertiesKey = @"Properties";
NSString *const kEDOMessageSerialNumberKey = @"SerialNumber";
NSString *const kEDOMessageTypeAttachedKey = @"Attached";
NSString *const kEDOMessageTypeDetachedKey = @"Detached";
NSString *const kEDOMessageTypeKey = @"MessageType";
NSString *const EDODeviceErrorDomain = @"EDODeviceError";
// The code of usbmuxd response.
typedef NS_ENUM(uint32_t, EDOUSBMuxReplyCode) {
USBMuxReplyCodeOK = 0,
USBMuxReplyCodeBadCommand = 1,
USBMuxReplyCodeBadDevice = 2,
USBMuxReplyCodeConnectionRefused = 3,
// ? = 4,
// ? = 5,
USBMuxReplyCodeBadVersion = 6,
};
// The code of usbmuxd packet type.
typedef NS_ENUM(uint32_t, EDOUSBMuxPacketType) {
USBMuxPacketTypeResult = 1,
USBMuxPacketTypeConnect = 2,
USBMuxPacketTypeListen = 3,
USBMuxPacketTypeDeviceAdd = 4,
USBMuxPacketTypeDeviceRemove = 5,
// ? = 6,
// ? = 7,
USBMuxPacketTypePlistPayload = 8,
};
// The code of usbmuxd protocol type. eDO is only using plist.
typedef NS_ENUM(uint32_t, EDOUSBMuxPacketProtocol) {
USBMuxPacketProtocolBinary = 0,
USBMuxPacketProtocolPlist = 1,
};
/** The struct of a usbmuxd packet. */
typedef struct EDOUSBMuxPacket {
uint32_t size;
EDOUSBMuxPacketProtocol protocol;
EDOUSBMuxPacketType type;
uint32_t tag;
char data[0];
} __attribute__((__packed__)) EDOUSBMuxPacket_t;
const uint32_t kEDOPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(EDOUSBMuxPacket_t);
@implementation EDOUSBMuxUtil
+ (NSDictionary *)listenPacket {
return [self packetDictionaryWithMessageType:kEDOPlistPacketTypeListen payload:nil];
}
+ (NSDictionary *)connectPacketWithDeviceID:(NSNumber *)deviceID port:(UInt16)port {
// Need to transform to big endian for usbmuxd.
UInt16 bigEndianPort = CFSwapInt16HostToBig(port);
return [self packetDictionaryWithMessageType:kEDOPlistPacketTypeConnect
payload:@{
@"DeviceID" : deviceID,
@"PortNumber" : [NSNumber numberWithInt:bigEndianPort]
}];
}
+ (size_t)sizeOfPayloadSize {
static EDOUSBMuxPacket_t refPacket;
return sizeof(refPacket.size);
}
+ (dispatch_data_t)createPacketDataWithPayload:(NSDictionary<NSString *, id> *)payload
error:(NSError **)error {
NSData *payloadData =
[NSPropertyListSerialization dataWithPropertyList:payload
format:NSPropertyListXMLFormat_v1_0
options:0
error:error];
if (!payload) {
return nil;
}
uint32_t payloadSize = (uint32_t)(payloadData ? payloadData.length : 0);
assert(payloadSize <= kEDOPacketMaxPayloadSize);
uint32_t packetSize = sizeof(EDOUSBMuxPacket_t) + payloadSize;
EDOUSBMuxPacket_t *packet = malloc(packetSize);
memset(packet, 0, sizeof(EDOUSBMuxPacket_t));
packet->size = packetSize;
packet->protocol = USBMuxPacketProtocolPlist;
packet->type = USBMuxPacketTypePlistPayload;
if (payloadData && payloadSize) {
const void *payloadBytes = payloadData ? payloadData.bytes : NULL;
memcpy((void *)packet->data, payloadBytes, (uint32_t)payloadSize);
}
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_data_t data =
dispatch_data_create((const void *)packet, packet->size, backgroundQueue, ^{
// Free packet when data is freed
free(packet);
});
return data;
}
+ (NSDictionary *)payloadDictionaryFromPacketData:(dispatch_data_t)data error:(NSError **)error {
// Copy read bytes onto our usbmux_packet_t
EDOUSBMuxPacket_t *packet = NULL;
size_t buffer_size = 0;
NS_VALID_UNTIL_END_OF_SCOPE dispatch_data_t map_data =
dispatch_data_create_map(data, (const void **)&packet, &buffer_size);
assert(buffer_size == (size_t)(packet->size));
NSDictionary *dict = nil;
NSError *packetError;
if ([self validatePacket:packet error:&packetError]) {
// Try to decode any payload as plist
uint32_t payloadSize = [self payloadSizeFromPacket:packet];
if (payloadSize > 0) {
dict = [NSPropertyListSerialization
propertyListWithData:[NSData dataWithBytesNoCopy:(void *)packet->data
length:payloadSize
freeWhenDone:NO]
options:NSPropertyListImmutable
format:NULL
error:&packetError];
}
}
if (error) {
*error = packetError;
}
return dict;
}
+ (NSError *)errorFromPlistResponsePacket:(NSDictionary<NSString *, id> *)packet {
NSNumber *number = [packet objectForKey:@"Number"];
if (number) {
EDOUSBMuxReplyCode replyCode = (EDOUSBMuxReplyCode)number.integerValue;
if (replyCode != 0) {
NSString *errmessage = @"Unspecified error";
switch (replyCode) {
case USBMuxReplyCodeBadCommand:
errmessage = @"illegal command";
break;
case USBMuxReplyCodeBadDevice:
errmessage = @"unknown device";
break;
case USBMuxReplyCodeConnectionRefused:
errmessage = @"connection refused";
break;
case USBMuxReplyCodeBadVersion:
errmessage = @"invalid version";
break;
default:
break;
}
return [NSError errorWithDomain:EDODeviceErrorDomain
code:replyCode
userInfo:@{NSLocalizedDescriptionKey : errmessage}];
}
}
return nil;
}
#pragma mark - Private
/**
* Creates a packet with given @c messageType and @c payload.
*
* @param messageType The type of message usbmuxd accepts. See @c EDOUSBMuxUtil.h for all
* available types.
* @param payload The payload sent to usbmuxd. The content depends on the type of message.
*
* @return The data of the packet to send to usbmuxd.
*/
+ (NSDictionary *)packetDictionaryWithMessageType:(NSString *)messageType
payload:(NSDictionary<NSString *, id> *)payload {
NSDictionary *packet = nil;
static NSString *bundleName = nil;
static NSString *bundleVersion = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
if (infoDict) {
bundleName = infoDict[@"CFBundleName"];
bundleVersion = [infoDict[@"CFBundleVersion"] description];
}
});
if (bundleName) {
packet = @{
kEDOMessageTypeKey : messageType,
@"ProgName" : bundleName,
@"ClientVersionString" : bundleVersion
};
} else {
packet = @{kEDOMessageTypeKey : messageType};
}
if (payload) {
NSMutableDictionary<NSString *, id> *mpacket =
[NSMutableDictionary dictionaryWithDictionary:payload];
[mpacket addEntriesFromDictionary:packet];
packet = mpacket;
}
return packet;
}
+ (BOOL)validatePacket:(EDOUSBMuxPacket_t *)packet error:(NSError **)error {
// We only support plist protocol
if (packet->protocol != USBMuxPacketProtocolPlist) {
*error =
[NSError errorWithDomain:EDODeviceErrorDomain
code:0
userInfo:@{NSLocalizedDescriptionKey : @"Unexpected package protocol"}];
return NO;
}
// Only one type of packet in the plist protocol
if (packet->type != USBMuxPacketTypePlistPayload) {
*error = [NSError errorWithDomain:EDODeviceErrorDomain
code:0
userInfo:@{NSLocalizedDescriptionKey : @"Unexpected package type"}];
return NO;
}
return YES;
}
+ (uint32_t)payloadSizeFromPacket:(EDOUSBMuxPacket_t *)packet {
return packet->size - sizeof(EDOUSBMuxPacket_t);
}
@end