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