1 /*
2 **
3 ** Copyright 2018, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 #ifndef KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_
19 #define KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_
20 
21 #include <android/hardware/confirmationui/1.0/types.h>
22 #include <chrono>
23 #include <stdint.h>
24 #include <sys/types.h>
25 #include <tuple>
26 #include <unordered_map>
27 
28 namespace keystore {
29 
30 using ConfirmationResponseCode = android::hardware::confirmationui::V1_0::ResponseCode;
31 
32 using std::chrono::time_point;
33 using std::chrono::duration;
34 
35 template <typename Clock = std::chrono::steady_clock> class RateLimiting {
36   private:
37     struct Slot {
38         Slot() : previous_start{}, prompt_start{}, counter(0) {}
39         typename Clock::time_point previous_start;
40         typename Clock::time_point prompt_start;
41         uint32_t counter;
42     };
43 
44     std::unordered_map<uid_t, Slot> slots_;
45 
46     uint_t latest_requester_;
47 
48     static std::chrono::seconds getBackoff(uint32_t counter) {
49         using namespace std::chrono_literals;
50         switch (counter) {
51         case 0:
52         case 1:
53         case 2:
54             return 0s;
55         case 3:
56         case 4:
57         case 5:
58             return 30s;
59         default:
60             return 60s * (1ULL << (counter - 6));
61         }
62     }
63 
64   public:
65     // Exposes the number of used slots. This is only used by the test to verify the assumption
66     // about used counter slots.
67     size_t usedSlots() const { return slots_.size(); }
68     void doGC() {
69         using namespace std::chrono_literals;
70         using std::chrono::system_clock;
71         using std::chrono::time_point_cast;
72         auto then = Clock::now() - 24h;
73         auto iter = slots_.begin();
74         while (iter != slots_.end()) {
75             if (iter->second.prompt_start <= then) {
76                 iter = slots_.erase(iter);
77             } else {
78                 ++iter;
79             }
80         }
81     }
82 
83     bool tryPrompt(uid_t id) {
84         using namespace std::chrono_literals;
85         // remove slots that have not been touched in 24 hours
86         doGC();
87         auto& slot = slots_[id];
88         auto now = Clock::now();
89         if (!slot.counter || slot.prompt_start <= now - getBackoff(slot.counter)) {
90             latest_requester_ = id;
91             slot.counter += 1;
92             slot.previous_start = slot.prompt_start;
93             slot.prompt_start = now;
94             return true;
95         }
96         return false;
97     }
98 
99     void processResult(ConfirmationResponseCode rc) {
100         switch (rc) {
101         case ConfirmationResponseCode::OK:
102             // reset the counter slot
103             slots_.erase(latest_requester_);
104             return;
105         case ConfirmationResponseCode::Canceled:
106             // nothing to do here
107             return;
108         default:;
109         }
110 
111         // roll back latest request
112         auto& slot = slots_[latest_requester_];
113         if (slot.counter <= 1) {
114             slots_.erase(latest_requester_);
115             return;
116         }
117         slot.counter -= 1;
118         slot.prompt_start = slot.previous_start;
119     }
120 };
121 
122 }  // namespace keystore
123 
124 #endif  // KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_
125