Skip to content

Commit 0250987

Browse files
committed
[DO NOT REVIEW][epskc] demo version impl for epskc
1 parent 4b80986 commit 0250987

34 files changed

+1440
-695
lines changed

android/build-commissioner-libs.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ cmake -GNinja \
5454
-DBUILD_SHARED_LIBS=OFF \
5555
-DCMAKE_CXX_STANDARD=11 \
5656
-DCMAKE_CXX_STANDARD_REQUIRED=ON \
57-
-DCMAKE_BUILD_TYPE=Release \
57+
-DCMAKE_BUILD_TYPE=Debug \
5858
-DOT_COMM_ANDROID=ON \
5959
-DOT_COMM_JAVA_BINDING=ON \
6060
-DOT_COMM_APP=OFF \

android/openthread_commissioner/app/build.gradle

+6-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@ dependencies {
9797

9898
implementation fileTree(dir: "libs", include: ["*.jar"])
9999

100-
implementation 'com.google.guava:guava:31.1-jre'
101-
implementation 'androidx.appcompat:appcompat:1.2.0'
100+
// Fix Duplicate class
101+
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
102+
103+
implementation 'com.google.guava:guava:33.2.1-android'
104+
implementation 'androidx.appcompat:appcompat:1.6.1'
105+
implementation 'androidx.activity:activity:1.9.1'
102106
implementation "androidx.concurrent:concurrent-futures:1.1.0"
103107
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
104108
implementation 'androidx.navigation:navigation-fragment:2.3.0'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.openthread.commissioner.app;
2+
3+
import android.content.Context;
4+
import android.os.Handler;
5+
import android.os.Looper;
6+
import android.util.ArrayMap;
7+
import android.view.LayoutInflater;
8+
import android.view.View;
9+
import android.view.ViewGroup;
10+
import android.widget.BaseAdapter;
11+
import android.widget.TextView;
12+
import androidx.annotation.Nullable;
13+
import io.openthread.commissioner.app.BorderAgentDiscoverer.BorderAgentListener;
14+
import java.net.Inet6Address;
15+
import java.util.Map;
16+
import java.util.Vector;
17+
18+
public class BorderAgentAdapter extends BaseAdapter implements BorderAgentListener {
19+
20+
private final Vector<BorderAgentInfo> borderAgentServices = new Vector<>();
21+
private final Map<String, BorderAgentInfo> epskcServices = new ArrayMap<>();
22+
23+
private final LayoutInflater inflater;
24+
25+
BorderAgentAdapter(Context context) {
26+
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
27+
}
28+
29+
public void addBorderAgent(BorderAgentInfo serviceInfo) {
30+
if (serviceInfo.isEpskcService) {
31+
epskcServices.put(serviceInfo.instanceName, serviceInfo);
32+
notifyDataSetChanged();
33+
return;
34+
}
35+
36+
boolean hasExistingBorderRouter = false;
37+
for (int i = 0; i < borderAgentServices.size(); i++) {
38+
if (borderAgentServices.get(i).instanceName.equals(serviceInfo.instanceName)) {
39+
borderAgentServices.set(i, serviceInfo);
40+
hasExistingBorderRouter = true;
41+
}
42+
}
43+
44+
if (!hasExistingBorderRouter) {
45+
borderAgentServices.add(serviceInfo);
46+
}
47+
48+
notifyDataSetChanged();
49+
}
50+
51+
public void removeBorderAgent(boolean isEpskcService, String instanceName) {
52+
if (isEpskcService) {
53+
epskcServices.remove(instanceName);
54+
} else {
55+
borderAgentServices.removeIf(serviceInfo -> serviceInfo.instanceName.equals(instanceName));
56+
}
57+
notifyDataSetChanged();
58+
}
59+
60+
public void clear() {
61+
borderAgentServices.clear();
62+
epskcServices.clear();
63+
notifyDataSetChanged();
64+
}
65+
66+
@Override
67+
public int getCount() {
68+
return borderAgentServices.size();
69+
}
70+
71+
@Override
72+
public Object getItem(int position) {
73+
return borderAgentServices.get(position);
74+
}
75+
76+
@Override
77+
public long getItemId(int position) {
78+
return position;
79+
}
80+
81+
@Override
82+
public View getView(int position, View convertView, ViewGroup container) {
83+
if (convertView == null) {
84+
convertView = inflater.inflate(R.layout.border_agent_list_item, container, false);
85+
}
86+
87+
BorderAgentInfo borderAgentInfo = borderAgentServices.get(position);
88+
89+
TextView instanceNameText = convertView.findViewById(R.id.border_agent_instance_name);
90+
instanceNameText.setText(borderAgentInfo.instanceName);
91+
92+
TextView vendorNameText = convertView.findViewById(R.id.border_agent_vendor_name);
93+
vendorNameText.setText(borderAgentInfo.vendorName);
94+
95+
TextView modelNameText = convertView.findViewById(R.id.border_agent_model_name);
96+
modelNameText.setText(borderAgentInfo.modelName);
97+
98+
TextView adminModeText = convertView.findViewById(R.id.border_agent_admin_mode);
99+
adminModeText.setText(
100+
"In Administration Mode: " + (inAdministrationMode(borderAgentInfo) ? "YES" : "NO"));
101+
102+
TextView borderAgentIpAddrText = convertView.findViewById(R.id.border_agent_ip_addr);
103+
int port =
104+
inAdministrationMode(borderAgentInfo)
105+
? getEpskcService(borderAgentInfo).port
106+
: borderAgentInfo.port;
107+
String socketAddress;
108+
if (borderAgentInfo.host instanceof Inet6Address) {
109+
socketAddress = "[" + borderAgentInfo.host.getHostAddress() + "]:" + port;
110+
} else {
111+
socketAddress = borderAgentInfo.host.getHostAddress() + ":" + port;
112+
}
113+
borderAgentIpAddrText.setText(socketAddress);
114+
return convertView;
115+
}
116+
117+
private boolean inAdministrationMode(BorderAgentInfo borderAgentInfo) {
118+
return epskcServices.containsKey(borderAgentInfo.instanceName);
119+
}
120+
121+
@Override
122+
public void onBorderAgentFound(BorderAgentInfo borderAgentInfo) {
123+
new Handler(Looper.getMainLooper()).post(() -> addBorderAgent(borderAgentInfo));
124+
}
125+
126+
@Override
127+
public void onBorderAgentLost(boolean isEpskcService, String instanceName) {
128+
new Handler(Looper.getMainLooper()).post(() -> removeBorderAgent(isEpskcService, instanceName));
129+
}
130+
131+
/**
132+
* Returns the _meshcop-e._udp service which is associated with the given _meshcop._udp service,
133+
* or {@code null} if such service doesn't exist.
134+
*/
135+
@Nullable
136+
private BorderAgentInfo getEpskcService(BorderAgentInfo meshcopService) {
137+
return epskcServices.get(meshcopService.instanceName);
138+
}
139+
}

android/openthread_commissioner/app/src/main/java/io/openthread/commissioner/app/BorderAgentDiscoverer.java

+72-33
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@
3030

3131
import android.Manifest.permission;
3232
import android.content.Context;
33+
import android.net.ConnectivityManager;
34+
import android.net.Network;
3335
import android.net.nsd.NsdManager;
3436
import android.net.nsd.NsdServiceInfo;
3537
import android.net.wifi.WifiManager;
3638
import android.util.Log;
3739
import androidx.annotation.Nullable;
3840
import androidx.annotation.RequiresPermission;
41+
import com.google.common.net.InetAddresses;
42+
import java.net.Inet6Address;
43+
import java.net.InetAddress;
3944
import java.util.Map;
4045
import java.util.concurrent.ArrayBlockingQueue;
4146
import java.util.concurrent.BlockingQueue;
@@ -47,35 +52,43 @@ public class BorderAgentDiscoverer implements NsdManager.DiscoveryListener {
4752

4853
private static final String TAG = BorderAgentDiscoverer.class.getSimpleName();
4954

50-
private static final String SERVICE_TYPE = "_meshcop._udp";
55+
public static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
56+
public static final String MESHCOP_E_SERVICE_TYPE = "_meshcop-e._udp";
5157
private static final String KEY_ID = "id";
5258
private static final String KEY_NETWORK_NAME = "nn";
5359
private static final String KEY_EXTENDED_PAN_ID = "xp";
60+
private static final String KEY_VENDOR_NAME = "vn";
61+
private static final String KEY_MODEL_NAME = "mn";
5462

55-
private WifiManager.MulticastLock wifiMulticastLock;
56-
private NsdManager nsdManager;
57-
private BorderAgentListener borderAgentListener;
63+
private final WifiManager.MulticastLock wifiMulticastLock;
64+
private final NsdManager nsdManager;
65+
private final ConnectivityManager connManager;
66+
private final String serviceType;
67+
private final BorderAgentListener borderAgentListener;
5868

5969
private ExecutorService executor = Executors.newSingleThreadExecutor();
60-
private BlockingQueue<NsdServiceInfo> unresolvedServices = new ArrayBlockingQueue<>(256);
61-
private AtomicBoolean isResolvingService = new AtomicBoolean(false);
70+
private final BlockingQueue<NsdServiceInfo> unresolvedServices = new ArrayBlockingQueue<>(256);
71+
private final AtomicBoolean isResolvingService = new AtomicBoolean(false);
6272

6373
private boolean isScanning = false;
6474

6575
public interface BorderAgentListener {
6676

6777
void onBorderAgentFound(BorderAgentInfo borderAgentInfo);
6878

69-
void onBorderAgentLost(byte[] id);
79+
default void onBorderAgentLost(boolean isEpskcService, String instanceName) {}
7080
}
7181

7282
@RequiresPermission(permission.INTERNET)
73-
public BorderAgentDiscoverer(Context context, BorderAgentListener borderAgentListener) {
83+
public BorderAgentDiscoverer(
84+
Context context, String serviceType, BorderAgentListener borderAgentListener) {
7485
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
7586
wifiMulticastLock = wifi.createMulticastLock("multicastLock");
7687

77-
nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
88+
nsdManager = context.getSystemService(NsdManager.class);
89+
connManager = context.getSystemService(ConnectivityManager.class);
7890

91+
this.serviceType = serviceType;
7992
this.borderAgentListener = borderAgentListener;
8093
}
8194

@@ -91,8 +104,8 @@ public void start() {
91104
wifiMulticastLock.acquire();
92105

93106
startResolver();
94-
nsdManager.discoverServices(
95-
BorderAgentDiscoverer.SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, this);
107+
108+
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, this);
96109
}
97110

98111
private void startResolver() {
@@ -112,7 +125,7 @@ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
112125
public void onServiceResolved(NsdServiceInfo serviceInfo) {
113126
BorderAgentInfo borderAgent = getBorderAgentInfo(serviceInfo);
114127
if (borderAgent != null) {
115-
Log.d(TAG, "successfully resolved service: " + serviceInfo.toString());
128+
Log.d(TAG, "successfully resolved service: " + serviceInfo);
116129
Log.d(
117130
TAG,
118131
"successfully resolved service: " + serviceInfo.getHost().getCanonicalHostName());
@@ -167,7 +180,7 @@ public void stop() {
167180

168181
@Override
169182
public void onDiscoveryStarted(String serviceType) {
170-
Log.d(TAG, "start discovering Border Agent");
183+
Log.d(TAG, "start discovering Border Agent: " + serviceType);
171184
}
172185

173186
@Override
@@ -184,11 +197,9 @@ public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
184197

185198
@Override
186199
public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
187-
byte[] id = getBorderAgentId(nsdServiceInfo);
188-
if (id != null) {
189-
Log.d(TAG, "a Border Agent service is gone: " + nsdServiceInfo.getServiceName());
190-
borderAgentListener.onBorderAgentLost(id);
191-
}
200+
Log.d(TAG, "a Border Agent service is gone: " + nsdServiceInfo.getServiceName());
201+
borderAgentListener.onBorderAgentLost(
202+
serviceType.equals(MESHCOP_E_SERVICE_TYPE), nsdServiceInfo.getServiceName());
192203
}
193204

194205
@Override
@@ -204,26 +215,54 @@ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
204215
@Nullable
205216
private BorderAgentInfo getBorderAgentInfo(NsdServiceInfo serviceInfo) {
206217
Map<String, byte[]> attrs = serviceInfo.getAttributes();
207-
byte[] id = getBorderAgentId(serviceInfo);
208-
209-
if (!attrs.containsKey(KEY_NETWORK_NAME) || !attrs.containsKey(KEY_EXTENDED_PAN_ID)) {
210-
return null;
211-
}
212-
213218
return new BorderAgentInfo(
214-
id,
215-
new String(attrs.get(KEY_NETWORK_NAME)),
219+
serviceType.equals(MESHCOP_E_SERVICE_TYPE),
220+
serviceInfo.getServiceName(),
221+
handleNsdServiceAddress(serviceInfo.getHost()),
222+
serviceInfo.getPort(),
223+
attrs.get(KEY_ID),
224+
getStringAttribute(attrs, KEY_NETWORK_NAME),
216225
attrs.get(KEY_EXTENDED_PAN_ID),
217-
serviceInfo.getHost(),
218-
serviceInfo.getPort());
226+
getStringAttribute(attrs, KEY_VENDOR_NAME),
227+
getStringAttribute(attrs, KEY_MODEL_NAME));
219228
}
220229

221230
@Nullable
222-
private byte[] getBorderAgentId(NsdServiceInfo serviceInfo) {
223-
Map<String, byte[]> attrs = serviceInfo.getAttributes();
224-
if (attrs.containsKey(KEY_ID)) {
225-
return attrs.get(KEY_ID).clone();
231+
private static String getStringAttribute(Map<String, byte[]> attributes, String key) {
232+
byte[] value = attributes.get(key);
233+
if (value == null) {
234+
return null;
235+
}
236+
return new String(value);
237+
}
238+
239+
/**
240+
* Properly handles the {@link InetAddress} within a discovered {@link NsdServiceInfo}.
241+
*
242+
* <p>For example, adds the scope ID to an IPv6 link-local address to make is usable.
243+
*/
244+
private InetAddress handleNsdServiceAddress(InetAddress address) {
245+
if (!(address instanceof Inet6Address)) {
246+
return address;
226247
}
227-
return null;
248+
249+
Inet6Address address6 = (Inet6Address) address;
250+
251+
// Sets the scope ID for IPv6 link-local address if it's missing. This can happen before
252+
// Android U
253+
if (address6.isLinkLocalAddress() && address6.getScopeId() == 0) {
254+
// Assume the mDNS service is discovered on the current active default network
255+
Network network = connManager.getActiveNetwork();
256+
if (network == null) {
257+
return address6;
258+
}
259+
String interfaceName = connManager.getLinkProperties(network).getInterfaceName();
260+
if (interfaceName == null) {
261+
return address6;
262+
}
263+
return InetAddresses.forString(address6.getHostAddress() + "%" + interfaceName);
264+
}
265+
266+
return address6;
228267
}
229268
}

0 commit comments

Comments
 (0)