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 package com.android.server.selinux;
17 
18 import com.android.internal.annotations.VisibleForTesting;
19 import com.android.internal.os.Clock;
20 
21 import java.time.Duration;
22 import java.time.Instant;
23 
24 /**
25  * A QuotaLimiter allows to define a maximum number of Atom pushes within a specific time window.
26  *
27  * <p>The limiter divides the time line in windows of a fixed size. Every time a new permit is
28  * requested, the limiter checks whether the previous request was in the same time window as the
29  * current one. If the two windows are the same, it grants a permit only if the number of permits
30  * granted within the window does not exceed the quota. If the two windows are different, it resets
31  * the quota.
32  */
33 public class QuotaLimiter {
34 
35     private final Clock mClock;
36     private final Duration mWindowSize;
37 
38     private int mMaxPermits;
39     private long mCurrentWindow;
40     private int mPermitsGranted;
41 
42     @VisibleForTesting
QuotaLimiter(Clock clock, Duration windowSize, int maxPermits)43     QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
44         mClock = clock;
45         mWindowSize = windowSize;
46         mMaxPermits = maxPermits;
47     }
48 
QuotaLimiter(Duration windowSize, int maxPermits)49     public QuotaLimiter(Duration windowSize, int maxPermits) {
50         this(Clock.SYSTEM_CLOCK, windowSize, maxPermits);
51     }
52 
QuotaLimiter(int maxPermitsPerDay)53     public QuotaLimiter(int maxPermitsPerDay) {
54         this(Clock.SYSTEM_CLOCK, Duration.ofDays(1), maxPermitsPerDay);
55     }
56 
57     /**
58      * Acquires a permit if there is one available in the current time window.
59      *
60      * @return true if a permit was acquired.
61      */
acquire()62     boolean acquire() {
63         long nowWindow =
64                 Duration.between(Instant.EPOCH, Instant.ofEpochMilli(mClock.currentTimeMillis()))
65                         .dividedBy(mWindowSize);
66         if (nowWindow > mCurrentWindow) {
67             mCurrentWindow = nowWindow;
68             mPermitsGranted = 0;
69         }
70 
71         if (mPermitsGranted < mMaxPermits) {
72             mPermitsGranted++;
73             return true;
74         }
75 
76         return false;
77     }
78 
setMaxPermits(int maxPermits)79     public void setMaxPermits(int maxPermits) {
80         this.mMaxPermits = maxPermits;
81     }
82 }
83