• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.server.net;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.NetworkStats;
22 import android.util.LruCache;
23 
24 import com.android.internal.annotations.GuardedBy;
25 
26 import java.time.Clock;
27 import java.util.Objects;
28 import java.util.function.Supplier;
29 
30 /**
31  * A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
32  * with an adjustable expiry duration to manage data freshness.
33  */
34 class TrafficStatsRateLimitCache {
35     private final Clock mClock;
36     private final long mExpiryDurationMs;
37 
38     /**
39      * Constructs a new {@link TrafficStatsRateLimitCache} with the specified expiry duration.
40      *
41      * @param clock The {@link Clock} to use for determining timestamps.
42      * @param expiryDurationMs The expiry duration in milliseconds.
43      * @param maxSize Maximum number of entries.
44      */
TrafficStatsRateLimitCache(@onNull Clock clock, long expiryDurationMs, int maxSize)45     TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs, int maxSize) {
46         mClock = clock;
47         mExpiryDurationMs = expiryDurationMs;
48         mMap = new LruCache<>(maxSize);
49     }
50 
51     private static class TrafficStatsCacheKey {
52         @Nullable
53         public final String iface;
54         public final int uid;
55 
TrafficStatsCacheKey(@ullable String iface, int uid)56         TrafficStatsCacheKey(@Nullable String iface, int uid) {
57             this.iface = iface;
58             this.uid = uid;
59         }
60 
61         @Override
equals(Object o)62         public boolean equals(Object o) {
63             if (this == o) return true;
64             if (!(o instanceof TrafficStatsCacheKey)) return false;
65             TrafficStatsCacheKey that = (TrafficStatsCacheKey) o;
66             return uid == that.uid && Objects.equals(iface, that.iface);
67         }
68 
69         @Override
hashCode()70         public int hashCode() {
71             return Objects.hash(iface, uid);
72         }
73     }
74 
75     private static class TrafficStatsCacheValue {
76         public final long timestamp;
77         @NonNull
78         public final NetworkStats.Entry entry;
79 
TrafficStatsCacheValue(long timestamp, NetworkStats.Entry entry)80         TrafficStatsCacheValue(long timestamp, NetworkStats.Entry entry) {
81             this.timestamp = timestamp;
82             this.entry = entry;
83         }
84     }
85 
86     @GuardedBy("mMap")
87     private final LruCache<TrafficStatsCacheKey, TrafficStatsCacheValue> mMap;
88 
89     /**
90      * Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
91      *
92      * @param iface The interface name to include in the cache key. Null if not applicable.
93      * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
94      * @return The cached {@link NetworkStats.Entry}, or null if not found or expired.
95      */
96     @Nullable
get(String iface, int uid)97     NetworkStats.Entry get(String iface, int uid) {
98         final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
99         synchronized (mMap) { // Synchronize for thread-safety
100             final TrafficStatsCacheValue value = mMap.get(key);
101             if (value != null && !isExpired(value.timestamp)) {
102                 return value.entry;
103             } else {
104                 mMap.remove(key); // Remove expired entries
105                 return null;
106             }
107         }
108     }
109 
110     /**
111      * Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
112      * If the entry is not found in the cache or has expired, computes it using the provided
113      * {@code supplier} and stores the result in the cache.
114      *
115      * @param iface The interface name to include in the cache key. {@code IFACE_ALL}
116      *              if not applicable.
117      * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
118      * @param supplier The {@link Supplier} to compute the {@link NetworkStats.Entry} if not found.
119      * @return The cached or computed {@link NetworkStats.Entry}, or null if not found, expired,
120      *         or if the {@code supplier} returns null.
121      */
122     @Nullable
getOrCompute(String iface, int uid, @NonNull Supplier<NetworkStats.Entry> supplier)123     NetworkStats.Entry getOrCompute(String iface, int uid,
124             @NonNull Supplier<NetworkStats.Entry> supplier) {
125         synchronized (mMap) {
126             final NetworkStats.Entry cachedValue = get(iface, uid);
127             if (cachedValue != null) {
128                 return cachedValue;
129             }
130 
131             // Entry not found or expired, compute it
132             final NetworkStats.Entry computedEntry = supplier.get();
133             if (computedEntry != null && !computedEntry.isEmpty()) {
134                 put(iface, uid, computedEntry);
135             }
136             return computedEntry;
137         }
138     }
139 
140     /**
141      * Stores a {@link NetworkStats.Entry} in the cache, associated with the given key.
142      *
143      * @param iface The interface name to include in the cache key. Null if not applicable.
144      * @param uid   The UID to include in the cache key. {@code UID_ALL} if not applicable.
145      * @param entry The {@link NetworkStats.Entry} to store in the cache.
146      */
put(String iface, int uid, @NonNull final NetworkStats.Entry entry)147     void put(String iface, int uid, @NonNull final NetworkStats.Entry entry) {
148         Objects.requireNonNull(entry);
149         final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
150         synchronized (mMap) { // Synchronize for thread-safety
151             mMap.put(key, new TrafficStatsCacheValue(mClock.millis(), entry));
152         }
153     }
154 
155     /**
156      * Clear the cache.
157      */
clear()158     void clear() {
159         synchronized (mMap) {
160             mMap.evictAll();
161         }
162     }
163 
isExpired(long timestamp)164     private boolean isExpired(long timestamp) {
165         return mClock.millis() > timestamp + mExpiryDurationMs;
166     }
167 }
168