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_ = bus_->GetObjectProxy(mmc::kMmcServiceName,
81                                         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_) bus_->ShutdownAndBlock();
91 }
92 
init(const ConfigParam config)93 int CodecClient::init(const ConfigParam config) {
94   cleanup();
95 
96   // Set up record logger.
97   record_logger_ = std::make_unique<MmcRttLogger>(CodecId(config));
98 
99   dbus::MethodCall method_call(mmc::kMmcServiceInterface,
100                                mmc::kCodecInitMethod);
101   dbus::MessageWriter writer(&method_call);
102 
103   mmc::CodecInitRequest request;
104   *request.mutable_config() = config;
105   if (!writer.AppendProtoAsArrayOfBytes(request)) {
106     log::error("Failed to encode CodecInitRequest protobuf");
107     return -EINVAL;
108   }
109 
110   std::unique_ptr<dbus::Response> dbus_response =
111       codec_manager_
112           ->CallMethodAndBlock(&method_call,
113                                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(),
152           sizeof(addr.sun_path) - 1);
153 
154   // Connect to socket for transcoding.
155   int rc =
156       connect(skt_fd_, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
157   if (rc < 0) {
158     log::error("Failed to connect socket: {}", strerror(errno));
159     return -errno;
160   }
161   unlink(addr.sun_path);
162   return response.input_frame_size();
163 }
164 
cleanup()165 void CodecClient::cleanup() {
166   if (skt_fd_ >= 0) {
167     close(skt_fd_);
168     skt_fd_ = -1;
169   }
170 
171   // Upload Rtt statics when the session ends.
172   if (record_logger_.get() != nullptr) {
173     record_logger_->UploadTranscodeRttStatics();
174     record_logger_.release();
175   }
176 
177   dbus::MethodCall method_call(mmc::kMmcServiceInterface,
178                                mmc::kCodecCleanUpMethod);
179 
180   std::unique_ptr<dbus::Response> dbus_response =
181       codec_manager_
182           ->CallMethodAndBlock(&method_call,
183                                dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)
184 // TODO(b/297976471): remove the build flag once libchrome uprev is done.
185 #if BASE_VER >= 1170299
186           .value_or(nullptr)
187 #endif
188       ;
189 
190   if (!dbus_response) {
191     log::warn("CodecCleanUp failed");
192   }
193   return;
194 }
195 
transcode(uint8_t * i_buf,int i_len,uint8_t * o_buf,int o_len)196 int CodecClient::transcode(uint8_t* i_buf, int i_len, uint8_t* o_buf,
197                            int o_len) {
198   // Start Timer
199   base::ElapsedTimer timer;
200 
201   // i_buf and o_buf cannot be null.
202   if (i_buf == nullptr || o_buf == nullptr) {
203     log::error("Buffer is null");
204     return -EINVAL;
205   }
206 
207   if (i_len <= 0 || o_len <= 0) {
208     log::error("Non-positive buffer length");
209     return -EINVAL;
210   }
211 
212   // Use MSG_NOSIGNAL to ignore SIGPIPE.
213   int rc = send(skt_fd_, i_buf, i_len, MSG_NOSIGNAL);
214 
215   if (rc < 0) {
216     log::error("Failed to send data: {}", strerror(errno));
217     return -errno;
218   }
219   // Full packet should be sent under SOCK_SEQPACKET setting.
220   if (rc < i_len) {
221     log::error("Failed to send full packet");
222     return -EIO;
223   }
224 
225   struct pollfd pfd;
226   pfd.fd = skt_fd_;
227   pfd.events = POLLIN;
228 
229   int pollret = poll(&pfd, 1, -1);
230   if (pollret < 0) {
231     log::error("Failed to poll: {}", strerror(errno));
232     return -errno;
233   }
234 
235   if (pfd.revents & (POLLHUP | POLLNVAL)) {
236     log::error("Socket closed remotely.");
237     return -EIO;
238   }
239 
240   // POLLIN is returned..
241   rc = recv(skt_fd_, o_buf, o_len, MSG_NOSIGNAL);
242   if (rc < 0) {
243     log::error("Failed to recv data: {}", strerror(errno));
244     return -errno;
245   }
246   // Should be able to recv data when POLLIN is returned.
247   if (rc == 0) {
248     log::error("Failed to recv data");
249     return -EIO;
250   }
251 
252   // End timer
253   record_logger_->RecordRtt(timer.Elapsed().InMicroseconds());
254 
255   return rc;
256 }
257 
258 }  // namespace mmc
259