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