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