1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.content.Context; 20 import android.content.ContentResolver; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.database.ContentObserver; 24 import android.net.Uri; 25 import android.net.nsd.NsdServiceInfo; 26 import android.net.nsd.DnsSdTxtRecord; 27 import android.net.nsd.INsdManager; 28 import android.net.nsd.NsdManager; 29 import android.os.Binder; 30 import android.os.HandlerThread; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.Messenger; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.util.Base64; 37 import android.util.Slog; 38 import android.util.SparseArray; 39 import android.util.SparseIntArray; 40 41 import java.io.FileDescriptor; 42 import java.io.PrintWriter; 43 import java.net.InetAddress; 44 import java.util.Arrays; 45 import java.util.HashMap; 46 import java.util.concurrent.CountDownLatch; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.util.AsyncChannel; 50 import com.android.internal.util.DumpUtils; 51 import com.android.internal.util.Protocol; 52 import com.android.internal.util.State; 53 import com.android.internal.util.StateMachine; 54 55 /** 56 * Network Service Discovery Service handles remote service discovery operation requests by 57 * implementing the INsdManager interface. 58 * 59 * @hide 60 */ 61 public class NsdService extends INsdManager.Stub { 62 private static final String TAG = "NsdService"; 63 private static final String MDNS_TAG = "mDnsConnector"; 64 65 private static final boolean DBG = true; 66 67 private final Context mContext; 68 private final NsdSettings mNsdSettings; 69 private final NsdStateMachine mNsdStateMachine; 70 private final DaemonConnection mDaemon; 71 private final NativeCallbackReceiver mDaemonCallback; 72 73 /** 74 * Clients receiving asynchronous messages 75 */ 76 private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>(); 77 78 /* A map from unique id to client info */ 79 private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>(); 80 81 private final AsyncChannel mReplyChannel = new AsyncChannel(); 82 83 private static final int INVALID_ID = 0; 84 private int mUniqueId = 1; 85 86 private class NsdStateMachine extends StateMachine { 87 88 private final DefaultState mDefaultState = new DefaultState(); 89 private final DisabledState mDisabledState = new DisabledState(); 90 private final EnabledState mEnabledState = new EnabledState(); 91 92 @Override 93 protected String getWhatToString(int what) { 94 return NsdManager.nameOf(what); 95 } 96 97 /** 98 * Observes the NSD on/off setting, and takes action when changed. 99 */ 100 private void registerForNsdSetting() { 101 final ContentObserver contentObserver = new ContentObserver(this.getHandler()) { 102 @Override 103 public void onChange(boolean selfChange) { 104 notifyEnabled(isNsdEnabled()); 105 } 106 }; 107 108 final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON); 109 mNsdSettings.registerContentObserver(uri, contentObserver); 110 } 111 112 NsdStateMachine(String name, Handler handler) { 113 super(name, handler); 114 addState(mDefaultState); 115 addState(mDisabledState, mDefaultState); 116 addState(mEnabledState, mDefaultState); 117 State initialState = isNsdEnabled() ? mEnabledState : mDisabledState; 118 setInitialState(initialState); 119 setLogRecSize(25); 120 registerForNsdSetting(); 121 } 122 123 class DefaultState extends State { 124 @Override 125 public boolean processMessage(Message msg) { 126 ClientInfo cInfo = null; 127 switch (msg.what) { 128 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 129 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 130 AsyncChannel c = (AsyncChannel) msg.obj; 131 if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); 132 c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); 133 cInfo = new ClientInfo(c, msg.replyTo); 134 mClients.put(msg.replyTo, cInfo); 135 } else { 136 Slog.e(TAG, "Client connection failure, error=" + msg.arg1); 137 } 138 break; 139 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 140 switch (msg.arg1) { 141 case AsyncChannel.STATUS_SEND_UNSUCCESSFUL: 142 Slog.e(TAG, "Send failed, client connection lost"); 143 break; 144 case AsyncChannel.STATUS_REMOTE_DISCONNECTION: 145 if (DBG) Slog.d(TAG, "Client disconnected"); 146 break; 147 default: 148 if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); 149 break; 150 } 151 cInfo = mClients.get(msg.replyTo); 152 if (cInfo != null) { 153 cInfo.expungeAllRequests(); 154 mClients.remove(msg.replyTo); 155 } 156 //Last client 157 if (mClients.size() == 0) { 158 mDaemon.stop(); 159 } 160 break; 161 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: 162 AsyncChannel ac = new AsyncChannel(); 163 ac.connect(mContext, getHandler(), msg.replyTo); 164 break; 165 case NsdManager.DISCOVER_SERVICES: 166 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, 167 NsdManager.FAILURE_INTERNAL_ERROR); 168 break; 169 case NsdManager.STOP_DISCOVERY: 170 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, 171 NsdManager.FAILURE_INTERNAL_ERROR); 172 break; 173 case NsdManager.REGISTER_SERVICE: 174 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, 175 NsdManager.FAILURE_INTERNAL_ERROR); 176 break; 177 case NsdManager.UNREGISTER_SERVICE: 178 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, 179 NsdManager.FAILURE_INTERNAL_ERROR); 180 break; 181 case NsdManager.RESOLVE_SERVICE: 182 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, 183 NsdManager.FAILURE_INTERNAL_ERROR); 184 break; 185 case NsdManager.NATIVE_DAEMON_EVENT: 186 default: 187 Slog.e(TAG, "Unhandled " + msg); 188 return NOT_HANDLED; 189 } 190 return HANDLED; 191 } 192 } 193 194 class DisabledState extends State { 195 @Override 196 public void enter() { 197 sendNsdStateChangeBroadcast(false); 198 } 199 200 @Override 201 public boolean processMessage(Message msg) { 202 switch (msg.what) { 203 case NsdManager.ENABLE: 204 transitionTo(mEnabledState); 205 break; 206 default: 207 return NOT_HANDLED; 208 } 209 return HANDLED; 210 } 211 } 212 213 class EnabledState extends State { 214 @Override 215 public void enter() { 216 sendNsdStateChangeBroadcast(true); 217 if (mClients.size() > 0) { 218 mDaemon.start(); 219 } 220 } 221 222 @Override 223 public void exit() { 224 if (mClients.size() > 0) { 225 mDaemon.stop(); 226 } 227 } 228 229 private boolean requestLimitReached(ClientInfo clientInfo) { 230 if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) { 231 if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo); 232 return true; 233 } 234 return false; 235 } 236 237 private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) { 238 clientInfo.mClientIds.put(clientId, globalId); 239 clientInfo.mClientRequests.put(clientId, what); 240 mIdToClientInfoMap.put(globalId, clientInfo); 241 } 242 243 private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) { 244 clientInfo.mClientIds.delete(clientId); 245 clientInfo.mClientRequests.delete(clientId); 246 mIdToClientInfoMap.remove(globalId); 247 } 248 249 @Override 250 public boolean processMessage(Message msg) { 251 ClientInfo clientInfo; 252 NsdServiceInfo servInfo; 253 int id; 254 switch (msg.what) { 255 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 256 //First client 257 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL && 258 mClients.size() == 0) { 259 mDaemon.start(); 260 } 261 return NOT_HANDLED; 262 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 263 return NOT_HANDLED; 264 case NsdManager.DISABLE: 265 //TODO: cleanup clients 266 transitionTo(mDisabledState); 267 break; 268 case NsdManager.DISCOVER_SERVICES: 269 if (DBG) Slog.d(TAG, "Discover services"); 270 servInfo = (NsdServiceInfo) msg.obj; 271 clientInfo = mClients.get(msg.replyTo); 272 273 if (requestLimitReached(clientInfo)) { 274 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, 275 NsdManager.FAILURE_MAX_LIMIT); 276 break; 277 } 278 279 id = getUniqueId(); 280 if (discoverServices(id, servInfo.getServiceType())) { 281 if (DBG) { 282 Slog.d(TAG, "Discover " + msg.arg2 + " " + id + 283 servInfo.getServiceType()); 284 } 285 storeRequestMap(msg.arg2, id, clientInfo, msg.what); 286 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo); 287 } else { 288 stopServiceDiscovery(id); 289 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, 290 NsdManager.FAILURE_INTERNAL_ERROR); 291 } 292 break; 293 case NsdManager.STOP_DISCOVERY: 294 if (DBG) Slog.d(TAG, "Stop service discovery"); 295 clientInfo = mClients.get(msg.replyTo); 296 297 try { 298 id = clientInfo.mClientIds.get(msg.arg2); 299 } catch (NullPointerException e) { 300 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, 301 NsdManager.FAILURE_INTERNAL_ERROR); 302 break; 303 } 304 removeRequestMap(msg.arg2, id, clientInfo); 305 if (stopServiceDiscovery(id)) { 306 replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED); 307 } else { 308 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, 309 NsdManager.FAILURE_INTERNAL_ERROR); 310 } 311 break; 312 case NsdManager.REGISTER_SERVICE: 313 if (DBG) Slog.d(TAG, "Register service"); 314 clientInfo = mClients.get(msg.replyTo); 315 if (requestLimitReached(clientInfo)) { 316 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, 317 NsdManager.FAILURE_MAX_LIMIT); 318 break; 319 } 320 321 id = getUniqueId(); 322 if (registerService(id, (NsdServiceInfo) msg.obj)) { 323 if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id); 324 storeRequestMap(msg.arg2, id, clientInfo, msg.what); 325 // Return success after mDns reports success 326 } else { 327 unregisterService(id); 328 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, 329 NsdManager.FAILURE_INTERNAL_ERROR); 330 } 331 break; 332 case NsdManager.UNREGISTER_SERVICE: 333 if (DBG) Slog.d(TAG, "unregister service"); 334 clientInfo = mClients.get(msg.replyTo); 335 try { 336 id = clientInfo.mClientIds.get(msg.arg2); 337 } catch (NullPointerException e) { 338 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, 339 NsdManager.FAILURE_INTERNAL_ERROR); 340 break; 341 } 342 removeRequestMap(msg.arg2, id, clientInfo); 343 if (unregisterService(id)) { 344 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED); 345 } else { 346 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, 347 NsdManager.FAILURE_INTERNAL_ERROR); 348 } 349 break; 350 case NsdManager.RESOLVE_SERVICE: 351 if (DBG) Slog.d(TAG, "Resolve service"); 352 servInfo = (NsdServiceInfo) msg.obj; 353 clientInfo = mClients.get(msg.replyTo); 354 355 356 if (clientInfo.mResolvedService != null) { 357 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, 358 NsdManager.FAILURE_ALREADY_ACTIVE); 359 break; 360 } 361 362 id = getUniqueId(); 363 if (resolveService(id, servInfo)) { 364 clientInfo.mResolvedService = new NsdServiceInfo(); 365 storeRequestMap(msg.arg2, id, clientInfo, msg.what); 366 } else { 367 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, 368 NsdManager.FAILURE_INTERNAL_ERROR); 369 } 370 break; 371 case NsdManager.NATIVE_DAEMON_EVENT: 372 NativeEvent event = (NativeEvent) msg.obj; 373 if (!handleNativeEvent(event.code, event.raw, event.cooked)) { 374 return NOT_HANDLED; 375 } 376 break; 377 default: 378 return NOT_HANDLED; 379 } 380 return HANDLED; 381 } 382 383 private boolean handleNativeEvent(int code, String raw, String[] cooked) { 384 NsdServiceInfo servInfo; 385 int id = Integer.parseInt(cooked[1]); 386 ClientInfo clientInfo = mIdToClientInfoMap.get(id); 387 if (clientInfo == null) { 388 String name = NativeResponseCode.nameOf(code); 389 Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name)); 390 return false; 391 } 392 393 /* This goes in response as msg.arg2 */ 394 int clientId = clientInfo.getClientId(id); 395 if (clientId < 0) { 396 // This can happen because of race conditions. For example, 397 // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY, 398 // and we may get in this situation. 399 String name = NativeResponseCode.nameOf(code); 400 Slog.d(TAG, String.format( 401 "Notification %s for listener id %d that is no longer active", 402 name, id)); 403 return false; 404 } 405 if (DBG) { 406 String name = NativeResponseCode.nameOf(code); 407 Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw)); 408 } 409 switch (code) { 410 case NativeResponseCode.SERVICE_FOUND: 411 /* NNN uniqueId serviceName regType domain */ 412 servInfo = new NsdServiceInfo(cooked[2], cooked[3]); 413 clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0, 414 clientId, servInfo); 415 break; 416 case NativeResponseCode.SERVICE_LOST: 417 /* NNN uniqueId serviceName regType domain */ 418 servInfo = new NsdServiceInfo(cooked[2], cooked[3]); 419 clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0, 420 clientId, servInfo); 421 break; 422 case NativeResponseCode.SERVICE_DISCOVERY_FAILED: 423 /* NNN uniqueId errorCode */ 424 clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED, 425 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 426 break; 427 case NativeResponseCode.SERVICE_REGISTERED: 428 /* NNN regId serviceName regType */ 429 servInfo = new NsdServiceInfo(cooked[2], null); 430 clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED, 431 id, clientId, servInfo); 432 break; 433 case NativeResponseCode.SERVICE_REGISTRATION_FAILED: 434 /* NNN regId errorCode */ 435 clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED, 436 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 437 break; 438 case NativeResponseCode.SERVICE_UPDATED: 439 /* NNN regId */ 440 break; 441 case NativeResponseCode.SERVICE_UPDATE_FAILED: 442 /* NNN regId errorCode */ 443 break; 444 case NativeResponseCode.SERVICE_RESOLVED: 445 /* NNN resolveId fullName hostName port txtlen txtdata */ 446 int index = 0; 447 while (index < cooked[2].length() && cooked[2].charAt(index) != '.') { 448 if (cooked[2].charAt(index) == '\\') { 449 ++index; 450 } 451 ++index; 452 } 453 if (index >= cooked[2].length()) { 454 Slog.e(TAG, "Invalid service found " + raw); 455 break; 456 } 457 String name = cooked[2].substring(0, index); 458 String rest = cooked[2].substring(index); 459 String type = rest.replace(".local.", ""); 460 461 name = unescape(name); 462 463 clientInfo.mResolvedService.setServiceName(name); 464 clientInfo.mResolvedService.setServiceType(type); 465 clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); 466 clientInfo.mResolvedService.setTxtRecords(cooked[6]); 467 468 stopResolveService(id); 469 removeRequestMap(clientId, id, clientInfo); 470 471 int id2 = getUniqueId(); 472 if (getAddrInfo(id2, cooked[3])) { 473 storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE); 474 } else { 475 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, 476 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 477 clientInfo.mResolvedService = null; 478 } 479 break; 480 case NativeResponseCode.SERVICE_RESOLUTION_FAILED: 481 /* NNN resolveId errorCode */ 482 stopResolveService(id); 483 removeRequestMap(clientId, id, clientInfo); 484 clientInfo.mResolvedService = null; 485 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, 486 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 487 break; 488 case NativeResponseCode.SERVICE_GET_ADDR_FAILED: 489 /* NNN resolveId errorCode */ 490 stopGetAddrInfo(id); 491 removeRequestMap(clientId, id, clientInfo); 492 clientInfo.mResolvedService = null; 493 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, 494 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 495 break; 496 case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: 497 /* NNN resolveId hostname ttl addr */ 498 try { 499 clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); 500 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 501 0, clientId, clientInfo.mResolvedService); 502 } catch (java.net.UnknownHostException e) { 503 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, 504 NsdManager.FAILURE_INTERNAL_ERROR, clientId); 505 } 506 stopGetAddrInfo(id); 507 removeRequestMap(clientId, id, clientInfo); 508 clientInfo.mResolvedService = null; 509 break; 510 default: 511 return false; 512 } 513 return true; 514 } 515 } 516 } 517 518 private String unescape(String s) { 519 StringBuilder sb = new StringBuilder(s.length()); 520 for (int i = 0; i < s.length(); ++i) { 521 char c = s.charAt(i); 522 if (c == '\\') { 523 if (++i >= s.length()) { 524 Slog.e(TAG, "Unexpected end of escape sequence in: " + s); 525 break; 526 } 527 c = s.charAt(i); 528 if (c != '.' && c != '\\') { 529 if (i + 2 >= s.length()) { 530 Slog.e(TAG, "Unexpected end of escape sequence in: " + s); 531 break; 532 } 533 c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0')); 534 i += 2; 535 } 536 } 537 sb.append(c); 538 } 539 return sb.toString(); 540 } 541 542 @VisibleForTesting 543 NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) { 544 mContext = ctx; 545 mNsdSettings = settings; 546 mNsdStateMachine = new NsdStateMachine(TAG, handler); 547 mNsdStateMachine.start(); 548 mDaemonCallback = new NativeCallbackReceiver(); 549 mDaemon = fn.get(mDaemonCallback); 550 } 551 552 public static NsdService create(Context context) throws InterruptedException { 553 NsdSettings settings = NsdSettings.makeDefault(context); 554 HandlerThread thread = new HandlerThread(TAG); 555 thread.start(); 556 Handler handler = new Handler(thread.getLooper()); 557 NsdService service = new NsdService(context, settings, handler, DaemonConnection::new); 558 service.mDaemonCallback.awaitConnection(); 559 return service; 560 } 561 562 public Messenger getMessenger() { 563 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService"); 564 return new Messenger(mNsdStateMachine.getHandler()); 565 } 566 567 public void setEnabled(boolean isEnabled) { 568 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL, 569 "NsdService"); 570 mNsdSettings.putEnabledStatus(isEnabled); 571 notifyEnabled(isEnabled); 572 } 573 574 private void notifyEnabled(boolean isEnabled) { 575 mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE); 576 } 577 578 private void sendNsdStateChangeBroadcast(boolean isEnabled) { 579 final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED); 580 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 581 int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED; 582 intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState); 583 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 584 } 585 586 private boolean isNsdEnabled() { 587 boolean ret = mNsdSettings.isEnabled(); 588 if (DBG) { 589 Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled")); 590 } 591 return ret; 592 } 593 594 private int getUniqueId() { 595 if (++mUniqueId == INVALID_ID) return ++mUniqueId; 596 return mUniqueId; 597 } 598 599 /* These should be in sync with system/netd/server/ResponseCode.h */ 600 static final class NativeResponseCode { 601 public static final int SERVICE_DISCOVERY_FAILED = 602; 602 public static final int SERVICE_FOUND = 603; 603 public static final int SERVICE_LOST = 604; 604 605 public static final int SERVICE_REGISTRATION_FAILED = 605; 606 public static final int SERVICE_REGISTERED = 606; 607 608 public static final int SERVICE_RESOLUTION_FAILED = 607; 609 public static final int SERVICE_RESOLVED = 608; 610 611 public static final int SERVICE_UPDATED = 609; 612 public static final int SERVICE_UPDATE_FAILED = 610; 613 614 public static final int SERVICE_GET_ADDR_FAILED = 611; 615 public static final int SERVICE_GET_ADDR_SUCCESS = 612; 616 617 private static final SparseArray<String> CODE_NAMES = new SparseArray<>(); 618 static { 619 CODE_NAMES.put(SERVICE_DISCOVERY_FAILED, "SERVICE_DISCOVERY_FAILED"); 620 CODE_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND"); 621 CODE_NAMES.put(SERVICE_LOST, "SERVICE_LOST"); 622 CODE_NAMES.put(SERVICE_REGISTRATION_FAILED, "SERVICE_REGISTRATION_FAILED"); 623 CODE_NAMES.put(SERVICE_REGISTERED, "SERVICE_REGISTERED"); 624 CODE_NAMES.put(SERVICE_RESOLUTION_FAILED, "SERVICE_RESOLUTION_FAILED"); 625 CODE_NAMES.put(SERVICE_RESOLVED, "SERVICE_RESOLVED"); 626 CODE_NAMES.put(SERVICE_UPDATED, "SERVICE_UPDATED"); 627 CODE_NAMES.put(SERVICE_UPDATE_FAILED, "SERVICE_UPDATE_FAILED"); 628 CODE_NAMES.put(SERVICE_GET_ADDR_FAILED, "SERVICE_GET_ADDR_FAILED"); 629 CODE_NAMES.put(SERVICE_GET_ADDR_SUCCESS, "SERVICE_GET_ADDR_SUCCESS"); 630 } 631 632 static String nameOf(int code) { 633 String name = CODE_NAMES.get(code); 634 if (name == null) { 635 return Integer.toString(code); 636 } 637 return name; 638 } 639 } 640 641 private class NativeEvent { 642 final int code; 643 final String raw; 644 final String[] cooked; 645 646 NativeEvent(int code, String raw, String[] cooked) { 647 this.code = code; 648 this.raw = raw; 649 this.cooked = cooked; 650 } 651 } 652 653 class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks { 654 private final CountDownLatch connected = new CountDownLatch(1); 655 656 public void awaitConnection() throws InterruptedException { 657 connected.await(); 658 } 659 660 @Override 661 public void onDaemonConnected() { 662 connected.countDown(); 663 } 664 665 @Override 666 public boolean onCheckHoldWakeLock(int code) { 667 return false; 668 } 669 670 @Override 671 public boolean onEvent(int code, String raw, String[] cooked) { 672 // TODO: NDC translates a message to a callback, we could enhance NDC to 673 // directly interact with a state machine through messages 674 NativeEvent event = new NativeEvent(code, raw, cooked); 675 mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event); 676 return true; 677 } 678 } 679 680 interface DaemonConnectionSupplier { 681 DaemonConnection get(NativeCallbackReceiver callback); 682 } 683 684 @VisibleForTesting 685 public static class DaemonConnection { 686 final NativeDaemonConnector mNativeConnector; 687 688 DaemonConnection(NativeCallbackReceiver callback) { 689 mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null); 690 new Thread(mNativeConnector, MDNS_TAG).start(); 691 } 692 693 public boolean execute(Object... args) { 694 if (DBG) { 695 Slog.d(TAG, "mdnssd " + Arrays.toString(args)); 696 } 697 try { 698 mNativeConnector.execute("mdnssd", args); 699 } catch (NativeDaemonConnectorException e) { 700 Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e); 701 return false; 702 } 703 return true; 704 } 705 706 public void start() { 707 execute("start-service"); 708 } 709 710 public void stop() { 711 execute("stop-service"); 712 } 713 } 714 715 private boolean registerService(int regId, NsdServiceInfo service) { 716 if (DBG) { 717 Slog.d(TAG, "registerService: " + regId + " " + service); 718 } 719 String name = service.getServiceName(); 720 String type = service.getServiceType(); 721 int port = service.getPort(); 722 byte[] textRecord = service.getTxtRecord(); 723 String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", ""); 724 return mDaemon.execute("register", regId, name, type, port, record); 725 } 726 727 private boolean unregisterService(int regId) { 728 return mDaemon.execute("stop-register", regId); 729 } 730 731 private boolean updateService(int regId, DnsSdTxtRecord t) { 732 if (t == null) { 733 return false; 734 } 735 return mDaemon.execute("update", regId, t.size(), t.getRawData()); 736 } 737 738 private boolean discoverServices(int discoveryId, String serviceType) { 739 return mDaemon.execute("discover", discoveryId, serviceType); 740 } 741 742 private boolean stopServiceDiscovery(int discoveryId) { 743 return mDaemon.execute("stop-discover", discoveryId); 744 } 745 746 private boolean resolveService(int resolveId, NsdServiceInfo service) { 747 String name = service.getServiceName(); 748 String type = service.getServiceType(); 749 return mDaemon.execute("resolve", resolveId, name, type, "local."); 750 } 751 752 private boolean stopResolveService(int resolveId) { 753 return mDaemon.execute("stop-resolve", resolveId); 754 } 755 756 private boolean getAddrInfo(int resolveId, String hostname) { 757 return mDaemon.execute("getaddrinfo", resolveId, hostname); 758 } 759 760 private boolean stopGetAddrInfo(int resolveId) { 761 return mDaemon.execute("stop-getaddrinfo", resolveId); 762 } 763 764 @Override 765 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 766 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 767 768 for (ClientInfo client : mClients.values()) { 769 pw.println("Client Info"); 770 pw.println(client); 771 } 772 773 mNsdStateMachine.dump(fd, pw, args); 774 } 775 776 /* arg2 on the source message has an id that needs to be retained in replies 777 * see NsdManager for details */ 778 private Message obtainMessage(Message srcMsg) { 779 Message msg = Message.obtain(); 780 msg.arg2 = srcMsg.arg2; 781 return msg; 782 } 783 784 private void replyToMessage(Message msg, int what) { 785 if (msg.replyTo == null) return; 786 Message dstMsg = obtainMessage(msg); 787 dstMsg.what = what; 788 mReplyChannel.replyToMessage(msg, dstMsg); 789 } 790 791 private void replyToMessage(Message msg, int what, int arg1) { 792 if (msg.replyTo == null) return; 793 Message dstMsg = obtainMessage(msg); 794 dstMsg.what = what; 795 dstMsg.arg1 = arg1; 796 mReplyChannel.replyToMessage(msg, dstMsg); 797 } 798 799 private void replyToMessage(Message msg, int what, Object obj) { 800 if (msg.replyTo == null) return; 801 Message dstMsg = obtainMessage(msg); 802 dstMsg.what = what; 803 dstMsg.obj = obj; 804 mReplyChannel.replyToMessage(msg, dstMsg); 805 } 806 807 /* Information tracked per client */ 808 private class ClientInfo { 809 810 private static final int MAX_LIMIT = 10; 811 private final AsyncChannel mChannel; 812 private final Messenger mMessenger; 813 /* Remembers a resolved service until getaddrinfo completes */ 814 private NsdServiceInfo mResolvedService; 815 816 /* A map from client id to unique id sent to mDns */ 817 private final SparseIntArray mClientIds = new SparseIntArray(); 818 819 /* A map from client id to the type of the request we had received */ 820 private final SparseIntArray mClientRequests = new SparseIntArray(); 821 822 private ClientInfo(AsyncChannel c, Messenger m) { 823 mChannel = c; 824 mMessenger = m; 825 if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m); 826 } 827 828 @Override 829 public String toString() { 830 StringBuffer sb = new StringBuffer(); 831 sb.append("mChannel ").append(mChannel).append("\n"); 832 sb.append("mMessenger ").append(mMessenger).append("\n"); 833 sb.append("mResolvedService ").append(mResolvedService).append("\n"); 834 for(int i = 0; i< mClientIds.size(); i++) { 835 int clientID = mClientIds.keyAt(i); 836 sb.append("clientId ").append(clientID). 837 append(" mDnsId ").append(mClientIds.valueAt(i)). 838 append(" type ").append(mClientRequests.get(clientID)).append("\n"); 839 } 840 return sb.toString(); 841 } 842 843 // Remove any pending requests from the global map when we get rid of a client, 844 // and send cancellations to the daemon. 845 private void expungeAllRequests() { 846 int globalId, clientId, i; 847 // TODO: to keep handler responsive, do not clean all requests for that client at once. 848 for (i = 0; i < mClientIds.size(); i++) { 849 clientId = mClientIds.keyAt(i); 850 globalId = mClientIds.valueAt(i); 851 mIdToClientInfoMap.remove(globalId); 852 if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId + 853 " global-ID " + globalId + " type " + mClientRequests.get(clientId)); 854 switch (mClientRequests.get(clientId)) { 855 case NsdManager.DISCOVER_SERVICES: 856 stopServiceDiscovery(globalId); 857 break; 858 case NsdManager.RESOLVE_SERVICE: 859 stopResolveService(globalId); 860 break; 861 case NsdManager.REGISTER_SERVICE: 862 unregisterService(globalId); 863 break; 864 default: 865 break; 866 } 867 } 868 mClientIds.clear(); 869 mClientRequests.clear(); 870 } 871 872 // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id, 873 // return the corresponding listener id. mDnsClient id is also called a global id. 874 private int getClientId(final int globalId) { 875 int idx = mClientIds.indexOfValue(globalId); 876 if (idx < 0) { 877 return idx; 878 } 879 return mClientIds.keyAt(idx); 880 } 881 } 882 883 /** 884 * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to 885 * override, or have side effects on global state in unit tests. 886 */ 887 @VisibleForTesting 888 public interface NsdSettings { 889 boolean isEnabled(); 890 void putEnabledStatus(boolean isEnabled); 891 void registerContentObserver(Uri uri, ContentObserver observer); 892 893 static NsdSettings makeDefault(Context context) { 894 final ContentResolver resolver = context.getContentResolver(); 895 return new NsdSettings() { 896 @Override 897 public boolean isEnabled() { 898 return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1; 899 } 900 901 @Override 902 public void putEnabledStatus(boolean isEnabled) { 903 Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0); 904 } 905 906 @Override 907 public void registerContentObserver(Uri uri, ContentObserver observer) { 908 resolver.registerContentObserver(uri, false, observer); 909 } 910 }; 911 } 912 } 913 } 914