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 #include <aidl/android/os/BnStatsSubscriptionCallback.h>
18 #include <aidl/android/os/IStatsd.h>
19 #include <aidl/android/os/StatsSubscriptionCallbackReason.h>
20 #include <android/binder_auto_utils.h>
21 #include <stats_provider.h>
22 #include <stats_subscription.h>
23 
24 #include <atomic>
25 #include <map>
26 #include <vector>
27 
28 using Status = ::ndk::ScopedAStatus;
29 using aidl::android::os::BnStatsSubscriptionCallback;
30 using aidl::android::os::IStatsd;
31 using aidl::android::os::StatsSubscriptionCallbackReason;
32 using ::ndk::SharedRefBase;
33 
34 class Subscription;
35 
36 // Mutex for accessing subscriptions map.
37 static std::mutex subscriptionsMutex;
38 
39 // TODO(b/271039569): Store subscriptions in a singleton object.
40 // Added subscriptions keyed by their subscription ID.
41 static std::map</* subscription ID */ int32_t, std::shared_ptr<Subscription>> subscriptions;
42 
43 class Subscription : public BnStatsSubscriptionCallback {
44 public:
Subscription(const int32_t subscriptionId,const std::vector<uint8_t> & subscriptionConfig,const AStatsManager_SubscriptionCallback callback,void * cookie)45     Subscription(const int32_t subscriptionId, const std::vector<uint8_t>& subscriptionConfig,
46                  const AStatsManager_SubscriptionCallback callback, void* cookie)
47         : mSubscriptionId(subscriptionId),
48           mSubscriptionParamsBytes(subscriptionConfig),
49           mCallback(callback),
50           mCookie(cookie) {
51     }
52 
onSubscriptionData(const StatsSubscriptionCallbackReason reason,const std::vector<uint8_t> & subscriptionPayload)53     Status onSubscriptionData(const StatsSubscriptionCallbackReason reason,
54                               const std::vector<uint8_t>& subscriptionPayload) override {
55         std::vector<uint8_t> mutablePayload = subscriptionPayload;
56         mCallback(mSubscriptionId, static_cast<AStatsManager_SubscriptionCallbackReason>(reason),
57                   mutablePayload.data(), mutablePayload.size(), mCookie);
58 
59         std::shared_ptr<Subscription> thisSubscription;
60         if (reason == StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED) {
61             std::lock_guard<std::mutex> lock(subscriptionsMutex);
62 
63             auto subscriptionsIt = subscriptions.find(mSubscriptionId);
64             if (subscriptionsIt != subscriptions.end()) {
65                 // Ensure this subscription's refcount doesn't hit 0 when we erase it from the
66                 // subscriptions map by adding a local reference here.
67                 thisSubscription = subscriptionsIt->second;
68 
69                 subscriptions.erase(subscriptionsIt);
70             }
71         }
72 
73         return Status::ok();
74     }
75 
getSubscriptionParamsBytes() const76     const std::vector<uint8_t>& getSubscriptionParamsBytes() const {
77         return mSubscriptionParamsBytes;
78     }
79 
80 private:
81     const int32_t mSubscriptionId;
82     const std::vector<uint8_t> mSubscriptionParamsBytes;
83     const AStatsManager_SubscriptionCallback mCallback;
84     void* mCookie;
85 };
86 
87 // forward declare so it can be referenced in StatsProvider constructor.
88 static void onStatsBinderRestart();
89 
90 static std::shared_ptr<StatsProvider> statsProvider =
91         std::make_shared<StatsProvider>(onStatsBinderRestart);
92 
onStatsBinderRestart()93 static void onStatsBinderRestart() {
94     const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
95     if (statsService == nullptr) {
96         return;
97     }
98 
99     // Since we do not want to make an IPC with the lock held, we first create a
100     // copy of the data with the lock held before iterating through the map.
101     std::map<int32_t, std::shared_ptr<Subscription>> subscriptionsCopy;
102     {
103         std::lock_guard<std::mutex> lock(subscriptionsMutex);
104         subscriptionsCopy = subscriptions;
105     }
106     for (const auto& [_, subscription] : subscriptionsCopy) {
107         statsService->addSubscription(subscription->getSubscriptionParamsBytes(), subscription);
108     }
109 }
110 
getNextSubscriptionId()111 static int32_t getNextSubscriptionId() {
112     static std::atomic_int32_t nextSubscriptionId(0);
113     return ++nextSubscriptionId;
114 }
115 
getBinderCallbackForSubscription(const int32_t subscription_id)116 static std::shared_ptr<Subscription> getBinderCallbackForSubscription(
117         const int32_t subscription_id) {
118     std::lock_guard<std::mutex> lock(subscriptionsMutex);
119     auto subscriptionsIt = subscriptions.find(subscription_id);
120     if (subscriptionsIt == subscriptions.end()) {
121         return nullptr;
122     }
123     return subscriptionsIt->second;
124 }
125 
AStatsManager_addSubscription(const uint8_t * subscription_config,const size_t num_bytes,const AStatsManager_SubscriptionCallback callback,void * cookie)126 int32_t AStatsManager_addSubscription(const uint8_t* subscription_config, const size_t num_bytes,
127                                       const AStatsManager_SubscriptionCallback callback,
128                                       void* cookie) {
129     const std::vector<uint8_t> subscriptionConfig(subscription_config,
130                                                   subscription_config + num_bytes);
131     const int32_t subscriptionId(getNextSubscriptionId());
132     std::shared_ptr<Subscription> subscription =
133             SharedRefBase::make<Subscription>(subscriptionId, subscriptionConfig, callback, cookie);
134 
135     {
136         std::lock_guard<std::mutex> lock(subscriptionsMutex);
137 
138         subscriptions[subscriptionId] = subscription;
139     }
140 
141     // TODO(b/270648168): Queue the binder call to not block on binder
142     const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
143     if (statsService != nullptr) {
144         statsService->addSubscription(subscriptionConfig, subscription);
145     }
146 
147     return subscriptionId;
148 }
149 
AStatsManager_removeSubscription(const int32_t subscription_id)150 void AStatsManager_removeSubscription(const int32_t subscription_id) {
151     std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
152     if (subscription == nullptr) {
153         return;
154     }
155 
156     // TODO(b/270648168): Queue the binder call to not block on binder
157     const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
158     if (statsService == nullptr) {
159         // Statsd not available.
160         // TODO(b/270656443): keep track of removeSubscription request and make the IPC call when
161         // statsd binder comes back up.
162         return;
163     }
164     statsService->removeSubscription(subscription);
165 }
166 
AStatsManager_flushSubscription(const int32_t subscription_id)167 void AStatsManager_flushSubscription(const int32_t subscription_id) {
168     std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
169     if (subscription == nullptr) {
170         return;
171     }
172 
173     // TODO(b/270648168): Queue the binder call to not block on binder
174     const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
175     if (statsService == nullptr) {
176         // Statsd not available.
177         // TODO(b/270656443): keep track of flushSubscription request and make the IPC call when
178         // statsd binder comes back up.
179         return;
180     }
181 
182     // TODO(b/273649282): Ensure the subscription is cleared in case the final Binder data
183     // callback fails.
184     statsService->flushSubscription(subscription);
185 }
186