In the previous post, we covered an overview of what App Tethering is and how some customers have used it. We also covered the four conceptual areas of App Tethering:
- Discovery – finding other apps, including whether you are looking for other apps over BlueTooth or IP Network
- Pairing – connecting to other apps, once you’ve found them. This includes Authentication.
- Resources – sharing data with connected apps
- Actions – sharing Actions with connected apps
Let’s take each of these concepts and start looking in more detail. First, start your IDE (Delphi, C++Builder or AppMethod, doesn’t matter). Create two empty multi-device applications and save their projects as App1 and App2 respectively. As already mentioned, App Tethering works exactly the same way in VCL, but creating multi-device apps will give us some flexibility during testing to run them up on Windows, OSX, Android or iOS.
Now, on App1’s main form, drop down a TTetheringManager component as well as a TTetheringAppProfile. Set the TTetheringAppProfile’s Manager property to point at the TTetheringManager component. Also set its Group property to TestGroup.
So we can identify each of our apps, put the following code in your main form’s OnCreate event.
For Delphi:
procedure TForm3.FormCreate(Sender: TObject); begin Caption := Format('App1 : %s', [TetheringManager1.Identifier]); end;
For C++:
void __fastcall TForm1::FormCreate(TObject *Sender) { Caption = Format("App1 : %s", ARRAYOFCONST((TetheringManager1->Identifier))); }
Do all of that again in your second multi-device app (App2), including dropping down and hooking up the components, setting the Group name and putting the code in your form’s OnCreate event (although when you set the caption, change the string to App2 instead of App1).
Both our apps are now configured to use App Tethering, however one of them needs to initiate the discovery and pairing process. Let’s look at that now.
If you look at the TTetheringManager component on our form, you’ll note the AllowedAdapters property is set to Network, which means we’ll be looking for other apps over IP. We’re going to stick to IP for awhile, but we’ll come back to Bluetooth later once we’re comfortable with IP Tethering.
Discovery using AutoConnect
The easiest way to discover other apps to pair with is to call TTetheringManager.AutoConnect. This will look out on the network for other apps which contain TetheringManagers. When it finds one or more, it interrogates them to see if they have “matching” TTetheringAppProfile components, and if so, attempts to pair with them.
I realise that might be a little hard to follow, so in my mind I picture it like this:
- App1 starts up and shouts out across the network “Any other TetheringManagers there?”.
- Let’s assume there is one other, App2, so App2 answers “Hey, I’m here”.
- App1 then asks “What TetheringAppProfiles do you have?”
- App2 replies with a list of TetheringAppProfiles.
- App1 then gets very excited and says “You have a TetheringAppProfile with a Group name of “FooBar”! I have one of them too! Let’s be friends!!1!”
- App2 replies “Not so fast buddy. Do you know the secret password?”
- App1 replies “Sure. ‘The wingless dove protects its nest’”
- App2 replies “ok, all good bestie”
- They then pair and live happily every after.
TTetheringManager.AutoConnect takes two optional parameters:
- The first is a timeout, expressed in milliseconds. This is the length of time it will wait for a response to the initial search for other TTetheringManagers and defaults to around 2 seconds. If you don’t seem to be finding all the remote managers, it could be that you need to lengthen the timeout.
- The second optional parameter is the target (or list of targets). For Network, this is either an IP address or IP subnet that you want to discover managers on. If you specify nothing, it will default to the same subnet your app is on. For Bluetooth, this is the name of the specific BlueTooth device (or MAC Address) you want to look for managers on.
Go back to App1 and add a button and a label to your main form. Set the Button’s Text property to Connect, and position them like the picture below:
In the button’s OnClick event, let’s put in the code to call AutoConnect with no parameters.
For Delphi:
procedure TForm3.Button1Click(Sender: TObject); begin TetheringManager1.AutoConnect; end;
For C++:
void __fastcall TForm1::Button1Click(TObject *Sender) { TetheringManager1->AutoConnect(); }
That’s actually enough to get us going, but I want to see some indication that we’ve connected successfully. I want to display the Identifier of the remote TTetheringManager in the label on our form once we connect. Inside App1, put the following code in the TTetheringManager1.OnPairedToRemote event.
For Delphi:
procedure TForm3.TetheringManager1PairedToRemote(const Sender: TObject; const AManagerInfo: TTetheringManagerInfo); begin Label1.Text := Format('Connected : %s %s', [AManagerInfo.ManagerIdentifier, AManagerInfo.ManagerName]); end;
For C++:
void __fastcall TForm1::TetheringManager1PairedToRemote(TObject * const Sender, const TTetheringManagerInfo &AManagerInfo) { Label1->Text = Format("Connected : %s %s", ARRAYOFCONST((AManagerInfo.ManagerIdentifier, AManagerInfo.ManagerName))); }
In App2, let’s add a label as well (no button needed, as in this case App2 will not be initiating the discovery process) and put the same code as we just put in App1 to display the remote TetheringManager’s details, but in App2 put it in the TetheringManager1.OnPairedFromLocal event. I’ll explain shortly why the difference.
If we didn’t care about Authentication, then we’d be done, however these days who doesn’t care about Authentication? So, in App2, set the Password property of the TTetheringManager component to whatever password you like. In my case I’m setting it to ‘The wingless dove protects its nest’.
Next, we need to tell App1 how to provide the password to App2 when requested. So back in App1, put the following code in its TetheringManager1.OnRequestManagerPassword event.
For Delphi:
procedure TForm3.TetheringManager1RequestManagerPassword(const Sender: TObject; const ARemoteIdentifier: string; var Password: string); begin Password := 'The wingless dove protects its nest'; end;
For C++:
void __fastcall TForm1::TetheringManager1RequestManagerPassword(TObject * const Sender, const UnicodeString ARemoteIdentifier, UnicodeString &Password) { Password = "The wingless dove protects its nest"; }
One important thing to realise about the OnRequestManagerPassword event is that it is called in the context of a background thread (the background thread that is doing all the network activity). In this case, all I’m doing is referencing one of the event parameters, so I don’t have any issues around shared resources. However, let’s say you wanted to prompt the user to enter a password. That would involve interaction with the UI thread, and that would need to be synchronised.
Now, let’s see this in action. For now we’ll just run them both on the local Windows machine,but they should work on different devices (iOS, Android, OSX or Windows) provided they are on the same subnet. Right-click on the App1.exe project in the IDE and select Run Without Debugging. You might get prompted by Windows Firewall the first time you run it to allow App1 access to the network. Once that has started up, do the same on the App2.exe project.
Position them side-by-side on the screen. If you look at the screenshot below, you can see App1 has an Identifier starting with {0E5E3725- and App2 has one starting with {B7039459-. Your Identifiers will be different of course.
Now click the Connect button in App1. Providing both our apps are on the same subnet, they should find each other. You’ll know when this has happened as your code to update the label in both App1 and App2 will run and the Identifier of the remote TetheringManager will be displayed in it. In the screenshot above, you’ll see App1’s label is displaying the same identifier as is in the Caption of App2, and vice versa.
A quick aside. Some of you may have wondered why I said to start the apps using Run Without Debugging. There’s no issue debugging one of them, however if you start one using Run Without Debugging, then start the second using Run, you’ll see some exceptions at startup. These are not bugs, the exceptions are caught, it’s just that the debugger surfaces them for you (which is why you don’t see them when running outside the debugger). The messages you’ll see come in pairs and look like:
Project App1.exe raised exception class EIdSocketError with message ‘Socket Error 10048 Address already in use.’
followed by:
Project App1.exe raised exception class EIdCouldNotBindSocket with message ‘Could not bind socket. Address and port are already in use.’
At startup your app is trying to find an available port to communicate on. Because we’re running them both on the same machine, when we start the second app the first port it tries has already been taken by the first app. Just click Continue to get past them and don’t worry.
App 1
|
Network
|
App 2
|
TetheringManager1 .AutoConnect;
|
Discover Managers (UDP Broadcast)
|
|
Manager Discovered (UDP Point to Point)
|
||
Request To Pair (UDP Point to Point)
|
||
Password Request (UDP Point to Point)
|
||
TetheringManager1 .OnRequestManagerPassword
|
Password (UDP Point to Point)
|
|
Pair Accepted (UDP Point to Point)
|
||
TetheringManager1 .OnPairedToRemote
|
TetheringManager1 .OnPairedFromLocal
|
|
TetheringManager1 .OnEndAutoConnect
|
A few things to note in the flow above:
- The first transmission on the network is a UDP Broadcast labelled Discover Managers. This is why AutoConnect by default requires apps to be on the same subnet. As already mentioned, you can optionally provide a different subnet or IP address.
- All subsequent network traffic (in this example) is UDP Point-to-point. Once we get into Resources you’ll see sometimes App Tethering uses TCP instead, but so far it’s all UDP.
- App2 is requesting the password. App1 is supplying it and you get a chance to provide it in the TetheringManager1.OnRequestManagerPassword event. App2 will compare it to the value stored in it’s TetheringManager1.Password property.
- The password is not sent in plain text. It is salted and hashed before being sent. Don’t believe me. Really, I’m serious, don’t believe me (or anyone else) when it comes to security. Run up WireShark and check it.
- The OnPairedToRemote and OnPairedFromLocal names can cause a bit of confusion. Even though App1 initiated the pairing, App2 fires its OnPairedFromLocal event because it is the one that accepted the pairing.
Let’s look at what happens if App 1 supplies an incorrect password in the OnRequestManagerPassword event (the differences are highlighted in blue):
App 1
|
Network
|
App 2
|
TetheringManager1 .AutoConnect;
|
Discover Managers (UDP Broadcast)
|
|
Manager Discovered (UDP Point to Point)
|
||
Request To Pair (UDP Point to Point)
|
||
Password Request (UDP Point to Point)
|
||
TetheringManager1 .OnRequestManagerPassword
|
Password (UDP Point to Point)
|
|
Auth Error (UDP Point to Point)
|
||
TetheringManager1 .OnAuthErrorFromRemote
|
TetheringManager1 .OnAuthErrorFromLocal
|
|
TetheringManager1 .OnEndAutoConnect
|
We’ll come back to the two other ways to discover other TetheringManagers on the network in a later post, but hopefully this has started to clarify what is going on under the covers.
If you think about it, we didn’t do very much to get two apps finding and connecting with each other. Try starting these up on different devices (mobile or desktop). Provided they are all on the same subnet, it should work exactly the same way.
You can get the source for the sample apps at this stage of the series, in both C++ and Delphi, but to really understand what is going on there is no substitute for building the apps yourself. They aren’t that big.
2 Comments
[…] That will do us for this first post. Hopefully you now understand better what App Tethering is for. Next post, we’ll start to explore how to actually use it, by looking at the process of discovering…. […]
[…] In the last post, we went through one method of discovery, AutoConnect, and then built a couple of a… […]