• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 package com.android.server.connectivity.mdns;
18 
19 import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
20 import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresApi;
25 import android.os.Build;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 
30 import com.android.net.module.util.HandlerUtils;
31 import com.android.net.module.util.SharedLog;
32 
33 import java.io.IOException;
34 import java.net.InetSocketAddress;
35 
36 /**
37  * A class used to send several packets at given time intervals.
38  * @param <T> The type of the request providing packet repeating parameters.
39  */
40 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
41 public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
42     private static final InetSocketAddress[] ALL_ADDRS = new InetSocketAddress[] {
43             IPV4_SOCKET_ADDR, IPV6_SOCKET_ADDR
44     };
45 
46     @NonNull
47     private final MdnsReplySender mReplySender;
48     @NonNull
49     protected final Handler mHandler;
50     @Nullable
51     private final PacketRepeaterCallback<T> mCb;
52     @NonNull
53     private final SharedLog mSharedLog;
54     private final boolean mEnableDebugLog;
55 
56     /**
57      * Status callback from {@link MdnsPacketRepeater}.
58      *
59      * Callbacks are called on the {@link MdnsPacketRepeater} handler thread.
60      * @param <T> The type of the request providing packet repeating parameters.
61      */
62     public interface PacketRepeaterCallback<T extends MdnsPacketRepeater.Request> {
63         /**
64          * Called when a packet was sent.
65          */
onSent(int index, @NonNull T info, int sentPacketCount)66         default void onSent(int index, @NonNull T info, int sentPacketCount) {}
67 
68         /**
69          * Called when the {@link MdnsPacketRepeater} is done sending packets.
70          */
onFinished(@onNull T info)71         default void onFinished(@NonNull T info) {}
72     }
73 
74     /**
75      * A request to repeat packets.
76      *
77      * All methods are called in the looper thread.
78      */
79     public interface Request {
80         /**
81          * Get a packet to send for one iteration.
82          */
83         @NonNull
getPacket(int index)84         MdnsPacket getPacket(int index);
85 
86         /**
87          * Get the delay in milliseconds until the next packet transmission.
88          */
getDelayMs(int nextIndex)89         long getDelayMs(int nextIndex);
90 
91         /**
92          * Get the number of packets that should be sent.
93          */
getNumSends()94         int getNumSends();
95     }
96 
97     private final class ProbeHandler extends Handler {
ProbeHandler(@onNull Looper looper)98         ProbeHandler(@NonNull Looper looper) {
99             super(looper);
100         }
101 
102         @Override
handleMessage(@onNull Message msg)103         public void handleMessage(@NonNull Message msg) {
104             final int index = msg.arg1;
105             final T request = (T) msg.obj;
106 
107             if (index >= request.getNumSends()) {
108                 if (mCb != null) {
109                     mCb.onFinished(request);
110                 }
111                 return;
112             }
113 
114             final MdnsPacket packet = request.getPacket(index);
115             if (mEnableDebugLog) {
116                 mSharedLog.v("Sending packets for iteration " + index + " out of "
117                         + request.getNumSends() + " for ID " + msg.what);
118             }
119             // Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
120             // send when the socket has not joined the relevant group.
121             int sentPacketCount = 0;
122             for (InetSocketAddress destination : ALL_ADDRS) {
123                 try {
124                     sentPacketCount += mReplySender.sendNow(packet, destination);
125                 } catch (IOException e) {
126                     mSharedLog.e("Error sending packet to " + destination, e);
127                 }
128             }
129 
130             int nextIndex = index + 1;
131             // No need to go through the last handler loop if there's no callback to call
132             if (nextIndex < request.getNumSends() || mCb != null) {
133                 // TODO: consider using AlarmManager / WakeupMessage to avoid missing sending during
134                 // deep sleep; but this would affect battery life, and discovered services are
135                 // likely not to be available since the device is in deep sleep anyway.
136                 final long delay = request.getDelayMs(nextIndex);
137                 sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
138                 if (mEnableDebugLog) mSharedLog.v("Scheduled next packet in " + delay + "ms");
139             }
140 
141             // Call onSent after scheduling the next run, to allow the callback to cancel it
142             if (mCb != null) {
143                 mCb.onSent(index, request, sentPacketCount);
144             }
145         }
146     }
147 
MdnsPacketRepeater(@onNull Looper looper, @NonNull MdnsReplySender replySender, @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog, boolean enableDebugLog)148     protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
149             @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog,
150             boolean enableDebugLog) {
151         mHandler = new ProbeHandler(looper);
152         mReplySender = replySender;
153         mCb = cb;
154         mSharedLog = sharedLog;
155         mEnableDebugLog = enableDebugLog;
156     }
157 
startSending(int id, @NonNull T request, long initialDelayMs)158     protected void startSending(int id, @NonNull T request, long initialDelayMs) {
159         if (mEnableDebugLog) {
160             mSharedLog.v("Starting send with id " + id + ", request "
161                     + request.getClass().getSimpleName() + ", delay " + initialDelayMs);
162         }
163         mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs);
164     }
165 
166     /**
167      * Stop sending the packets for the specified ID
168      * @return true if probing was in progress, false if this was a no-op
169      */
stop(int id)170     public boolean stop(int id) {
171         HandlerUtils.ensureRunningOnHandlerThread(mHandler);
172         // Since this is run on the looper thread, messages cannot be currently processing and are
173         // all in the handler queue; unless this method is called from a message, but the current
174         // message cannot be cancelled.
175         if (mHandler.hasMessages(id)) {
176             if (mEnableDebugLog) {
177                 mSharedLog.v("Stopping send on id " + id);
178             }
179             mHandler.removeMessages(id);
180             return true;
181         }
182         return false;
183     }
184 }
185