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