• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 android.healthconnect;
18 
19 import static android.health.connect.ratelimiter.RateLimiter.CHUNK_SIZE_LIMIT_IN_BYTES;
20 import static android.health.connect.ratelimiter.RateLimiter.RECORD_SIZE_LIMIT_IN_BYTES;
21 
22 import static org.hamcrest.CoreMatchers.containsString;
23 
24 import android.health.connect.HealthConnectException;
25 import android.health.connect.ratelimiter.RateLimiter;
26 import android.health.connect.ratelimiter.RateLimiter.QuotaCategory;
27 
28 import com.android.server.healthconnect.HealthConnectDeviceConfigManager;
29 
30 import org.junit.Before;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.junit.rules.ExpectedException;
34 
35 import java.time.Duration;
36 import java.time.Instant;
37 import java.util.HashMap;
38 import java.util.Map;
39 
40 public class RateLimiterTest {
41     private static final int UID = 1;
42     private static final boolean IS_IN_FOREGROUND_TRUE = true;
43     private static final boolean IS_IN_FOREGROUND_FALSE = false;
44     private static final int MAX_FOREGROUND_CALL_15M = 1000;
45     private static final int MAX_BACKGROUND_CALL_15M = 1000;
46     private static final Duration WINDOW_15M = Duration.ofMinutes(15);
47 
48     @Rule public ExpectedException exception = ExpectedException.none();
49 
50     @Before
setUp()51     public void setUp() {
52         Map<Integer, Integer> quotaBucketToMaxApiCallQuotaMap = new HashMap<>();
53         Map<String, Integer> quotaBucketToMaxMemoryQuotaMap = new HashMap<>();
54         quotaBucketToMaxApiCallQuotaMap.put(
55                 RateLimiter.QuotaBucket.QUOTA_BUCKET_READS_PER_15M_FOREGROUND,
56                 HealthConnectDeviceConfigManager
57                         .QUOTA_BUCKET_PER_15M_FOREGROUND_DEFAULT_FLAG_VALUE);
58         quotaBucketToMaxApiCallQuotaMap.put(
59                 RateLimiter.QuotaBucket.QUOTA_BUCKET_READS_PER_15M_BACKGROUND,
60                 HealthConnectDeviceConfigManager
61                         .QUOTA_BUCKET_PER_15M_BACKGROUND_DEFAULT_FLAG_VALUE);
62         quotaBucketToMaxApiCallQuotaMap.put(
63                 RateLimiter.QuotaBucket.QUOTA_BUCKET_WRITES_PER_15M_FOREGROUND,
64                 HealthConnectDeviceConfigManager
65                         .QUOTA_BUCKET_PER_15M_FOREGROUND_DEFAULT_FLAG_VALUE);
66         quotaBucketToMaxApiCallQuotaMap.put(
67                 RateLimiter.QuotaBucket.QUOTA_BUCKET_WRITES_PER_15M_BACKGROUND,
68                 HealthConnectDeviceConfigManager
69                         .QUOTA_BUCKET_PER_15M_BACKGROUND_DEFAULT_FLAG_VALUE);
70         quotaBucketToMaxApiCallQuotaMap.put(
71                 RateLimiter.QuotaBucket.QUOTA_BUCKET_READS_PER_24H_FOREGROUND,
72                 HealthConnectDeviceConfigManager
73                         .QUOTA_BUCKET_PER_24H_FOREGROUND_DEFAULT_FLAG_VALUE);
74         quotaBucketToMaxApiCallQuotaMap.put(
75                 RateLimiter.QuotaBucket.QUOTA_BUCKET_READS_PER_24H_BACKGROUND,
76                 HealthConnectDeviceConfigManager
77                         .QUOTA_BUCKET_PER_24H_BACKGROUND_DEFAULT_FLAG_VALUE);
78         quotaBucketToMaxApiCallQuotaMap.put(
79                 RateLimiter.QuotaBucket.QUOTA_BUCKET_WRITES_PER_24H_FOREGROUND,
80                 HealthConnectDeviceConfigManager
81                         .QUOTA_BUCKET_PER_24H_FOREGROUND_DEFAULT_FLAG_VALUE);
82         quotaBucketToMaxApiCallQuotaMap.put(
83                 RateLimiter.QuotaBucket.QUOTA_BUCKET_WRITES_PER_24H_BACKGROUND,
84                 HealthConnectDeviceConfigManager
85                         .QUOTA_BUCKET_PER_24H_BACKGROUND_DEFAULT_FLAG_VALUE);
86         quotaBucketToMaxMemoryQuotaMap.put(
87                 CHUNK_SIZE_LIMIT_IN_BYTES,
88                 HealthConnectDeviceConfigManager.CHUNK_SIZE_LIMIT_IN_BYTES_DEFAULT_FLAG_VALUE);
89         quotaBucketToMaxMemoryQuotaMap.put(
90                 RECORD_SIZE_LIMIT_IN_BYTES,
91                 HealthConnectDeviceConfigManager.RECORD_SIZE_LIMIT_IN_BYTES_DEFAULT_FLAG_VALUE);
92         RateLimiter.updateApiCallQuotaMap(quotaBucketToMaxApiCallQuotaMap);
93         RateLimiter.updateMemoryQuotaMap(quotaBucketToMaxMemoryQuotaMap);
94         RateLimiter.updateEnableRateLimiterFlag(true);
95     }
96 
97     @Test
testTryAcquireApiCallQuota_invalidQuotaCategory()98     public void testTryAcquireApiCallQuota_invalidQuotaCategory() {
99         RateLimiter.clearCache();
100         @QuotaCategory.Type int quotaCategory = 0;
101         exception.expect(IllegalArgumentException.class);
102         exception.expectMessage("Quota category not defined.");
103         RateLimiter.tryAcquireApiCallQuota(UID, quotaCategory, IS_IN_FOREGROUND_TRUE);
104     }
105 
106     @Test
testTryAcquireApiCallQuota_unmeteredForegroundCalls()107     public void testTryAcquireApiCallQuota_unmeteredForegroundCalls() {
108         RateLimiter.clearCache();
109         @QuotaCategory.Type int quotaCategory = 1;
110         tryAcquireCallQuotaNTimes(
111                 quotaCategory, IS_IN_FOREGROUND_TRUE, MAX_FOREGROUND_CALL_15M + 1);
112     }
113 
114     @Test
testTryAcquireApiCallQuota_unmeteredBackgroundCalls()115     public void testTryAcquireApiCallQuota_unmeteredBackgroundCalls() {
116         RateLimiter.clearCache();
117         @QuotaCategory.Type int quotaCategory = 1;
118         tryAcquireCallQuotaNTimes(
119                 quotaCategory, IS_IN_FOREGROUND_TRUE, MAX_BACKGROUND_CALL_15M + 1);
120     }
121 
122     @Test
testTryAcquireApiCallQuota_meteredForegroundCallsInLimit()123     public void testTryAcquireApiCallQuota_meteredForegroundCallsInLimit() {
124         RateLimiter.clearCache();
125         @QuotaCategory.Type int quotaCategoryRead = 2;
126         tryAcquireCallQuotaNTimes(
127                 quotaCategoryRead, IS_IN_FOREGROUND_TRUE, MAX_FOREGROUND_CALL_15M);
128     }
129 
130     @Test
testTryAcquireApiCallQuota_meteredBackgroundCallsInLimit()131     public void testTryAcquireApiCallQuota_meteredBackgroundCallsInLimit() {
132         RateLimiter.clearCache();
133         @QuotaCategory.Type int quotaCategoryWrite = 3;
134         tryAcquireCallQuotaNTimes(
135                 quotaCategoryWrite, IS_IN_FOREGROUND_FALSE, MAX_BACKGROUND_CALL_15M);
136     }
137 
138     @Test
testTryAcquireApiCallQuota_meteredForegroundCallsLimitExceeded()139     public void testTryAcquireApiCallQuota_meteredForegroundCallsLimitExceeded() {
140         RateLimiter.clearCache();
141         @QuotaCategory.Type int quotaCategoryRead = 2;
142         Instant startTime = Instant.now();
143         tryAcquireCallQuotaNTimes(
144                 quotaCategoryRead, IS_IN_FOREGROUND_TRUE, MAX_FOREGROUND_CALL_15M);
145         Instant endTime = Instant.now();
146         int ceilQuotaAcquired =
147                 getCeilQuotaAcquired(startTime, endTime, WINDOW_15M, MAX_FOREGROUND_CALL_15M);
148         exception.expect(HealthConnectException.class);
149         exception.expectMessage(containsString("API call quota exceeded"));
150         tryAcquireCallQuotaNTimes(quotaCategoryRead, IS_IN_FOREGROUND_TRUE, ceilQuotaAcquired);
151     }
152 
153     @Test
testTryAcquireApiCallQuota_meteredBackgroundCallsLimitExceeded()154     public void testTryAcquireApiCallQuota_meteredBackgroundCallsLimitExceeded() {
155         RateLimiter.clearCache();
156         @QuotaCategory.Type int quotaCategoryWrite = 3;
157         Instant startTime = Instant.now();
158         tryAcquireCallQuotaNTimes(
159                 quotaCategoryWrite, IS_IN_FOREGROUND_FALSE, MAX_BACKGROUND_CALL_15M);
160         Instant endTime = Instant.now();
161         int ceilQuotaAcquired =
162                 getCeilQuotaAcquired(startTime, endTime, WINDOW_15M, MAX_BACKGROUND_CALL_15M);
163         exception.expect(HealthConnectException.class);
164         exception.expectMessage(containsString("API call quota exceeded"));
165         tryAcquireCallQuotaNTimes(quotaCategoryWrite, IS_IN_FOREGROUND_FALSE, ceilQuotaAcquired);
166     }
167 
168     @Test
checkMaxChunkMemoryUsage_LimitExceeded()169     public void checkMaxChunkMemoryUsage_LimitExceeded() {
170         long valueExceeding = 5000001;
171         exception.expect(HealthConnectException.class);
172         exception.expectMessage(
173                 "Records chunk size exceeded the max chunk limit: 5000000, was: 5000001");
174         RateLimiter.checkMaxChunkMemoryUsage(valueExceeding);
175     }
176 
177     @Test
checkMaxChunkMemoryUsage_inLimit()178     public void checkMaxChunkMemoryUsage_inLimit() {
179         long value = 5000000;
180         RateLimiter.checkMaxChunkMemoryUsage(value);
181     }
182 
183     @Test
checkMaxRecordMemoryUsage_LimitExceeded()184     public void checkMaxRecordMemoryUsage_LimitExceeded() {
185         long valueExceeding = 1000001;
186         exception.expect(HealthConnectException.class);
187         exception.expectMessage(
188                 "Record size exceeded the single record size limit: 1000000, was: 1000001");
189         RateLimiter.checkMaxRecordMemoryUsage(valueExceeding);
190     }
191 
192     @Test
checkMaxRecordMemoryUsage_inLimit()193     public void checkMaxRecordMemoryUsage_inLimit() {
194         long value = 1000000;
195         RateLimiter.checkMaxRecordMemoryUsage(value);
196     }
197 
getCeilQuotaAcquired( Instant startTime, Instant endTime, Duration window, int maxQuota)198     private int getCeilQuotaAcquired(
199             Instant startTime, Instant endTime, Duration window, int maxQuota) {
200         Duration timeSpent = Duration.between(startTime, endTime);
201         float accumulated = timeSpent.toMillis() * ((float) maxQuota / (float) window.toMillis());
202         return accumulated > (int) accumulated
203                 ? (int) Math.ceil(accumulated)
204                 : (int) accumulated + 1;
205     }
206 
tryAcquireCallQuotaNTimes( @uotaCategory.Type int quotaCategory, boolean isInForeground, int nTimes)207     private void tryAcquireCallQuotaNTimes(
208             @QuotaCategory.Type int quotaCategory, boolean isInForeground, int nTimes) {
209         for (int i = 0; i < nTimes; i++) {
210             RateLimiter.tryAcquireApiCallQuota(UID, quotaCategory, isInForeground);
211         }
212     }
213 }
214