KDE Connect iOS Develop Dairy(2) Identity Protocol

In the previous post, KDE Connect iOS Develop Dairy(1) Build, the build of KDE Connect iOS has been fixed, we can install the application into a device or a simulator.

To connect with the other devices, we need to pair with them. But, before that, the devices need to discover each other with the KDE Connect identity mechanism.

Identity Process

The initial identity process is straightforward, as shown below:

  1. First, device A will send a UDP broadcast packet, which carries its identity packet;
  2. Each device B, which receives the UDP broadcast, will try to extract the TCP port information in the packet and try to connect with the device via the port;
  3. With a TCP connection, each device B will send its identity packet to device A;
  4. Then, the devices will add a link item to the discovered device list and wait for users’ actions.

Update Identity Packet

With a first attempt, all my devices can find each other, except the iOS one.

In the debug mode, I saw a discover message and an associated output:

1
"Inoki" uses an old protocol version, this won't work

So, I captured the packets using Wireshark, to see why the old implementation in KDE Connect iOS does not work. The differences are in the packet content and tailored data.

Identity Packet content

All network packets are generated by serializing NetWorkPackage class, which is defined in lib/NetworkPackage.h and lib/NetworkPackage.m.

The properties of that class are:

1
2
3
4
5
6
@property(nonatomic) NSString* _Id;
@property(nonatomic) NSString *_Type;
@property(nonatomic) NSMutableDictionary *_Body;
@property(nonatomic) NSData *_Payload;
@property(nonatomic) NSDictionary *_PayloadTransferInfo;
@property(nonatomic)long _PayloadSize;

After the serialization, the content will be a JSON format string. For example, the packet content from KDE Connect iOS is:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"id":"1587284674",
"type":"kdeconnect.identity",
"body":{
"deviceId":"test-kdeconnect-ios",
"SupportedOutgoingInterfaces":"kdeconnect.ping,kdeconnect.mpris,kdeconnect.share,kdeconnect.clipboard,kdeconnect.mousepad,kdeconnect.battery,kdeconnect.calendar,kdeconnect.reminder,kdeconnect.contact",
"protocolVersion":5,
"tcpPort":1714,
"deviceType":"Phone",
"deviceName":"Inoki",
"SupportedIncomingInterfaces":"kdeconnect.calendar,kdeconnect.clipboard,kdeconnect.ping,kdeconnect.reminder,kdeconnect.share,kdeconnect.contact"
}
}

And the one from KDE Connect on the other platforms is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"id":1587284383,
"type":"kdeconnect.identity",
"body":{
"deviceId":"9985DA4FDD3449C78ACC8597D2C5A782",
"protocolVersion":7,
"tcpPort":1716,
"deviceType":"phone",
"deviceName":"Inoki",
"incomingCapabilities":[
"kdeconnect.calendar","kdeconnect.clipboard","kdeconnect.ping","kdeconnect.reminder","kdeconnect.share","kdeconnect.contact"
],
"outgoingCapabilities":[
"kdeconnect.ping","kdeconnect.mpris","kdeconnect.share","kdeconnect.clipboard","kdeconnect.mousepad","kdeconnect.battery","kdeconnect.calendar","kdeconnect.reminder","kdeconnect.contact"
]
}
}

Fix id field type

We can see the first difference is about the id field. In the KDE Connect iOS packet, it is a string. But in the newer version protocol, it is an integer.

So, I changedß its property type from NSString to NSNumber:

1
2
3
4
5
6
@property(nonatomic) NSNumber *_Id;
@property(nonatomic) NSString *_Type;
@property(nonatomic) NSMutableDictionary *_Body;
@property(nonatomic) NSData *_Payload;
@property(nonatomic) NSDictionary *_PayloadTransferInfo;
@property(nonatomic)long _PayloadSize;

Update capabilities type

Another significant change is the type and the name of description of capacibilities:

  • They were SupportedOutgoingInterfaces and SupportedIncomingInterfaces, with string type;
  • In the latest version, they are incomingCapabilities and outgoingCapabilities, with array type.

In KDE Connect iOS, it is generated by the following lines in lib/NetworkPackage.m:

1
2
[np setObject:[[[PluginFactory sharedInstance] getSupportedIncomingInterfaces] componentsJoinedByString:@","] forKey:@"SupportedIncomingInterfaces"];
[np setObject:[[[PluginFactory sharedInstance] getSupportedOutgoingInterfaces] componentsJoinedByString:@"," ] forKey:@"SupportedOutgoingInterfaces"];

The returned values are arrays, and a comma string joins them.

So, I changed the key and removed the componentsJoinedByString method:

1
2
[np setObject:[[PluginFactory sharedInstance] getSupportedIncomingInterfaces] forKey:@"incomingCapabilities"];
[np setObject:[[PluginFactory sharedInstance] getSupportedOutgoingInterfaces] forKey:@"outgoingCapabilities"];

Update the protocol version

The protocol version field is defined in the header file:

1
#define ProtocolVersion         5

I update it to 7 to match the current version.

Identity Packet tailor data

The tailor data of identity packet from KDE Connect iOS in a Wireshark traffic is \x0D\x0A.

And the one from other KDE Connect versions is \x0A.

So, I change the line in lib/NetworkPackage.m:

1
2
- #define LFDATA [NSData dataWithBytes:"\x0D\x0A" length:2]
+ #define LFDATA [NSData dataWithBytes:"\x0A" length:1]

Conclusion

Finally, the KDE Connect iOS can find the other device and establish connections to them:

On the contrary, the others cannot find KDE Connect iOS client out yet. That’s because the new version requires TLS/SSL after a TCP connection. This will get fixed in the next post.

Good luck to myself!