1 package com.android.tv.mdnsoffloadmanager; 2 3 import android.os.IInterface; 4 import android.os.RemoteException; 5 import android.util.Log; 6 7 import androidx.annotation.NonNull; 8 import androidx.annotation.Nullable; 9 10 import java.util.ArrayList; 11 import java.util.HashMap; 12 import java.util.List; 13 import java.util.Map; 14 15 import device.google.atv.mdns_offload.IMdnsOffload; 16 17 /** 18 * Lightweight test double, mimicking the behavior we expect from the real vendor service 19 * implementation. Refer to {@link IMdnsOffload} for the API specification. 20 * 21 * For testing purposes only, see go/choose-test-double#faking-definition. 22 */ 23 public class FakeMdnsOffloadService extends IMdnsOffload.Stub { 24 25 private static final String TAG = FakeMdnsOffloadService.class.getSimpleName(); 26 private static final int MAX_QNAME_LENGTH = 255; 27 28 /** 29 * No. of records that can be held in memory, per interface. Further records will be dropped. 30 */ 31 private static final int OFFLOAD_CAPACITY = 3; 32 33 /** 34 * No. of QNames in passthrough list that can be held in memory, per interface. Further entries 35 * will be dropped. 36 */ 37 private static final int PASSTHROUGH_CAPACITY = 4; 38 39 static class OffloadData { 40 byte passthroughBehavior = PassthroughBehavior.DROP_ALL; 41 final List<MdnsProtocolData> offloadedRecords = new ArrayList<>(); 42 final List<String> passthroughQNames = new ArrayList<>(); 43 } 44 45 boolean mOffloadState = false; 46 int mNextId = 0; 47 int mMissCounter = 0; 48 final Map<String, OffloadData> mOffloadDataByInterface = new HashMap<>(); 49 final Map<Integer, MdnsProtocolData> mProtocolDataById = new HashMap<>(); 50 final Map<Integer, Integer> mHitCounters = new HashMap<>(); 51 getOffloadData(String iface)52 OffloadData getOffloadData(String iface) { 53 return mOffloadDataByInterface.computeIfAbsent(iface, ifc -> new OffloadData()); 54 } 55 56 @Override resetAll()57 public void resetAll() throws RemoteException { 58 mNextId = 0; 59 mMissCounter = 0; 60 mProtocolDataById.clear(); 61 mOffloadDataByInterface.clear(); 62 mHitCounters.clear(); 63 } 64 65 @Override setOffloadState(boolean enabled)66 public boolean setOffloadState(boolean enabled) throws RemoteException { 67 mOffloadState = enabled; 68 return mOffloadState; 69 } 70 71 /** 72 * @see IMdnsOffload#addProtocolResponses 73 * 74 * Note that we do not deduplicate records here, since it is explicitly not part of the vendor 75 * spec. 76 */ 77 @Override addProtocolResponses(String iface, MdnsProtocolData protocolData)78 public int addProtocolResponses(String iface, MdnsProtocolData protocolData) 79 throws RemoteException { 80 OffloadData offloadData = getOffloadData(iface); 81 if (offloadData.offloadedRecords.size() < OFFLOAD_CAPACITY) { 82 offloadData.offloadedRecords.add(protocolData); 83 int id = mNextId++; 84 mProtocolDataById.put(id, protocolData); 85 log("Added offloaded data with key %d on iface %s", id, iface); 86 return id; 87 } 88 log("Failed to add offloaded data on iface %s", iface); 89 return -1; 90 } 91 92 @Override removeProtocolResponses(int recordKey)93 public void removeProtocolResponses(int recordKey) throws RemoteException { 94 for (OffloadData offloadData : mOffloadDataByInterface.values()) { 95 offloadData.offloadedRecords.remove(mProtocolDataById.get(recordKey)); 96 } 97 MdnsProtocolData removed = mProtocolDataById.remove(recordKey); 98 if (removed != null) { 99 log("Removed offloaded record %d.", recordKey); 100 } else { 101 log("Failed to remove offloaded record %s.", recordKey); 102 } 103 } 104 105 @Override getAndResetHitCounter(int recordKey)106 public int getAndResetHitCounter(int recordKey) throws RemoteException { 107 int count = mHitCounters.getOrDefault(recordKey, 0); 108 mHitCounters.remove(recordKey); 109 return count; 110 } 111 112 @Override getAndResetMissCounter()113 public int getAndResetMissCounter() throws RemoteException { 114 int count = mMissCounter; 115 mMissCounter = 0; 116 return count; 117 } 118 119 @Override addToPassthroughList(String iface, String qname)120 public boolean addToPassthroughList(String iface, String qname) 121 throws RemoteException { 122 OffloadData offloadData = getOffloadData(iface); 123 if (offloadData.passthroughQNames.size() < PASSTHROUGH_CAPACITY 124 && qname.length() <= MAX_QNAME_LENGTH) { 125 offloadData.passthroughQNames.add(qname); 126 log("Added %s to PT list for iface %s", qname, iface); 127 return true; 128 } 129 log("Failed to add %s to PT list for iface %s", qname, iface); 130 return false; 131 } 132 133 @Override removeFromPassthroughList(String iface, String qname)134 public void removeFromPassthroughList(String iface, String qname) 135 throws RemoteException { 136 boolean removed = getOffloadData(iface).passthroughQNames.remove(qname); 137 if (removed) { 138 log("Removed %s from PT list for iface %s", qname, iface); 139 } else { 140 log("Failed to remove %s from PT list for iface %s", qname, iface); 141 } 142 } 143 144 @Override setPassthroughBehavior(String iface, byte behavior)145 public void setPassthroughBehavior(String iface, byte behavior) 146 throws RemoteException { 147 if (!List.of( 148 PassthroughBehavior.FORWARD_ALL, 149 PassthroughBehavior.DROP_ALL, 150 PassthroughBehavior.PASSTHROUGH_LIST).contains(behavior)) { 151 throw new IllegalArgumentException("Invalid passthrough behavior"); 152 } 153 getOffloadData(iface).passthroughBehavior = behavior; 154 log("Set PT behavior to %d for iface %s", behavior, iface); 155 } 156 157 @Override getInterfaceVersion()158 public int getInterfaceVersion() throws RemoteException { 159 return 0; 160 } 161 162 @Override getInterfaceHash()163 public String getInterfaceHash() throws RemoteException { 164 return null; 165 } 166 167 @Nullable 168 @Override queryLocalInterface(@onNull String descriptor)169 public IInterface queryLocalInterface(@NonNull String descriptor) { 170 return this; 171 } 172 log(String pattern, Object... args)173 private void log(String pattern, Object... args) { 174 Log.d(TAG, String.format(pattern, args)); 175 } 176 } 177