Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.content.Context;
     20 import android.content.ContentResolver;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.database.ContentObserver;
     24 import android.net.nsd.NsdServiceInfo;
     25 import android.net.nsd.DnsSdTxtRecord;
     26 import android.net.nsd.INsdManager;
     27 import android.net.nsd.NsdManager;
     28 import android.os.Binder;
     29 import android.os.Message;
     30 import android.os.Messenger;
     31 import android.os.UserHandle;
     32 import android.provider.Settings;
     33 import android.util.Base64;
     34 import android.util.Slog;
     35 import android.util.SparseArray;
     36 
     37 import java.io.FileDescriptor;
     38 import java.io.PrintWriter;
     39 import java.net.InetAddress;
     40 import java.util.HashMap;
     41 import java.util.concurrent.CountDownLatch;
     42 
     43 import com.android.internal.util.AsyncChannel;
     44 import com.android.internal.util.DumpUtils;
     45 import com.android.internal.util.Protocol;
     46 import com.android.internal.util.State;
     47 import com.android.internal.util.StateMachine;
     48 import 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  */
     56 public 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