1 /*
2  * Copyright 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 "mmc/codec_client/codec_client.h"
18 
19 #include <base/timer/elapsed_timer.h>
20 #include <bluetooth/log.h>
21 #include <dbus/bus.h>
22 #include <dbus/message.h>
23 #include <dbus/object_proxy.h>
24 #include <poll.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <unistd.h>
28 
29 #include <cerrno>
30 #include <cstring>
31 
32 #include "mmc/daemon/constants.h"
33 #include "mmc/metrics/mmc_rtt_logger.h"
34 #include "mmc/proto/mmc_config.pb.h"
35 #include "mmc/proto/mmc_service.pb.h"
36 
37 namespace mmc {
38 namespace {
39 
40 using namespace bluetooth;
41 
42 // Codec param field number in |ConfigParam|
43 const int kUnsupportedType = -1;
44 const int kHfpLc3EncoderId = 1;
45 const int kHfpLc3DecoderId = 2;
46 const int kA2dpAacEncoderId = 5;
47 
48 // Maps |ConfigParam| proto field to int, because proto-lite does not support
49 // reflection.
CodecId(const ConfigParam & config)50 int CodecId(const ConfigParam& config) {
51   if (config.has_hfp_lc3_encoder_param()) {
52     return kHfpLc3EncoderId;
53   } else if (config.has_hfp_lc3_decoder_param()) {
54     return kHfpLc3DecoderId;
55   } else if (config.has_a2dp_aac_encoder_param()) {
56     return kA2dpAacEncoderId;
57   } else {
58     log::warn("Unsupported codec type is used.");
59     return kUnsupportedType;
60   }
61 }
62 }  // namespace
63 
CodecClient()64 CodecClient::CodecClient() {
65   skt_fd_ = -1;
66   codec_manager_ = nullptr;
67   record_logger_ = nullptr;
68 
69   // Set up DBus connection.
70   dbus::Bus::Options options;
71   options.bus_type = dbus::Bus::SYSTEM;
72   bus_ = new dbus::Bus(options);
73 
74   if (!bus_->Connect()) {
75     log::error("Failed to connect system bus");
76     return;
77   }
78 
79   // Get proxy to send DBus method call.
80   codec_manager_ =
81           bus_->GetObjectProxy(mmc::kMmcServiceName, dbus::ObjectPath(mmc::kMmcServicePath));
82   if (!codec_manager_) {
83     log::error("Failed to get object proxy");
84     return;
85   }
86 }
87 
~CodecClient()88 CodecClient::~CodecClient() {
89   cleanup();
90   if (bus_) {
91     bus_->ShutdownAndBlock();
92   }
93 }
94 
init(const ConfigParam config)95 int CodecClient::init(const ConfigParam config) {
96   cleanup();
97 
98   // Set up record logger.
99   record_logger_ = std::make_unique<MmcRttLogger>(CodecId(config));
100 
101   dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecInitMethod);
102   dbus::MessageWriter writer(&method_call);
103 
104   mmc::CodecInitRequest request;
105   *request.mutable_config() = config;
106   if (!writer.AppendProtoAsArrayOfBytes(request)) {
107     log::error("Failed to encode CodecInitRequest protobuf");
108     return -EINVAL;
109   }
110 
111   std::unique_ptr<dbus::Response> dbus_response =
112           codec_manager_
113                   ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)
114 // TODO(b/297976471): remove the build flag once libchrome uprev is done.
115 #if BASE_VER >= 1170299
116                   .value_or(nullptr)
117 #endif
118           ;
119 
120   if (!dbus_response) {
121     log::error("CodecInit failed");
122     return -ECOMM;
123   }
124 
125   dbus::MessageReader reader(dbus_response.get());
126   mmc::CodecInitResponse response;
127   if (!reader.PopArrayOfBytesAsProto(&response)) {
128     log::error("Failed to parse response protobuf");
129     return -EINVAL;
130   }
131 
132   if (response.socket_token().empty()) {
133     log::error("CodecInit returned empty socket token");
134     return -EBADMSG;
135   }
136 
137   if (response.input_frame_size() < 0) {
138     log::error("CodecInit returned negative frame size");
139     return -EBADMSG;
140   }
141 
142   // Create socket.
143   skt_fd_ = socket(AF_UNIX, SOCK_SEQPACKET, 0);
144   if (skt_fd_ < 0) {
145     log::error("Failed to create socket: {}", strerror(errno));
146     return -errno;
147   }
148 
149   struct sockaddr_un addr = {};
150   addr.sun_family = AF_UNIX;
151   strncpy(addr.sun_path, response.socket_token().c_str(), sizeof(addr.sun_path) - 1);
152 
153   // Connect to socket for transcoding.
154   int rc = connect(skt_fd_, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
155   if (rc < 0) {
156     log::error("Failed to connect socket: {}", strerror(errno));
157     return -errno;
158   }
159   unlink(addr.sun_path);
160   return response.input_frame_size();
161 }
162 
cleanup()163 void CodecClient::cleanup() {
164   if (skt_fd_ >= 0) {
165     close(skt_fd_);
166     skt_fd_ = -1;
167   }
168 
169   // Upload Rtt statics when the session ends.
170   if (record_logger_.get() != nullptr) {
171     record_logger_->UploadTranscodeRttStatics();
172     record_logger_.release();
173   }
174 
175   dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecCleanUpMethod);
176 
177   std::unique_ptr<dbus::Response> dbus_response =
178           codec_manager_
179                   ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)
180 // TODO(b/297976471): remove the build flag once libchrome uprev is done.
181 #if BASE_VER >= 1170299
182                   .value_or(nullptr)
183 #endif
184           ;
185 
186   if (!dbus_response) {
187     log::warn("CodecCleanUp failed");
188   }
189   return;
190 }
191 
transcode(uint8_t * i_buf,int i_len,uint8_t * o_buf,int o_len)192 int CodecClient::transcode(uint8_t* i_buf, int i_len, uint8_t* o_buf, int o_len) {
193   // Start Timer
194   base::ElapsedTimer timer;
195 
196   // i_buf and o_buf cannot be null.
197   if (i_buf == nullptr || o_buf == nullptr) {
198     log::error("Buffer is null");
199     return -EINVAL;
200   }
201 
202   if (i_len <= 0 || o_len <= 0) {
203     log::error("Non-positive buffer length");
204     return -EINVAL;
205   }
206 
207   // Use MSG_NOSIGNAL to ignore SIGPIPE.
208   int rc = send(skt_fd_, i_buf, i_len, MSG_NOSIGNAL);
209 
210   if (rc < 0) {
211     log::error("Failed to send data: {}", strerror(errno));
212     return -errno;
213   }
214   // Full packet should be sent under SOCK_SEQPACKET setting.
215   if (rc < i_len) {
216     log::error("Failed to send full packet");
217     return -EIO;
218   }
219 
220   struct pollfd pfd;
221   pfd.fd = skt_fd_;
222   pfd.events = POLLIN;
223 
224   int pollret = poll(&pfd, 1, -1);
225   if (pollret < 0) {
226     log::error("Failed to poll: {}", strerror(errno));
227     return -errno;
228   }
229 
230   if (pfd.revents & (POLLHUP | POLLNVAL)) {
231     log::error("Socket closed remotely.");
232     return -EIO;
233   }
234 
235   // POLLIN is returned..
236   rc = recv(skt_fd_, o_buf, o_len, MSG_NOSIGNAL);
237   if (rc < 0) {
238     log::error("Failed to recv data: {}", strerror(errno));
239     return -errno;
240   }
241   // Should be able to recv data when POLLIN is returned.
242   if (rc == 0) {
243     log::error("Failed to recv data");
244     return -EIO;
245   }
246 
247   // End timer
248   record_logger_->RecordRtt(timer.Elapsed().InMicroseconds());
249 
250   return rc;
251 }
252 
253 }  // namespace mmc
254