• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::duration;
33 using std::chrono::time_point;
34 
35 template <typename Clock = std::chrono::steady_clock> class RateLimiting {
36   private:
37     struct Slot {
SlotSlot38         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 
getBackoff(uint32_t counter)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.
usedSlots()67     size_t usedSlots() const { return slots_.size(); }
doGC()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 
tryPrompt(uid_t id)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     // The app is penalized for cancelling a request. Request are rolled back only if
100     // the prompt was cancelled by the system: e.g. a system error or asynchronous event.
101     // When the user cancels the prompt, it is subject to rate limiting.
102     static constexpr const uint_t kInvalidRequester = -1;
103 
cancelPrompt()104     void cancelPrompt() { latest_requester_ = kInvalidRequester; }
105 
processResult(ConfirmationResponseCode rc)106     void processResult(ConfirmationResponseCode rc) {
107         if (latest_requester_ == kInvalidRequester) {
108             return;
109         }
110         switch (rc) {
111         case ConfirmationResponseCode::OK:
112             // reset the counter slot
113             slots_.erase(latest_requester_);
114             return;
115         case ConfirmationResponseCode::Canceled:
116             // nothing to do here
117             return;
118         default:;
119         }
120 
121         // roll back latest request
122         auto& slot = slots_[latest_requester_];
123         if (slot.counter <= 1) {
124             slots_.erase(latest_requester_);
125             return;
126         }
127         slot.counter -= 1;
128         slot.prompt_start = slot.previous_start;
129     }
130 };
131 
132 }  // namespace keystore
133 
134 #endif  // KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_
135