• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.tv.mdnsoffloadmanager;
2 
3 import static device.google.atv.mdns_offload.IMdnsOffload.PassthroughBehavior.DROP_ALL;
4 import static device.google.atv.mdns_offload.IMdnsOffload.PassthroughBehavior.PASSTHROUGH_LIST;
5 
6 import android.os.RemoteException;
7 import android.os.ServiceSpecificException;
8 import android.util.Log;
9 
10 import androidx.annotation.NonNull;
11 import androidx.annotation.Nullable;
12 import androidx.annotation.WorkerThread;
13 
14 import java.io.PrintWriter;
15 import java.util.Collection;
16 import java.util.Comparator;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 
21 import device.google.atv.mdns_offload.IMdnsOffload;
22 
23 @WorkerThread
24 public class OffloadWriter {
25 
26     private static final String TAG = OffloadWriter.class.getSimpleName();
27     private static final int INVALID_OFFLOAD_KEY = -1;
28 
29     private boolean mOffloadState = false;
30     private IMdnsOffload mVendorService;
31 
32     @NonNull
convertQNameForVendorService(String qname)33     private static String convertQNameForVendorService(String qname) {
34         // We strip the trailing '.' when we provide QNames to the vendor service.
35         if (qname.endsWith(".")) {
36             return qname.substring(0, qname.length() - 1);
37         }
38         return qname;
39     }
40 
passthroughBehaviorToString( @MdnsOffload.PassthroughBehavior byte passthroughBehavior)41     private static String passthroughBehaviorToString(
42             @IMdnsOffload.PassthroughBehavior byte passthroughBehavior) {
43         switch (passthroughBehavior) {
44             case IMdnsOffload.PassthroughBehavior.FORWARD_ALL:
45                 return "FORWARD_ALL";
46             case IMdnsOffload.PassthroughBehavior.DROP_ALL:
47                 return "DROP_ALL";
48             case IMdnsOffload.PassthroughBehavior.PASSTHROUGH_LIST:
49                 return "PASSTHROUGH_LIST";
50         }
51         throw new IllegalArgumentException("No such passthrough behavior " + passthroughBehavior);
52     }
53 
setVendorService(@ullable IMdnsOffload vendorService)54     void setVendorService(@Nullable IMdnsOffload vendorService) {
55         mVendorService = vendorService;
56     }
57 
isVendorServiceConnected()58     boolean isVendorServiceConnected() {
59         return mVendorService != null;
60     }
61 
resetAll()62     void resetAll() {
63         if (!isVendorServiceConnected()) {
64             Log.e(TAG, "Cannot reset vendor service, service is not connected.");
65             return;
66         }
67         try {
68             mVendorService.resetAll();
69         } catch (RemoteException | ServiceSpecificException e) {
70             Log.e(TAG, "Failed to reset vendor service.", e);
71         }
72     }
73 
74     /**
75      * Apply the desired offload state on the vendor service. It may be necessary to refresh it,
76      * after we bind to the vendor service to set the initial state, or restore the previous state.
77      */
applyOffloadState()78     void applyOffloadState() {
79         setOffloadState(mOffloadState);
80     }
81 
82     /**
83      * Set the desired offload state and propagate to the vendor service.
84      */
setOffloadState(boolean enabled)85     void setOffloadState(boolean enabled) {
86         if (!isVendorServiceConnected()) {
87             Log.e(TAG, "Cannot set offload state, vendor service is not connected.");
88             return;
89         }
90         try {
91             Log.d(TAG, "Setting offload state: %b".formatted(enabled));
92             mVendorService.setOffloadState(enabled);
93         } catch (RemoteException | ServiceSpecificException e) {
94             Log.e(TAG, "Failed to set offload state to {" + enabled + "}.", e);
95         }
96         mOffloadState = enabled;
97     }
98 
99     /**
100      * Retrieve and clear all metric counters.
101      * <p>
102      * TODO(b/270115511) do something with these metrics.
103      */
retrieveAndClearMetrics(Collection<Integer> recordKeys)104     void retrieveAndClearMetrics(Collection<Integer> recordKeys) {
105         try {
106             int missCounter = mVendorService.getAndResetMissCounter();
107             Log.d(TAG, "Missed queries:" + missCounter);
108         } catch (RemoteException | ServiceSpecificException e) {
109             Log.e(TAG, "getAndResetMissCounter failure", e);
110         }
111         for (int recordKey : recordKeys) {
112             try {
113                 int hitCounter = mVendorService.getAndResetHitCounter(recordKey);
114                 Log.d(TAG, "Hits for record " + recordKey + " : " + hitCounter);
115             } catch (RemoteException | ServiceSpecificException e) {
116                 Log.e(TAG, "getAndResetHitCounter failure for recordKey {" + recordKey + "}", e);
117             }
118         }
119     }
120 
121     /**
122      * Offload a list of records. Records are prioritized by their priority value, and lower
123      * priority records may be dropped if not all fit in memory.
124      *
125      * @return The offload keys of successfully offloaded protocol responses.
126      */
writeOffloadData( String networkInterface, Collection<OffloadIntentStore.OffloadIntent> offloadIntents)127     Collection<Integer> writeOffloadData(
128             String networkInterface, Collection<OffloadIntentStore.OffloadIntent> offloadIntents) {
129         List<OffloadIntentStore.OffloadIntent> orderedOffloadIntents = offloadIntents
130                 .stream()
131                 .sorted(Comparator.comparingInt(offloadIntent -> offloadIntent.mPriority))
132                 .toList();
133         Set<Integer> offloaded = new HashSet<>();
134         for (OffloadIntentStore.OffloadIntent offloadIntent : orderedOffloadIntents) {
135             Integer offloadKey = tryAddProtocolResponses(networkInterface, offloadIntent);
136             if (offloadKey != null) {
137                 offloaded.add(offloadKey);
138             }
139         }
140         return offloaded;
141     }
142 
143     /**
144      * Remove a set of protocol responses.
145      *
146      * @return The offload keys of deleted protocol responses.
147      */
deleteOffloadData(Set<Integer> offloadKeys)148     Collection<Integer> deleteOffloadData(Set<Integer> offloadKeys) {
149         Set<Integer> deleted = new HashSet<>();
150         for (Integer offloadKey : offloadKeys) {
151             if (tryRemoveProtocolResponses(offloadKey)) {
152                 deleted.add(offloadKey);
153             }
154         }
155         return deleted;
156     }
157 
158     /**
159      * Add a list of entries to the passthrough list. Entries will be prioritized based on the
160      * supplied priority value, where the supplied order will be maintained for equal values. Lower
161      * priority records may be dropped if not all fit in memory.
162      *
163      * @return The set of successfully added passthrough entries.
164      */
writePassthroughData( String networkInterface, List<OffloadIntentStore.PassthroughIntent> ptIntents)165     Collection<String> writePassthroughData(
166             String networkInterface,
167             List<OffloadIntentStore.PassthroughIntent> ptIntents) {
168         byte passthroughMode = ptIntents.isEmpty() ? DROP_ALL : PASSTHROUGH_LIST;
169         trySetPassthroughBehavior(networkInterface, passthroughMode);
170 
171         // Note that this is a stable sort, therefore the provided order will be preserved for
172         // entries that are not on the priority list.
173         List<OffloadIntentStore.PassthroughIntent> orderedPtIntents = ptIntents
174                 .stream()
175                 .sorted(Comparator.comparingInt(pt -> pt.mPriority))
176                 .toList();
177         Set<String> added = new HashSet<>();
178         for (OffloadIntentStore.PassthroughIntent ptIntent : orderedPtIntents) {
179             if (tryAddToPassthroughList(networkInterface, ptIntent)) {
180                 added.add(ptIntent.mOriginalQName);
181             }
182         }
183         return added;
184     }
185 
186     /**
187      * Delete a set of entries on the passthrough list.
188      *
189      * @return The set of entries that were deleted.
190      */
deletePassthroughData(String networkInterface, Collection<String> qnames)191     Collection<String> deletePassthroughData(String networkInterface, Collection<String> qnames) {
192         Set<String> deleted = new HashSet<>();
193         for (String qname : qnames) {
194             if (tryRemoveFromPassthroughList(networkInterface, qname)) {
195                 deleted.add(qname);
196             }
197         }
198         return deleted;
199     }
200 
201     @Nullable
tryAddProtocolResponses( String networkInterface, OffloadIntentStore.OffloadIntent offloadIntent)202     private Integer tryAddProtocolResponses(
203             String networkInterface, OffloadIntentStore.OffloadIntent offloadIntent) {
204         int offloadKey;
205         try {
206             offloadKey = mVendorService.addProtocolResponses(
207                     networkInterface, offloadIntent.mProtocolData);
208         } catch (RemoteException | ServiceSpecificException e) {
209             String msg = "Failed to offload mDNS protocol response for record key {" +
210                     offloadIntent.mRecordKey + "} on iface {" + networkInterface + "}";
211             Log.e(TAG, msg, e);
212             return null;
213         }
214         if (offloadKey == INVALID_OFFLOAD_KEY) {
215             Log.e(TAG, "Failed to offload mDNS protocol data, vendor service returned error.");
216             return null;
217         }
218         return offloadKey;
219     }
220 
tryRemoveProtocolResponses(Integer offloadKey)221     private boolean tryRemoveProtocolResponses(Integer offloadKey) {
222         try {
223             mVendorService.removeProtocolResponses(offloadKey);
224             return true;
225         } catch (RemoteException | ServiceSpecificException e) {
226             String msg = "Failed to remove offloaded mDNS protocol response for offload key {"
227                     + offloadKey + "}";
228             Log.e(TAG, msg, e);
229         }
230         return false;
231     }
232 
trySetPassthroughBehavior(String networkInterface, byte passthroughMode)233     private void trySetPassthroughBehavior(String networkInterface, byte passthroughMode) {
234         try {
235             mVendorService.setPassthroughBehavior(networkInterface, passthroughMode);
236             String msg = "Set passthrough mode {"
237                     + passthroughBehaviorToString(passthroughMode) + "}"
238                     + " on iface {" + networkInterface + "}";
239             Log.e(TAG, msg);
240         } catch (RemoteException | ServiceSpecificException e) {
241             String msg = "Failed to set passthrough mode {"
242                     + passthroughBehaviorToString(passthroughMode) + "}"
243                     + " on iface {" + networkInterface + "}";
244             Log.e(TAG, msg, e);
245         }
246     }
247 
tryAddToPassthroughList( String networkInterface, OffloadIntentStore.PassthroughIntent ptIntent)248     private boolean tryAddToPassthroughList(
249             String networkInterface,
250             OffloadIntentStore.PassthroughIntent ptIntent) {
251         String simpleQName = convertQNameForVendorService(ptIntent.mOriginalQName);
252         boolean addedEntry;
253         try {
254             addedEntry = mVendorService.addToPassthroughList(networkInterface, simpleQName);
255         } catch (RemoteException | ServiceSpecificException e) {
256             String msg = "Failed to add passthrough list entry for qname {"
257                     + ptIntent.mOriginalQName + "} on iface {" + networkInterface + "}";
258             Log.e(TAG, msg, e);
259             return false;
260         }
261         if (!addedEntry) {
262             String msg = "Failed to add passthrough list entry for qname {"
263                     + ptIntent.mOriginalQName + "} on iface {" + networkInterface + "}.";
264             Log.e(TAG, msg);
265             return false;
266         }
267         String msg = "Added passthrough list entry for qname {"
268                     + ptIntent.mOriginalQName + "} on iface {" + networkInterface + "}.";
269         Log.e(TAG, msg);
270         return true;
271     }
272 
tryRemoveFromPassthroughList(String networkInterface, String qname)273     private boolean tryRemoveFromPassthroughList(String networkInterface, String qname) {
274         String simpleQName = convertQNameForVendorService(qname);
275         try {
276             mVendorService.removeFromPassthroughList(networkInterface, simpleQName);
277             return true;
278         } catch (RemoteException | ServiceSpecificException e) {
279             String msg = "Failed to remove passthrough for qname {" + qname + "}.";
280             Log.e(TAG, msg, e);
281         }
282         return false;
283     }
284 
dump(PrintWriter writer)285     void dump(PrintWriter writer) {
286         writer.println("OffloadWriter:");
287         writer.println("mOffloadState=%b".formatted(mOffloadState));
288         writer.println("isVendorServiceConnected=%b".formatted(isVendorServiceConnected()));
289         writer.println();
290     }
291 }
292