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