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             mVendorService.setOffloadState(enabled);
92         } catch (RemoteException | ServiceSpecificException e) {
93             Log.e(TAG, "Failed to set offload state to {" + enabled + "}.", e);
94         }
95         mOffloadState = enabled;
96     }
97 
98     /**
99      * Retrieve and clear all metric counters.
100      * <p>
101      * TODO(b/270115511) do something with these metrics.
102      */
retrieveAndClearMetrics(Collection<Integer> recordKeys)103     void retrieveAndClearMetrics(Collection<Integer> recordKeys) {
104         try {
105             int missCounter = mVendorService.getAndResetMissCounter();
106             Log.d(TAG, "Missed queries:" + missCounter);
107         } catch (RemoteException | ServiceSpecificException e) {
108             Log.e(TAG, "getAndResetMissCounter failure", e);
109         }
110         for (int recordKey : recordKeys) {
111             try {
112                 int hitCounter = mVendorService.getAndResetHitCounter(recordKey);
113                 Log.d(TAG, "Hits for record " + recordKey + " : " + hitCounter);
114             } catch (RemoteException | ServiceSpecificException e) {
115                 Log.e(TAG, "getAndResetHitCounter failure for recordKey {" + recordKey + "}", e);
116             }
117         }
118     }
119 
120     /**
121      * Offload a list of records. Records are prioritized by their priority value, and lower
122      * priority records may be dropped if not all fit in memory.
123      *
124      * @return The offload keys of successfully offloaded protocol responses.
125      */
writeOffloadData( String networkInterface, Collection<OffloadIntentStore.OffloadIntent> offloadIntents)126     Collection<Integer> writeOffloadData(
127             String networkInterface, Collection<OffloadIntentStore.OffloadIntent> offloadIntents) {
128         List<OffloadIntentStore.OffloadIntent> orderedOffloadIntents = offloadIntents
129                 .stream()
130                 .sorted(Comparator.comparingInt(offloadIntent -> offloadIntent.mPriority))
131                 .toList();
132         Set<Integer> offloaded = new HashSet<>();
133         for (OffloadIntentStore.OffloadIntent offloadIntent : orderedOffloadIntents) {
134             Integer offloadKey = tryAddProtocolResponses(networkInterface, offloadIntent);
135             if (offloadKey != null) {
136                 offloaded.add(offloadKey);
137             }
138         }
139         return offloaded;
140     }
141 
142     /**
143      * Remove a set of protocol responses.
144      *
145      * @return The offload keys of deleted protocol responses.
146      */
deleteOffloadData(Set<Integer> offloadKeys)147     Collection<Integer> deleteOffloadData(Set<Integer> offloadKeys) {
148         Set<Integer> deleted = new HashSet<>();
149         for (Integer offloadKey : offloadKeys) {
150             if (tryRemoveProtocolResponses(offloadKey)) {
151                 deleted.add(offloadKey);
152             }
153         }
154         return deleted;
155     }
156 
157     /**
158      * Add a list of entries to the passthrough list. Entries will be prioritized based on the
159      * supplied priority value, where the supplied order will be maintained for equal values. Lower
160      * priority records may be dropped if not all fit in memory.
161      *
162      * @return The set of successfully added passthrough entries.
163      */
writePassthroughData( String networkInterface, List<OffloadIntentStore.PassthroughIntent> ptIntents)164     Collection<String> writePassthroughData(
165             String networkInterface,
166             List<OffloadIntentStore.PassthroughIntent> ptIntents) {
167         byte passthroughMode = ptIntents.isEmpty() ? DROP_ALL : PASSTHROUGH_LIST;
168         trySetPassthroughBehavior(networkInterface, passthroughMode);
169 
170         // Note that this is a stable sort, therefore the provided order will be preserved for
171         // entries that are not on the priority list.
172         List<OffloadIntentStore.PassthroughIntent> orderedPtIntents = ptIntents
173                 .stream()
174                 .sorted(Comparator.comparingInt(pt -> pt.mPriority))
175                 .toList();
176         Set<String> added = new HashSet<>();
177         for (OffloadIntentStore.PassthroughIntent ptIntent : orderedPtIntents) {
178             if (tryAddToPassthroughList(networkInterface, ptIntent)) {
179                 added.add(ptIntent.mOriginalQName);
180             }
181         }
182         return added;
183     }
184 
185     /**
186      * Delete a set of entries on the passthrough list.
187      *
188      * @return The set of entries that were deleted.
189      */
deletePassthroughData(String networkInterface, Collection<String> qnames)190     Collection<String> deletePassthroughData(String networkInterface, Collection<String> qnames) {
191         Set<String> deleted = new HashSet<>();
192         for (String qname : qnames) {
193             if (tryRemoveFromPassthroughList(networkInterface, qname)) {
194                 deleted.add(qname);
195             }
196         }
197         return deleted;
198     }
199 
200     @Nullable
tryAddProtocolResponses( String networkInterface, OffloadIntentStore.OffloadIntent offloadIntent)201     private Integer tryAddProtocolResponses(
202             String networkInterface, OffloadIntentStore.OffloadIntent offloadIntent) {
203         int offloadKey;
204         try {
205             offloadKey = mVendorService.addProtocolResponses(
206                     networkInterface, offloadIntent.mProtocolData);
207         } catch (RemoteException | ServiceSpecificException e) {
208             String msg = "Failed to offload mDNS protocol response for record key {" +
209                     offloadIntent.mRecordKey + "} on iface {" + networkInterface + "}";
210             Log.e(TAG, msg, e);
211             return null;
212         }
213         if (offloadKey == INVALID_OFFLOAD_KEY) {
214             Log.e(TAG, "Failed to offload mDNS protocol data, vendor service returned error.");
215             return null;
216         }
217         return offloadKey;
218     }
219 
tryRemoveProtocolResponses(Integer offloadKey)220     private boolean tryRemoveProtocolResponses(Integer offloadKey) {
221         try {
222             mVendorService.removeProtocolResponses(offloadKey);
223             return true;
224         } catch (RemoteException | ServiceSpecificException e) {
225             String msg = "Failed to remove offloaded mDNS protocol response for offload key {"
226                     + offloadKey + "}";
227             Log.e(TAG, msg, e);
228         }
229         return false;
230     }
231 
trySetPassthroughBehavior(String networkInterface, byte passthroughMode)232     private void trySetPassthroughBehavior(String networkInterface, byte passthroughMode) {
233         try {
234             mVendorService.setPassthroughBehavior(networkInterface, passthroughMode);
235         } catch (RemoteException | ServiceSpecificException e) {
236             String msg = "Failed to set passthrough mode {"
237                     + passthroughBehaviorToString(passthroughMode) + "}"
238                     + " on iface {" + networkInterface + "}";
239             Log.e(TAG, msg, e);
240         }
241     }
242 
tryAddToPassthroughList( String networkInterface, OffloadIntentStore.PassthroughIntent ptIntent)243     private boolean tryAddToPassthroughList(
244             String networkInterface,
245             OffloadIntentStore.PassthroughIntent ptIntent) {
246         String simpleQName = convertQNameForVendorService(ptIntent.mOriginalQName);
247         boolean addedEntry;
248         try {
249             addedEntry = mVendorService.addToPassthroughList(networkInterface, simpleQName);
250         } catch (RemoteException | ServiceSpecificException e) {
251             String msg = "Failed to add passthrough list entry for qname {"
252                     + ptIntent.mOriginalQName + "} on iface {" + networkInterface + "}";
253             Log.e(TAG, msg, e);
254             return false;
255         }
256         if (!addedEntry) {
257             String msg = "Failed to add passthrough list entry for qname {"
258                     + ptIntent.mOriginalQName + "} on iface {" + networkInterface + "}.";
259             Log.e(TAG, msg);
260             return false;
261         }
262         return true;
263     }
264 
tryRemoveFromPassthroughList(String networkInterface, String qname)265     private boolean tryRemoveFromPassthroughList(String networkInterface, String qname) {
266         String simpleQName = convertQNameForVendorService(qname);
267         try {
268             mVendorService.removeFromPassthroughList(networkInterface, simpleQName);
269             return true;
270         } catch (RemoteException | ServiceSpecificException e) {
271             String msg = "Failed to remove passthrough for qname {" + qname + "}.";
272             Log.e(TAG, msg, e);
273         }
274         return false;
275     }
276 
dump(PrintWriter writer)277     void dump(PrintWriter writer) {
278         writer.println("OffloadWriter:");
279         writer.println("mOffloadState=%b".formatted(mOffloadState));
280         writer.println("isVendorServiceConnected=%b".formatted(isVendorServiceConnected()));
281         writer.println();
282     }
283 }
284