1 /*
2  * Copyright (C) 2013 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 #define TLOG_TAG "stats-consumer"
17 
18 #include <cstddef>
19 
20 #include <inttypes.h>
21 #include <lib/tipc/tipc.h>
22 #include <lk/err_ptr.h>
23 #include <sys/auxv.h>
24 #include <sys/mman.h>
25 #include <trusty/sys/mman.h>
26 #include <trusty/time.h>
27 #include <trusty_log.h>
28 #include <uapi/mm.h>
29 
30 #include <binder/RpcServerTrusty.h>
31 #include <binder/RpcTransportTipcTrusty.h>
32 #include <stdio.h>
33 
34 #include <android/trusty/stats/tz/BnStats.h>
35 #include <android/trusty/stats/tz/IStatsSetter.h>
36 #include <consumer_consts.h>
37 #include <consumer_ctl.h>
38 #include <relayer_consts.h>
39 
40 using android::String16;
41 using android::binder::Status;
42 using android::frameworks::stats::VendorAtom;
43 using android::frameworks::stats::VendorAtomValue;
44 using android::trusty::stats::tz::BnStats;
45 using android::trusty::stats::tz::IStatsSetter;
46 
47 /* How long to sleep between spin lock checks */
48 constexpr uint64_t kSpinSleepMs = 10;
49 
50 /**
51  * DOC: The consumer TA is exposing a test control port to share
52  * received vendorAtoms with the stats-test TA via shared memory.
53  * As soon as the test control port is connected, the consumer will connect
54  * to the producer and write the received atoms into the memory address
55  * shared with the stats-test TA.
56  */
57 
58 class StatsConsumer;
59 
60 static tipc_port_acl test_ctl_port_acl = {
61         .flags = IPC_PORT_ALLOW_TA_CONNECT,
62         .uuid_num = 0,
63         .uuids = nullptr,
64         .extra_data = nullptr,
65 };
66 
67 static tipc_port test_ctl_port = {
68         .name = CONSUMER_PORT_TEST_CTL,
69         .msg_max_size = sizeof(ConsumerCtlMsg),
70         .msg_queue_len = 1,
71         .acl = &test_ctl_port_acl,
72         .priv = nullptr,
73 };
74 
75 struct ConsumerCtx {
76     void* shm_ptr;
77     int atom_cnt;
78     android::sp<StatsConsumer> stats_consumer;
79     android::sp<IStatsSetter> istats_setter;
80 };
81 
82 static ConsumerCtx consumer_ctx = {
83         .shm_ptr = nullptr,
84         .atom_cnt = -1,
85 };
86 
87 /**
88  * test_ctl_on_connect() - on test_ctl connect the connection with
89  * the producer is made and vendorAtoms can be received
90  */
test_ctl_on_connect(const tipc_port * port,handle_t chan,const uuid * peer,void ** ctx_p)91 int test_ctl_on_connect(const tipc_port* port,
92                         handle_t chan,
93                         const uuid* peer,
94                         void** ctx_p) {
95     if (consumer_ctx.atom_cnt != -1) {
96         *ctx_p = nullptr;
97         return ERR_BAD_STATE;
98     }
99 
100     // make sure consumer is ready
101     assert(consumer_ctx.stats_consumer);
102     assert(!consumer_ctx.istats_setter);
103     assert(!consumer_ctx.shm_ptr);
104     assert(consumer_ctx.atom_cnt == -1);
105     consumer_ctx.atom_cnt = 0;
106 
107     // connect to the producer
108     android::sp<android::RpcSession> sess_stats_setter;
109     android::sp<IStatsSetter> srv_stats_setter;
110 
111     TLOGD("Connecting to the producer via IStatsSetter\n");
112     int rc = connect(RELAYER_PORT_ISTATS_SETTER_SECURE_WORLD,
113                      IPC_CONNECT_WAIT_FOR_PORT);
114     TLOGD("connected to IStatsSetter\n");
115     if (rc < 0) {
116         TLOGE("Couldn't connect to IStatsSetter::PORT\n");
117         return rc;
118     }
119     android::sp<android::RpcSession> sess = android::RpcSession::make(
120             android::RpcTransportCtxFactoryTipcTrusty::make());
121     if (sess == nullptr) {
122         TLOGE("Couldn't make RPC session\n");
123         return ERR_GENERIC;
124     }
125     android::binder::unique_fd chan_fd;
126     chan_fd.reset(rc);
127     android::status_t status = sess->setupPreconnectedClient(
128             std::move(chan_fd), []() { return android::binder::unique_fd(); });
129     if (status != android::OK) {
130         TLOGE("error during setupPreconnectedClient\n");
131         return ERR_GENERIC;
132     }
133     android::sp<android::IBinder> root = sess->getRootObject();
134     if (root == nullptr) {
135         TLOGE("Couldn't get root object\n");
136         return ERR_GENERIC;
137     }
138     consumer_ctx.istats_setter = IStatsSetter::asInterface(root);
139     assert(consumer_ctx.istats_setter);
140     *ctx_p = &consumer_ctx;
141 
142     return NO_ERROR;
143 }
144 
145 /**
146  * test_ctl_on_message() - test_ctl on_message allows the consumer
147  * to receive the shared memory region to which received vendorAtoms will
148  * be copied.
149  */
test_ctl_on_message(const tipc_port * port,handle_t chan,void * ctx_)150 static int test_ctl_on_message(const tipc_port* port,
151                                handle_t chan,
152                                void* ctx_) {
153     assert(ctx_);
154     assert(port == &test_ctl_port);
155     ConsumerCtx* ctx = static_cast<ConsumerCtx*>(ctx_);
156     ConsumerCtlMsg request;
157 
158     handle_t shm_handle = INVALID_IPC_HANDLE;
159     iovec iov = {
160             .iov_base = &request,
161             .iov_len = sizeof(request),
162     };
163     ipc_msg msg = {
164             .num_iov = 1,
165             .iov = &iov,
166             .num_handles = 1,
167             .handles = &shm_handle,
168     };
169 
170     ipc_msg_info msg_inf;
171     int rc = get_msg(chan, &msg_inf);
172     if (rc) {
173         return rc;
174     }
175 
176     if (msg_inf.num_handles != 1) {
177         TLOGE("Message had no handles\n");
178         put_msg(chan, msg_inf.id);
179         return ERR_INVALID_ARGS;
180     }
181 
182     rc = read_msg(chan, msg_inf.id, 0, &msg);
183     put_msg(chan, msg_inf.id);
184     if (static_cast<size_t>(rc) != sizeof(request)) {
185         TLOGE("Failed to read message (rc=%d)\n", rc);
186         close(shm_handle);
187         return ERR_INVALID_ARGS;
188     }
189 
190     size_t page_size = PAGE_SIZE;  // getauxval(AT_PAGESZ);
191     switch (request.cmd) {
192     case CONSUMER_CTL_SHM_SHARE:
193         assert(!ctx->shm_ptr);
194         ctx->shm_ptr =
195                 mmap(0, page_size, PROT_READ | PROT_WRITE, 0, shm_handle, 0);
196         if (ctx->shm_ptr == MAP_FAILED) {
197             close(shm_handle);
198             ctx->shm_ptr = nullptr;
199             TLOGE("Failed to mmap handle (rc=%d)\n", rc);
200             return ERR_NO_MEMORY;
201         }
202         break;
203     case CONSUMER_CTL_SHM_RECLAIM:
204         if (ctx->shm_ptr) {
205             munmap((void*)ctx->shm_ptr, page_size);
206             ctx->shm_ptr = nullptr;
207         }
208         break;
209     default:
210         TLOGE("Invalid cmd %d\n", request.cmd);
211     }
212 
213     close(shm_handle);
214 
215     return NO_ERROR;
216 }
217 
test_ctl_on_channel_cleanup(void * ctx_)218 static void test_ctl_on_channel_cleanup(void* ctx_) {
219     return;
220 }
221 
222 class StatsConsumer : public BnStats {
223 public:
reportVendorAtom(const VendorAtom & vendorAtom)224     Status reportVendorAtom(const VendorAtom& vendorAtom) {
225         TLOGD("reportVendorAtom atomId=%d.\n", vendorAtom.atomId);
226         if (consumer_ctx.shm_ptr) {
227             volatile ShmContent* content = (ShmContent*)consumer_ctx.shm_ptr;
228 
229             // Spin and sleep until the other side consumes the active value
230             while (content->full.load(std::memory_order_acquire)) {
231                 trusty_nanosleep(0, 0, MS_TO_NS(kSpinSleepMs));
232             }
233 
234             TLOGD("content->atom_id=%d.\n", content->atom_id);
235             TLOGD("content->reverse_domain_name=%s.\n",
236                   content->reverse_domain_name);
237             content->atom_id = vendorAtom.atomId;
238 
239             size_t last_atom_str_idx = SHM_CONTENT_VENDOR_ATOM_STR_SIZE - 1;
240             strncpy((char*)content->reverse_domain_name,
241                     android::String8(vendorAtom.reverseDomainName).c_str(),
242                     last_atom_str_idx);
243             content->reverse_domain_name[last_atom_str_idx] = '\0';
244 
245             int idx = 0;
246             for (const auto& input_value : vendorAtom.values) {
247                 VendorAtomValue::Tag tag = input_value.getTag();
248                 TLOGD("vendorAtom.values[%d].getTag = %d.\n", idx, tag);
249 
250                 volatile ShmVendorAtomValue& atom_val =
251                         content->vendor_atom_values[idx];
252                 switch (tag) {
253                 case VendorAtomValue::intValue:
254                     atom_val.i = input_value.get<VendorAtomValue::intValue>();
255                     break;
256                 case VendorAtomValue::longValue:
257                     atom_val.l = input_value.get<VendorAtomValue::longValue>();
258                     break;
259                 case VendorAtomValue::floatValue:
260                     atom_val.f = input_value.get<VendorAtomValue::floatValue>();
261                     break;
262                 case VendorAtomValue::stringValue: {
263                     auto s = android::String8(
264                             input_value.get<VendorAtomValue::stringValue>());
265                     char* buf = const_cast<char*>(atom_val.s);
266                     strncpy(buf, s.c_str(), last_atom_str_idx);
267                     buf[last_atom_str_idx] = '\0';
268                     break;
269                 }
270                 default:
271                     TLOGE("Unexpected vendorAtom.values[%d].getTag = %d.\n",
272                           idx, tag);
273                 }
274                 atom_val.tag = tag;
275 
276                 if (++idx >= SHM_CONTENT_VENDOR_ATOM_VALUES) {
277                     break;
278                 }
279             }
280             content->full.store(true, std::memory_order_release);
281         }
282         return Status::ok();
283     }
284 };
285 
286 static tipc_srv_ops test_ctl_ops = {
287         .on_connect = test_ctl_on_connect,
288         .on_message = test_ctl_on_message,
289         .on_channel_cleanup = test_ctl_on_channel_cleanup,
290 };
291 
main(void)292 int main(void) {
293     TLOGI("Starting StatsConsumer\n");
294 
295     tipc_hset* hset = tipc_hset_create();
296     if (IS_ERR(hset)) {
297         TLOGE("Failed to create handle set (%d)\n", PTR_ERR(hset));
298         return EXIT_FAILURE;
299     }
300 
301     const auto port_acl = android::RpcServerTrusty::PortAcl{
302             .flags = IPC_PORT_ALLOW_TA_CONNECT,
303     };
304 
305     TLOGE("Creating Consumer (exposing IStats)\n");
306     consumer_ctx.stats_consumer = android::sp<StatsConsumer>::make();
307 
308     int rc = tipc_add_service(hset, &test_ctl_port, 1, 1, &test_ctl_ops);
309     if (rc < 0) {
310         return rc;
311     }
312 
313     uint32_t timeout = INFINITE_TIME;
314     do {
315         // Changing timeout interval from infinite to simulate a timer
316         rc = tipc_handle_event(hset, timeout);
317         if (consumer_ctx.shm_ptr) {
318             TLOGD("interacting with producer\n");
319             consumer_ctx.istats_setter->setInterface(
320                     consumer_ctx.stats_consumer);
321             timeout = TIME_OUT_INTERVAL_MS;
322             TLOGD("timeout = TIME_OUT_INTERVAL_MS\n");
323         } else {
324             timeout = INFINITE_TIME;
325             TLOGD("timeout = INFINITE_TIME\n");
326         }
327     } while (rc == 0 || timeout == TIME_OUT_INTERVAL_MS);
328 
329     TLOGE("stats-consumer service died\n");
330 
331     return rc;
332 }
333