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