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