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