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