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