1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testutils;
18 
19 import android.system.ErrnoException;
20 
21 import androidx.annotation.NonNull;
22 
23 import com.android.net.module.util.IBpfMap;
24 import com.android.net.module.util.Struct;
25 
26 import java.io.IOException;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.NoSuchElementException;
30 import java.util.Objects;
31 import java.util.concurrent.ConcurrentHashMap;
32 
33 /**
34  *
35  * Fake BPF map class for tests that have no privilege to access real BPF maps. TestBpfMap does not
36  * load JNI and all member functions do not access real BPF maps.
37  *
38  * Implements IBpfMap so that any class using IBpfMap can use this class in its tests.
39  *
40  * @param <K> the key type
41  * @param <V> the value type
42  */
43 public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
44     private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>();
45 
TestBpfMap()46     public TestBpfMap() {}
47 
48     // TODO: Remove this constructor
TestBpfMap(final Class<K> key, final Class<V> value)49     public TestBpfMap(final Class<K> key, final Class<V> value) {
50     }
51 
52     @Override
forEach(ThrowingBiConsumer<K, V> action)53     public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException {
54         // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to
55         // implement the entry deletion in the iteration if required.
56         for (Map.Entry<K, V> entry : mMap.entrySet()) {
57             action.accept(entry.getKey(), entry.getValue());
58         }
59     }
60 
61     @Override
updateEntry(K key, V value)62     public void updateEntry(K key, V value) throws ErrnoException {
63         mMap.put(key, value);
64     }
65 
66     @Override
insertEntry(K key, V value)67     public void insertEntry(K key, V value) throws ErrnoException,
68             IllegalArgumentException {
69         // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry.
70         if (mMap.get(key) != null) {
71             throw new IllegalArgumentException(key + " already exist");
72         }
73         mMap.put(key, value);
74     }
75 
76     @Override
replaceEntry(K key, V value)77     public void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException {
78         if (!mMap.containsKey(key)) throw new NoSuchElementException();
79         mMap.put(key, value);
80     }
81 
82     @Override
insertOrReplaceEntry(K key, V value)83     public boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
84         // Returns true if inserted, false if replaced.
85         boolean ret = !mMap.containsKey(key);
86         mMap.put(key, value);
87         return ret;
88     }
89 
90     @Override
deleteEntry(Struct key)91     public boolean deleteEntry(Struct key) throws ErrnoException {
92         return mMap.remove(key) != null;
93     }
94 
95     @Override
isEmpty()96     public boolean isEmpty() throws ErrnoException {
97         return mMap.isEmpty();
98     }
99 
100     @Override
getNextKey(@onNull K key)101     public K getNextKey(@NonNull K key) {
102         // Expensive, but since this is only for tests...
103         Iterator<K> it = mMap.keySet().iterator();
104         while (it.hasNext()) {
105             if (Objects.equals(it.next(), key)) {
106                 return it.hasNext() ? it.next() : null;
107             }
108         }
109         return null;
110     }
111 
112     @Override
getFirstKey()113     public K getFirstKey() {
114         for (K key : mMap.keySet()) {
115             return key;
116         }
117         return null;
118     }
119 
120     @Override
containsKey(@onNull K key)121     public boolean containsKey(@NonNull K key) throws ErrnoException {
122         return mMap.containsKey(key);
123     }
124 
125     @Override
getValue(@onNull K key)126     public V getValue(@NonNull K key) throws ErrnoException {
127         // Return value for a given key. Otherwise, return null without an error ENOENT.
128         // BpfMap#getValue treats that the entry is not found as no error.
129         return mMap.get(key);
130     }
131 
132     @Override
clear()133     public void clear() throws ErrnoException {
134         // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
135         mMap.clear();
136     }
137 
138     @Override
close()139     public void close() throws IOException {
140     }
141 }
142