{"id":1854,"date":"2015-05-06T15:00:20","date_gmt":"2015-05-06T05:00:20","guid":{"rendered":"http:\/\/www.malcolmgroves.com\/blog\/?p=1854"},"modified":"2015-06-02T13:24:40","modified_gmt":"2015-06-02T03:24:40","slug":"app-tethering-discovery-and-pairing","status":"publish","type":"post","link":"http:\/\/www.malcolmgroves.com\/blog\/?p=1854","title":{"rendered":"App Tethering : Discovery and Pairing"},"content":{"rendered":"<p><a href=\"http:\/\/www.malcolmgroves.com\/blog\/?p=1842\">In the previous post<\/a>, 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:<\/p>\n<ul>\n<li>Discovery \u2013 finding other apps, including whether you are looking for other apps over BlueTooth or IP Network<\/li>\n<li>Pairing \u2013 connecting to other apps, once you\u2019ve found them. This includes Authentication.<\/li>\n<li>Resources \u2013 sharing data with connected apps<\/li>\n<li>Actions \u2013 sharing Actions with connected apps<\/li>\n<\/ul>\n<p>Let\u2019s take each of these concepts and start looking in more detail.<!--more--> First, start your IDE (Delphi, C++Builder or AppMethod, doesn&#8217;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.<\/p>\n<p>Now, on App1&#8217;s main form, drop down a TTetheringManager component as well as a TTetheringAppProfile. Set the TTetheringAppProfile&#8217;s Manager property to point at the TTetheringManager component. Also set its Group property to TestGroup.<\/p>\n<p>So we can identify each of our apps, put the following code in your main form&#8217;s OnCreate event.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\" title=\"Delphi\">procedure TForm3.FormCreate(Sender: TObject);\r\nbegin\r\n  Caption := Format('App1 : %s', [TetheringManager1.Identifier]);\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm1::FormCreate(TObject *Sender)\r\n{\r\n  Caption = Format(\"App1 : %s\", ARRAYOFCONST((TetheringManager1-&gt;Identifier)));\r\n}<\/pre>\n<p>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&#8217;s OnCreate event (although when you set the caption, change the string to App2 instead of App1).<\/p>\n<p>Both our apps are now configured to use App Tethering, however one of them needs to initiate the discovery and pairing process. Let&#8217;s look at that now.<\/p>\n<p>If you look at the TTetheringManager component on our form, you\u2019ll note the AllowedAdapters property is set to Network, which means we\u2019ll be looking for other apps over IP. We\u2019re going to stick to IP for awhile, but we\u2019ll come back to Bluetooth later once we\u2019re comfortable with IP Tethering.<\/p>\n<div>When our app wants to find out if there are any other apps available to\u00a0potentially pair with, there are three ways we can go about it. We\u2019re going to start our explanation with the easiest way, but once we have more experience with App Tethering we\u2019ll come back and look at the other two in a future article<\/div>\n<div><\/div>\n<h2>Discovery using AutoConnect<\/h2>\n<p>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\u00a0 have \u201cmatching&#8221; TTetheringAppProfile components, and if so, attempts\u00a0to pair with them.<\/p>\n<div>What does it mean for an app to have a \u201cmatching&#8221; TTetheringAppProfile? Very simply, if it contains a TTetheringAppProfile component that has the same string value in its Group property as one of your TTetheringAppProfile components. That&#8217;s why I got you to\u00a0set your TetheringAppProfile.Group property to the same string in both App1 and App2.<\/div>\n<p>I realise that might be a little hard to follow, so in my mind I picture it like this:<\/p>\n<ul>\n<li>App1 starts up and shouts out across the network \u201cAny other TetheringManagers there?\u201d.<\/li>\n<li>Let\u2019s assume there is one other,\u00a0App2, so App2\u00a0answers \u201cHey, I\u2019m here\u201d.<\/li>\n<li>App1 then asks\u00a0\u201cWhat TetheringAppProfiles do you have?\u201d<\/li>\n<li>App2 replies with a list of TetheringAppProfiles.<\/li>\n<li>App1 then gets very excited and says \u201cYou have a TetheringAppProfile with a Group name of \u201cFooBar\u201d! I have one of them too! Let\u2019s be friends!!1!&#8221;<\/li>\n<li>App2 replies \u201cNot so fast buddy. Do you know the secret password?&#8221;<\/li>\n<li>App1 replies \u201cSure. \u2018The wingless dove protects its nest\u2019&#8221;<\/li>\n<li>App2 replies &#8220;ok, all good bestie&#8221;<\/li>\n<li>They then pair and live happily every after.<\/li>\n<\/ul>\n<div>As ridiculous as it may sound, that\u2019s not that far off what is actually happening at the network level as we\u2019ll see shortly\u00a0(minus the exclamation points perhaps).<\/div>\n<p>TTetheringManager.AutoConnect takes two optional parameters:<\/p>\n<ul>\n<li>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\u00a0don\u2019t seem to be finding all the remote managers, it could be that you need to lengthen the timeout.<\/li>\n<li>The second optional parameter is the target (or list of targets). For Network, this is either an IP address or IP subnet\u00a0that 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.<\/li>\n<\/ul>\n<div>Let&#8217;s add some more code to our examples and get them to the point where they can connect.<\/div>\n<div><\/div>\n<p>Go back to App1 and add a button and a label to your main form. Set the Button&#8217;s Text property to Connect, and position them like the picture below:<\/p>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1880 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.1.png\" alt=\"\" width=\"400\" height=\"143\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.1.png 400w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.1-300x107.png 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/div>\n<div><\/div>\n<p>In the button&#8217;s OnClick event, let&#8217;s put in the code to call AutoConnect with no parameters.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm3.Button1Click(Sender: TObject);\r\nbegin\r\n  TetheringManager1.AutoConnect;\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm1::Button1Click(TObject *Sender)\r\n{\r\n  TetheringManager1-&gt;AutoConnect();\r\n}<\/pre>\n<p>That&#8217;s actually enough to get us going, but I want to see some indication that we&#8217;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.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm3.TetheringManager1PairedToRemote(const Sender: TObject;\r\nconst AManagerInfo: TTetheringManagerInfo);\r\nbegin\r\n  Label1.Text := Format('Connected : %s %s',\r\n                        [AManagerInfo.ManagerIdentifier,\r\n                         AManagerInfo.ManagerName]);\r\nend;\r\n\r\n<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm1::TetheringManager1PairedToRemote(TObject * const Sender,\r\nconst TTetheringManagerInfo &amp;AManagerInfo)\r\n\r\n{\r\n  Label1-&gt;Text = Format(\"Connected : %s %s\",\r\n                        ARRAYOFCONST((AManagerInfo.ManagerIdentifier,\r\n                                      AManagerInfo.ManagerName)));\r\n}<\/pre>\n<p>In App2, let&#8217;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&#8217;s details, but in App2 put it in the TetheringManager1.OnPairedFromLocal event. I&#8217;ll explain shortly why the difference.<\/p>\n<p>If we didn&#8217;t care about Authentication, then we&#8217;d be done, however these days who doesn&#8217;t care about Authentication? So, in App2, set the Password property of the TTetheringManager component to whatever password you like. In my case I&#8217;m setting it to &#8216;The wingless dove protects its nest&#8217;.<\/p>\n<p>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.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm3.TetheringManager1RequestManagerPassword(const Sender: TObject;\r\nconst ARemoteIdentifier: string; var Password: string);\r\nbegin\r\n  Password := 'The wingless dove protects its nest';\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true  \">void __fastcall TForm1::TetheringManager1RequestManagerPassword(TObject * const Sender,\r\nconst UnicodeString ARemoteIdentifier, UnicodeString &amp;Password)\r\n\r\n{\r\n  Password = \"The wingless dove protects its nest\";\r\n}<\/pre>\n<p>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&#8217;m doing is referencing one of the event parameters, so I don&#8217;t have any issues around shared resources. However, let&#8217;s say you wanted to prompt the user to enter a password. <a href=\"http:\/\/www.malcolmgroves.com\/blog\/?p=1678\" target=\"_blank\">That would involve interaction with the UI thread, and that would need to be synchronised<\/a>.<\/p>\n<p>Now,\u00a0<span style=\"line-height: 1.6;\">let\u2019s see this in action. \u00a0For now we&#8217;ll just run them both on the local Windows machine,but they should work on different devices (iOS, Android, OSX\u00a0or 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. \u00a0Once that has started up, do the same on the App2.exe project.\u00a0<\/span><\/p>\n<p>Position them side-by-side on the screen. If you look\u00a0at 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.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1885\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.2.png\" alt=\"\" width=\"486\" height=\"341\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.2.png 486w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/tethering2.2-300x210.png 300w\" sizes=\"auto, (max-width: 486px) 100vw, 486px\" \/><\/p>\n<p>Now click the Connect button in App1. Providing both our apps are on the same subnet, they should find each other. You\u2019ll 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\u2019ll see App1&#8217;s label is displaying the same identifier as is in the Caption of App2, and vice versa.<\/p>\n<p>A quick aside. Some of you may have wondered why I said to start the apps using Run Without Debugging. There&#8217;s no issue debugging one of them, however if you start one using Run Without Debugging, then start the second using Run, you&#8217;ll see some exceptions at startup. These are not bugs, the exceptions are caught, it&#8217;s just that the debugger\u00a0surfaces them for you (which is why you don&#8217;t see them when running outside the debugger). The messages you&#8217;ll see come in pairs and look like:<\/p>\n<p>Project App1.exe raised exception class EIdSocketError with message &#8216;Socket Error 10048 Address already in use.&#8217;<\/p>\n<p class=\"p1\">followed by:<\/p>\n<p>Project App1.exe raised exception class EIdCouldNotBindSocket with message &#8216;Could not bind socket. Address and port are already in use.&#8217;<\/p>\n<p class=\"p1\">At startup your app is trying to find an available port to communicate on. Because we&#8217;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&#8217;t worry.<\/p>\n<div>OK, back to the connection process. Let\u2019s look at what just happened, both in terms of the events that fired and also what happened on the network:<\/div>\n<div><\/div>\n<table>\n<tbody>\n<tr>\n<td>\n<div><u><b>App 1<\/b><\/u><\/div>\n<\/td>\n<td>\n<div><u><b>Network<\/b><\/u><\/div>\n<\/td>\n<td>\n<div><u><b>App 2<\/b><\/u><\/div>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .AutoConnect;<\/div>\n<div><\/div>\n<\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/broadcast.png\" alt=\"UDP Broadcast\" width=\"30\" height=\"30\" \/>\u00a0Discover Managers (UDP Broadcast)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/>Manager Discovered (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1860\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/rightarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/> Request To Pair (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/>Password Request (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .OnRequestManagerPassword<\/div>\n<\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1860\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/rightarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/> Password (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/>Pair Accepted (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .OnPairedToRemote<\/div>\n<\/td>\n<td>\u00a0<img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1871\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/blank.png\" alt=\"\" width=\"40\" height=\"40\" \/><\/td>\n<td>\n<div>TetheringManager1 .OnPairedFromLocal<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .OnEndAutoConnect<\/div>\n<\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A few things to note in the flow above:<\/p>\n<ul>\n<li>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.<\/li>\n<li>All subsequent network traffic (in this example) is UDP Point-to-point. Once we get into Resources you&#8217;ll see sometimes App Tethering uses TCP instead, but so far it&#8217;s all UDP.<\/li>\n<li>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\u2019s TetheringManager1.Password property.<\/li>\n<li>The password is not sent in plain text. It is salted and hashed before being sent. Don&#8217;t believe me. Really, I&#8217;m serious, don&#8217;t believe me (or anyone else) when it comes to security. Run up <a href=\"https:\/\/www.wireshark.org\/\" target=\"_blank\">WireShark<\/a> and check it.<\/li>\n<li>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.<\/li>\n<\/ul>\n<div><\/div>\n<p>Let\u2019s look at what\u00a0happens if App 1 supplies an incorrect password in the OnRequestManagerPassword event (the differences are highlighted in blue):<\/p>\n<div><\/div>\n<table>\n<tbody>\n<tr>\n<td>\n<div><u><b>App 1<\/b><\/u><\/div>\n<\/td>\n<td>\n<div><u><b>Network<\/b><\/u><\/div>\n<\/td>\n<td>\n<div><u><b>App 2<\/b><\/u><\/div>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .AutoConnect;<\/div>\n<div><\/div>\n<\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/broadcast.png\" alt=\"UDP Broadcast\" width=\"30\" height=\"30\" \/>\u00a0Discover Managers (UDP Broadcast)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/>Manager Discovered (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1860\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/rightarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/> Request To Pair (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/>Password Request (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .OnRequestManagerPassword<\/div>\n<\/td>\n<td>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1860\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/rightarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/> Password (UDP Point to Point)<\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>\n<div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow wp-image-1859 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/leftarrow.png\" alt=\"UDP Point to Point\" width=\"40\" height=\"20\" \/><\/div>\n<\/div>\n<div><strong><span style=\"color: #3366ff;\">Auth Error (UDP Point to Point)<\/span><\/strong><\/div>\n<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>\n<div>\n<div><strong><span style=\"color: #3366ff;\">TetheringManager1 .OnAuthErrorFromRemote<\/span><\/strong><\/div>\n<\/div>\n<\/td>\n<td>\u00a0<img loading=\"lazy\" decoding=\"async\" class=\"alignleft noshadow size-full wp-image-1871\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/04\/blank.png\" alt=\"\" width=\"40\" height=\"40\" \/><\/td>\n<td>\n<div>\n<div><span style=\"color: #3366ff;\"><strong>TetheringManager1 .OnAuthErrorFromLocal<\/strong><\/span><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<div>TetheringManager1 .OnEndAutoConnect<\/div>\n<\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div><\/div>\n<p>We\u2019ll come back to the two other ways to discover other TetheringManagers on the network in a later post, but hopefully this\u00a0has started to clarify what is going on under the covers.<\/p>\n<p>If you think about it, we didn&#8217;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.<\/p>\n<p><a href=\"http:\/\/www.malcolmgroves.com\/blog\/?p=1891\">In the next post, we\u2019ll do something with this connection, specifically sharing Resources between the two apps.<\/a><\/p>\n<p>You can get the source for the sample apps at this stage of the series, in both <a href=\"https:\/\/github.com\/malcolmgroves\/tethering_series_cpp\/releases\/tag\/2.DiscoveryAndPairing\" target=\"_blank\">C++<\/a> and <a href=\"https:\/\/github.com\/malcolmgroves\/tethering_series_delphi\/releases\/tag\/2.DiscoveryAndPairing\" target=\"_blank\">Delphi<\/a>, but to really understand what is going on there is no substitute for building the apps yourself. They aren&#8217;t that big.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 \u2013 finding other apps, including whether you are looking for other apps over BlueTooth or IP Network Pairing \u2013 connecting to other apps, once [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[113,119],"tags":[48],"class_list":["post-1854","post","type-post","status-publish","format-standard","hentry","category-apptethering","category-distributed-systems","tag-embarcadero"],"_links":{"self":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1854","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1854"}],"version-history":[{"count":47,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1854\/revisions"}],"predecessor-version":[{"id":2022,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1854\/revisions\/2022"}],"wp:attachment":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1854"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1854"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1854"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}