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