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