• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.server.wifi;
18 
19 import android.net.MacAddress;
20 import android.net.wifi.WifiConfiguration;
21 import android.os.Handler;
22 import android.util.Log;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Objects;
31 
32 /** Utilities for storing PMK cache. */
33 public class PmkCacheManager {
34     private static final String TAG = "PmkCacheManager";
35 
36     @VisibleForTesting
37     static final String PMK_CACHE_EXPIRATION_ALARM_TAG = "PMK_CACHE_EXPIRATION_TIMER";
38 
39     private final Clock mClock;
40     private final Handler mEventHandler;
41 
42     private boolean mVerboseLoggingEnabled = false;
43 
44     private final Object mLock = new Object();
45 
46     @GuardedBy("mLock")
47     private SparseArray<List<PmkCacheStoreData>> mPmkCacheEntries = new SparseArray<>();
48 
PmkCacheManager(Clock clock, Handler eventHandler)49     public PmkCacheManager(Clock clock, Handler eventHandler) {
50         mClock = clock;
51         mEventHandler = eventHandler;
52     }
53 
54     /**
55      * Add a PMK cache entry to the store.
56      *
57      * @param macAddress the interface MAC address to connect to the network.
58      * @param networkId the network ID of the WifiConfiguration associates with the network.
59      * @param bssid BSSID of the access point to which the station is associated
60      * @param expirationTimeInSec the expiration time of the PMK cache since boot.
61      * @param serializedEntry the opaque data of the PMK cache.
62      * @return true when PMK cache is added; otherwise, false.
63      */
add(MacAddress macAddress, int networkId, MacAddress bssid, long expirationTimeInSec, ArrayList<Byte> serializedEntry)64     public boolean add(MacAddress macAddress, int networkId, MacAddress bssid,
65             long expirationTimeInSec, ArrayList<Byte> serializedEntry) {
66         synchronized (mLock) {
67             if (WifiConfiguration.INVALID_NETWORK_ID == networkId) return false;
68             if (macAddress == null) {
69                 Log.w(TAG, "Omit PMK cache due to no valid MAC address");
70                 return false;
71             }
72             if (null == serializedEntry) {
73                 Log.w(TAG, "Omit PMK cache due to null entry.");
74                 return false;
75             }
76             final long elapseTimeInSecond = mClock.getElapsedSinceBootMillis() / 1000;
77             if (elapseTimeInSecond >= expirationTimeInSec) {
78                 Log.w(TAG, "Omit expired PMK cache.");
79                 return false;
80             }
81 
82             PmkCacheStoreData newStoreData =
83                     new PmkCacheStoreData(macAddress, bssid, serializedEntry, expirationTimeInSec);
84             List<PmkCacheStoreData> pmkDataList = mPmkCacheEntries.get(networkId);
85             if (pmkDataList == null) {
86                 pmkDataList = new ArrayList<>();
87                 mPmkCacheEntries.put(networkId, pmkDataList);
88             } else {
89                 PmkCacheStoreData existStoreData = null;
90                 if (bssid != null) {
91                     // Remove the stored PMK cache if the PMK cache is changed for an existing
92                     // BSSID.
93                     for (PmkCacheStoreData storeData : pmkDataList) {
94                         if (Objects.equals(storeData.bssid, bssid)) {
95                             existStoreData = storeData;
96                             break;
97                         }
98                     }
99                     if (null != existStoreData) {
100                         if (Objects.equals(existStoreData, newStoreData)) {
101                             if (mVerboseLoggingEnabled) {
102                                 Log.d(TAG, "PMK entry exists for the BSSID, skip it.");
103                             }
104                             return true;
105                         }
106                         pmkDataList.remove(existStoreData);
107                     }
108                 } else {
109                     for (PmkCacheStoreData storeData : pmkDataList) {
110                         if (Objects.equals(storeData, newStoreData)) {
111                             existStoreData = storeData;
112                             break;
113                         }
114                     }
115                     if (null != existStoreData) {
116                         if (mVerboseLoggingEnabled) {
117                             Log.d(TAG, "PMK entry exists, skip it.");
118                         }
119                         return true;
120                     }
121                 }
122             }
123 
124             pmkDataList.add(newStoreData);
125             if (mVerboseLoggingEnabled) {
126                 Log.d(TAG, "Network " + networkId + " PmkCache Count: " + pmkDataList.size());
127             }
128             updatePmkCacheExpiration();
129             return true;
130         }
131     }
132 
133     /**
134      * Remove PMK caches associated with the network ID.
135      *
136      * @param networkId the network ID of PMK caches to be removed.
137      * @return true when PMK caches are removed; otherwise, false.
138      */
remove(int networkId)139     public boolean remove(int networkId) {
140         synchronized (mLock) {
141             if (WifiConfiguration.INVALID_NETWORK_ID == networkId) return false;
142             if (!mPmkCacheEntries.contains(networkId)) return false;
143 
144             mPmkCacheEntries.remove(networkId);
145             updatePmkCacheExpiration();
146             return true;
147         }
148     }
149 
150     /**
151      * Remove PMK caches associated with the network ID when the interface
152      * MAC address is changed.
153      *
154      * @param networkId the network ID of PMK caches to be removed.
155      * @param curMacAddress current interface MAC address.
156      * @return true when PMK caches are removed; otherwise, false.
157      */
158 
remove(int networkId, MacAddress curMacAddress)159     public boolean remove(int networkId, MacAddress curMacAddress) {
160         synchronized (mLock) {
161             if (WifiConfiguration.INVALID_NETWORK_ID == networkId) return false;
162             List<PmkCacheStoreData> pmkDataList = mPmkCacheEntries.get(networkId);
163             if (null == pmkDataList) return false;
164 
165             pmkDataList.removeIf(pmkData -> !Objects.equals(curMacAddress, pmkData.macAddress));
166 
167             if (pmkDataList.size() == 0) {
168                 remove(networkId);
169             }
170             return true;
171         }
172     }
173 
174     /**
175      * Get PMK caches associated with the network ID.
176      *
177      * @param networkId the network ID to be queried.
178      * @return A list of PMK caches associated with the network ID.
179      *         If none of PMK cache is associated with the network ID, return null.
180      */
get(int networkId)181     public List<ArrayList<Byte>> get(int networkId) {
182         synchronized (mLock) {
183             List<PmkCacheStoreData> pmkDataList = mPmkCacheEntries.get(networkId);
184             if (WifiConfiguration.INVALID_NETWORK_ID == networkId) return null;
185             if (null == pmkDataList) return null;
186 
187             final long elapseTimeInSecond = mClock.getElapsedSinceBootMillis() / 1000;
188             List<ArrayList<Byte>> dataList = new ArrayList<>();
189             for (PmkCacheStoreData pmkData : pmkDataList) {
190                 if (pmkData.isValid(elapseTimeInSecond)) {
191                     dataList.add(pmkData.data);
192                 }
193             }
194             return dataList;
195         }
196     }
197 
198     /**
199      * Enable/Disable verbose logging.
200      */
enableVerboseLogging(boolean verboseEnabled)201     public void enableVerboseLogging(boolean verboseEnabled) {
202         mVerboseLoggingEnabled = verboseEnabled;
203     }
204 
205     @VisibleForTesting
updatePmkCacheExpiration()206     void updatePmkCacheExpiration() {
207         synchronized (mLock) {
208             mEventHandler.removeCallbacksAndMessages(PMK_CACHE_EXPIRATION_ALARM_TAG);
209 
210             long elapseTimeInSecond = mClock.getElapsedSinceBootMillis() / 1000;
211             long nextUpdateTimeInSecond = Long.MAX_VALUE;
212             if (mVerboseLoggingEnabled) {
213                 Log.d(TAG, "Update PMK cache expiration at " + elapseTimeInSecond);
214             }
215 
216             List<Integer> emptyStoreDataList = new ArrayList<>();
217             for (int i = 0; i < mPmkCacheEntries.size(); i++) {
218                 int networkId = mPmkCacheEntries.keyAt(i);
219                 List<PmkCacheStoreData> list = mPmkCacheEntries.get(networkId);
220                 list.removeIf(pmkData -> !pmkData.isValid(elapseTimeInSecond));
221                 if (list.size() == 0) {
222                     emptyStoreDataList.add(networkId);
223                     continue;
224                 }
225                 for (PmkCacheStoreData pmkData : list) {
226                     if (nextUpdateTimeInSecond > pmkData.expirationTimeInSec) {
227                         nextUpdateTimeInSecond = pmkData.expirationTimeInSec;
228                     }
229                 }
230             }
231             emptyStoreDataList.forEach(networkId -> mPmkCacheEntries.remove(networkId));
232 
233             // No need to arrange next update since there is no valid PMK in the cache.
234             if (nextUpdateTimeInSecond == Long.MAX_VALUE) {
235                 return;
236             }
237 
238             if (mVerboseLoggingEnabled) {
239                 Log.d(TAG, "PMK cache next expiration time: " + nextUpdateTimeInSecond);
240             }
241             long delayedTimeInMs = (nextUpdateTimeInSecond - elapseTimeInSecond) * 1000;
242             mEventHandler.postDelayed(
243                     () -> {
244                         updatePmkCacheExpiration();
245                     },
246                     PMK_CACHE_EXPIRATION_ALARM_TAG,
247                     (delayedTimeInMs > 0) ? delayedTimeInMs : 0);
248         }
249     }
250 
251     private static class PmkCacheStoreData {
252 
253         public MacAddress macAddress;
254         public MacAddress bssid;
255         public ArrayList<Byte> data;
256         public long expirationTimeInSec;
257 
PmkCacheStoreData(MacAddress macAddr, MacAddress bssAddr, ArrayList<Byte> serializedData, long timeInSec)258         PmkCacheStoreData(MacAddress macAddr, MacAddress bssAddr, ArrayList<Byte> serializedData,
259                 long timeInSec) {
260             macAddress = macAddr;
261             bssid = bssAddr;
262             data = serializedData;
263             expirationTimeInSec = timeInSec;
264         }
265 
266         /**
267          * Validate this PMK cache against the timestamp.
268          *
269          * @param currentTimeInSec the timestamp to be checked.
270          * @return true if this PMK cache is valid against the timestamp; otherwise, false.
271          */
isValid(long currentTimeInSec)272         public boolean isValid(long currentTimeInSec) {
273             return expirationTimeInSec > 0 && expirationTimeInSec > currentTimeInSec;
274         }
275 
276         @Override
equals(Object o)277         public boolean equals(Object o) {
278             if (this == o) return true;
279             if (!(o instanceof PmkCacheStoreData)) return false;
280             PmkCacheStoreData storeData = (PmkCacheStoreData) o;
281             return expirationTimeInSec == storeData.expirationTimeInSec
282                     && Objects.equals(macAddress, storeData.macAddress)
283                     && Objects.equals(data, storeData.data)
284                     && Objects.equals(bssid, storeData.bssid);
285         }
286 
287         @Override
hashCode()288         public int hashCode() {
289             return Objects.hash(macAddress, data, expirationTimeInSec, bssid);
290         }
291     }
292 }
293