NsdService.java revision 111db01a2668b7a1ec256a4166c322db061d1ddd
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;
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,
400                                NativeDaemonEvent.unescapeArgs(event.raw))) {
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 = -1;
424                int keyId = clientInfo.mClientIds.indexOfValue(id);
425                if (keyId != -1) {
426                    clientId = clientInfo.mClientIds.keyAt(keyId);
427                } else {
428                    // This can happen because of race conditions. For example,
429                    // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
430                    // and we may get in this situation.
431                    Slog.d(TAG, "Notification for a listener that is no longer active: " + id);
432                    handled = false;
433                    return handled;
434                }
435
436                switch (code) {
437                    case NativeResponseCode.SERVICE_FOUND:
438                        /* NNN uniqueId serviceName regType domain */
439                        if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
440                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
441                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
442                                clientId, servInfo);
443                        break;
444                    case NativeResponseCode.SERVICE_LOST:
445                        /* NNN uniqueId serviceName regType domain */
446                        if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
447                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
448                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
449                                clientId, servInfo);
450                        break;
451                    case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
452                        /* NNN uniqueId errorCode */
453                        if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
454                        clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
455                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
456                        break;
457                    case NativeResponseCode.SERVICE_REGISTERED:
458                        /* NNN regId serviceName regType */
459                        if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
460                        servInfo = new NsdServiceInfo(cooked[2], null);
461                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
462                                id, clientId, servInfo);
463                        break;
464                    case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
465                        /* NNN regId errorCode */
466                        if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
467                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
468                               NsdManager.FAILURE_INTERNAL_ERROR, clientId);
469                        break;
470                    case NativeResponseCode.SERVICE_UPDATED:
471                        /* NNN regId */
472                        break;
473                    case NativeResponseCode.SERVICE_UPDATE_FAILED:
474                        /* NNN regId errorCode */
475                        break;
476                    case NativeResponseCode.SERVICE_RESOLVED:
477                        /* NNN resolveId fullName hostName port txtlen txtdata */
478                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
479                        int index = cooked[2].indexOf(".");
480                        if (index == -1) {
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                        clientInfo.mResolvedService.setServiceName(name);
489                        clientInfo.mResolvedService.setServiceType(type);
490                        clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
491
492                        stopResolveService(id);
493                        removeRequestMap(clientId, id, clientInfo);
494
495                        int id2 = getUniqueId();
496                        if (getAddrInfo(id2, cooked[3])) {
497                            storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
498                        } else {
499                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
500                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
501                            clientInfo.mResolvedService = null;
502                        }
503                        break;
504                    case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
505                        /* NNN resolveId errorCode */
506                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
507                        stopResolveService(id);
508                        removeRequestMap(clientId, id, clientInfo);
509                        clientInfo.mResolvedService = null;
510                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
511                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
512                        break;
513                    case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
514                        /* NNN resolveId errorCode */
515                        stopGetAddrInfo(id);
516                        removeRequestMap(clientId, id, clientInfo);
517                        clientInfo.mResolvedService = null;
518                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
519                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
520                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
521                        break;
522                    case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
523                        /* NNN resolveId hostname ttl addr */
524                        if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
525                        try {
526                            clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
527                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
528                                   0, clientId, clientInfo.mResolvedService);
529                        } catch (java.net.UnknownHostException e) {
530                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
531                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
532                        }
533                        stopGetAddrInfo(id);
534                        removeRequestMap(clientId, id, clientInfo);
535                        clientInfo.mResolvedService = null;
536                        break;
537                    default:
538                        handled = false;
539                        break;
540                }
541                return handled;
542            }
543       }
544    }
545
546    private NativeDaemonConnector mNativeConnector;
547    private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
548
549    private NsdService(Context context) {
550        mContext = context;
551        mContentResolver = context.getContentResolver();
552
553        mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
554                MDNS_TAG, 25, null);
555
556        mNsdStateMachine = new NsdStateMachine(TAG);
557        mNsdStateMachine.start();
558
559        Thread th = new Thread(mNativeConnector, MDNS_TAG);
560        th.start();
561    }
562
563    public static NsdService create(Context context) throws InterruptedException {
564        NsdService service = new NsdService(context);
565        service.mNativeDaemonConnected.await();
566        return service;
567    }
568
569    public Messenger getMessenger() {
570        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
571            "NsdService");
572        return new Messenger(mNsdStateMachine.getHandler());
573    }
574
575    public void setEnabled(boolean enable) {
576        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
577                "NsdService");
578        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
579        if (enable) {
580            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
581        } else {
582            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
583        }
584    }
585
586    private void sendNsdStateChangeBroadcast(boolean enabled) {
587        final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
588        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
589        if (enabled) {
590            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
591        } else {
592            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
593        }
594        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
595    }
596
597    private boolean isNsdEnabled() {
598        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
599        if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
600        return ret;
601    }
602
603    private int getUniqueId() {
604        if (++mUniqueId == INVALID_ID) return ++mUniqueId;
605        return mUniqueId;
606    }
607
608    /* These should be in sync with system/netd/mDnsResponseCode.h */
609    class NativeResponseCode {
610        public static final int SERVICE_DISCOVERY_FAILED    =   602;
611        public static final int SERVICE_FOUND               =   603;
612        public static final int SERVICE_LOST                =   604;
613
614        public static final int SERVICE_REGISTRATION_FAILED =   605;
615        public static final int SERVICE_REGISTERED          =   606;
616
617        public static final int SERVICE_RESOLUTION_FAILED   =   607;
618        public static final int SERVICE_RESOLVED            =   608;
619
620        public static final int SERVICE_UPDATED             =   609;
621        public static final int SERVICE_UPDATE_FAILED       =   610;
622
623        public static final int SERVICE_GET_ADDR_FAILED     =   611;
624        public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
625    }
626
627    private class NativeEvent {
628        final int code;
629        final String raw;
630
631        NativeEvent(int code, String raw) {
632            this.code = code;
633            this.raw = raw;
634        }
635    }
636
637    class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
638        public void onDaemonConnected() {
639            mNativeDaemonConnected.countDown();
640        }
641
642        public boolean onCheckHoldWakeLock(int code) {
643            return false;
644        }
645
646        public boolean onEvent(int code, String raw, String[] cooked) {
647            // TODO: NDC translates a message to a callback, we could enhance NDC to
648            // directly interact with a state machine through messages
649            NativeEvent event = new NativeEvent(code, raw);
650            mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
651            return true;
652        }
653    }
654
655    private boolean startMDnsDaemon() {
656        if (DBG) Slog.d(TAG, "startMDnsDaemon");
657        try {
658            mNativeConnector.execute("mdnssd", "start-service");
659        } catch(NativeDaemonConnectorException e) {
660            Slog.e(TAG, "Failed to start daemon" + e);
661            return false;
662        }
663        return true;
664    }
665
666    private boolean stopMDnsDaemon() {
667        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
668        try {
669            mNativeConnector.execute("mdnssd", "stop-service");
670        } catch(NativeDaemonConnectorException e) {
671            Slog.e(TAG, "Failed to start daemon" + e);
672            return false;
673        }
674        return true;
675    }
676
677    private boolean registerService(int regId, NsdServiceInfo service) {
678        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
679        try {
680            Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
681                    service.getServiceType(), service.getPort());
682
683            // Add TXT records as additional arguments.
684            Map<String, byte[]> txtRecords = service.getAttributes();
685            for (String key : txtRecords.keySet()) {
686                try {
687                    // TODO: Send encoded TXT record as bytes once NDC/netd supports binary data.
688                    cmd.appendArg(String.format(Locale.US, "%s=%s", key,
689                            new String(txtRecords.get(key), "UTF_8")));
690                } catch (UnsupportedEncodingException e) {
691                    Slog.e(TAG, "Failed to encode txtRecord " + e);
692                }
693            }
694
695            mNativeConnector.execute(cmd);
696        } catch(NativeDaemonConnectorException e) {
697            Slog.e(TAG, "Failed to execute registerService " + e);
698            return false;
699        }
700        return true;
701    }
702
703    private boolean unregisterService(int regId) {
704        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
705        try {
706            mNativeConnector.execute("mdnssd", "stop-register", regId);
707        } catch(NativeDaemonConnectorException e) {
708            Slog.e(TAG, "Failed to execute unregisterService " + e);
709            return false;
710        }
711        return true;
712    }
713
714    private boolean updateService(int regId, DnsSdTxtRecord t) {
715        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
716        try {
717            if (t == null) return false;
718            mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
719        } catch(NativeDaemonConnectorException e) {
720            Slog.e(TAG, "Failed to updateServices " + e);
721            return false;
722        }
723        return true;
724    }
725
726    private boolean discoverServices(int discoveryId, String serviceType) {
727        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
728        try {
729            mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
730        } catch(NativeDaemonConnectorException e) {
731            Slog.e(TAG, "Failed to discoverServices " + e);
732            return false;
733        }
734        return true;
735    }
736
737    private boolean stopServiceDiscovery(int discoveryId) {
738        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
739        try {
740            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
741        } catch(NativeDaemonConnectorException e) {
742            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
743            return false;
744        }
745        return true;
746    }
747
748    private boolean resolveService(int resolveId, NsdServiceInfo service) {
749        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
750        try {
751            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
752                    service.getServiceType(), "local.");
753        } catch(NativeDaemonConnectorException e) {
754            Slog.e(TAG, "Failed to resolveService " + e);
755            return false;
756        }
757        return true;
758    }
759
760    private boolean stopResolveService(int resolveId) {
761        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
762        try {
763            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
764        } catch(NativeDaemonConnectorException e) {
765            Slog.e(TAG, "Failed to stop resolve " + e);
766            return false;
767        }
768        return true;
769    }
770
771    private boolean getAddrInfo(int resolveId, String hostname) {
772        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
773        try {
774            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
775        } catch(NativeDaemonConnectorException e) {
776            Slog.e(TAG, "Failed to getAddrInfo " + e);
777            return false;
778        }
779        return true;
780    }
781
782    private boolean stopGetAddrInfo(int resolveId) {
783        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
784        try {
785            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
786        } catch(NativeDaemonConnectorException e) {
787            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
788            return false;
789        }
790        return true;
791    }
792
793    @Override
794    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
795        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
796                != PackageManager.PERMISSION_GRANTED) {
797            pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid="
798                    + Binder.getCallingPid()
799                    + ", uid=" + Binder.getCallingUid());
800            return;
801        }
802
803        for (ClientInfo client : mClients.values()) {
804            pw.println("Client Info");
805            pw.println(client);
806        }
807
808        mNsdStateMachine.dump(fd, pw, args);
809    }
810
811    /* arg2 on the source message has an id that needs to be retained in replies
812     * see NsdManager for details */
813    private Message obtainMessage(Message srcMsg) {
814        Message msg = Message.obtain();
815        msg.arg2 = srcMsg.arg2;
816        return msg;
817    }
818
819    private void replyToMessage(Message msg, int what) {
820        if (msg.replyTo == null) return;
821        Message dstMsg = obtainMessage(msg);
822        dstMsg.what = what;
823        mReplyChannel.replyToMessage(msg, dstMsg);
824    }
825
826    private void replyToMessage(Message msg, int what, int arg1) {
827        if (msg.replyTo == null) return;
828        Message dstMsg = obtainMessage(msg);
829        dstMsg.what = what;
830        dstMsg.arg1 = arg1;
831        mReplyChannel.replyToMessage(msg, dstMsg);
832    }
833
834    private void replyToMessage(Message msg, int what, Object obj) {
835        if (msg.replyTo == null) return;
836        Message dstMsg = obtainMessage(msg);
837        dstMsg.what = what;
838        dstMsg.obj = obj;
839        mReplyChannel.replyToMessage(msg, dstMsg);
840    }
841
842    /* Information tracked per client */
843    private class ClientInfo {
844
845        private static final int MAX_LIMIT = 10;
846        private final AsyncChannel mChannel;
847        private final Messenger mMessenger;
848        /* Remembers a resolved service until getaddrinfo completes */
849        private NsdServiceInfo mResolvedService;
850
851        /* A map from client id to unique id sent to mDns */
852        private SparseArray<Integer> mClientIds = new SparseArray<Integer>();
853
854        /* A map from client id to the type of the request we had received */
855        private SparseArray<Integer> mClientRequests = new SparseArray<Integer>();
856
857        private ClientInfo(AsyncChannel c, Messenger m) {
858            mChannel = c;
859            mMessenger = m;
860            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
861        }
862
863        @Override
864        public String toString() {
865            StringBuffer sb = new StringBuffer();
866            sb.append("mChannel ").append(mChannel).append("\n");
867            sb.append("mMessenger ").append(mMessenger).append("\n");
868            sb.append("mResolvedService ").append(mResolvedService).append("\n");
869            for(int i = 0; i< mClientIds.size(); i++) {
870                int clientID = mClientIds.keyAt(i);
871                sb.append("clientId ").append(clientID).
872                    append(" mDnsId ").append(mClientIds.valueAt(i)).
873                    append(" type ").append(mClientRequests.get(clientID)).append("\n");
874            }
875            return sb.toString();
876        }
877
878        // Remove any pending requests from the global map when we get rid of a client,
879        // and send cancellations to the daemon.
880        private void expungeAllRequests() {
881            int globalId, clientId, i;
882            for (i = 0; i < mClientIds.size(); i++) {
883                clientId = mClientIds.keyAt(i);
884                globalId = mClientIds.valueAt(i);
885                mIdToClientInfoMap.remove(globalId);
886                if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
887                        " global-ID " + globalId + " type " + mClientRequests.get(clientId));
888                switch (mClientRequests.get(clientId)) {
889                    case NsdManager.DISCOVER_SERVICES:
890                        stopServiceDiscovery(globalId);
891                        break;
892                    case NsdManager.RESOLVE_SERVICE:
893                        stopResolveService(globalId);
894                        break;
895                    case NsdManager.REGISTER_SERVICE:
896                        unregisterService(globalId);
897                        break;
898                    default:
899                        break;
900                }
901            }
902            mClientIds.clear();
903            mClientRequests.clear();
904        }
905
906    }
907}
908