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