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