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