diff --git a/internal/platform/implementation/linux/ble_l2cap_server_socket.cc b/internal/platform/implementation/linux/ble_l2cap_server_socket.cc index f1d0a9a9..eb452f62 100644 --- a/internal/platform/implementation/linux/ble_l2cap_server_socket.cc +++ b/internal/platform/implementation/linux/ble_l2cap_server_socket.cc @@ -49,18 +49,15 @@ std::unique_ptr BleL2capServerSocket::Accept() { psm_ = 0x80 + (prng.NextUint32() % 0x80); server_fd_ = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - int rcv = 1 << 20; // 1 MiB (kernel may clamp) - int snd = 1 << 20; - int err2 = setsockopt(server_fd_, SOL_SOCKET, SO_RCVBUF, &rcv, sizeof(rcv)); - int err3 = setsockopt(server_fd_, SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd)); - - LOG(INFO) << __func__ << ": Using server_fd: " << server_fd_; - if (server_fd_ < 0 or err2 == -1 or err3 == -1) { + if (server_fd_ < 0) { LOG(ERROR) << "Failed to create L2CAP server socket: " << std::strerror(errno); return nullptr; } + + LOG(INFO) << __func__ << ": Using server_fd: " << server_fd_; + struct sockaddr_l2 addr; std::memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; @@ -77,6 +74,15 @@ std::unique_ptr BleL2capServerSocket::Accept() { return nullptr; } + struct l2cap_options opts; + opts.omtu = 0; + opts.imtu = 672; + + if (setsockopt(server_fd_, SOL_BLUETOOTH, BT_RCVMTU, &opts.imtu, + sizeof(opts.imtu)) < 0) { + LOG(ERROR) << "Failed to set socket options on L2CAP server socket" ; + } + if (listen(server_fd_, 5) < 0) { LOG(ERROR) << "Failed to listen on L2CAP server socket: " << std::strerror(errno); diff --git a/internal/platform/implementation/linux/ble_l2cap_socket.cc b/internal/platform/implementation/linux/ble_l2cap_socket.cc index 9dd4d14d..395d9a6d 100644 --- a/internal/platform/implementation/linux/ble_l2cap_socket.cc +++ b/internal/platform/implementation/linux/ble_l2cap_socket.cc @@ -23,13 +23,13 @@ #include #include #include +#include #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/logging.h" -#include #include "bluetooth_classic_socket.h" @@ -107,7 +107,6 @@ static size_t GetBleCocRcvMtu(int fd) { if (getsockopt(fd, SOL_BLUETOOTH, BT_RCVMTU, &mtu, &len) == 0 && mtu > 0) return mtu; - // Fallback ONLY (avoid 23 unless you're sure; 23 causes truncation if peer sends bigger SDUs). return 512; } @@ -125,19 +124,9 @@ ExceptionOr BleL2capInputStream::Read(std::int64_t size) { if (size <= 0) { return ExceptionOr(ByteArray{}); } + LOG(INFO) << "Reading"; - // Serve any buffered bytes first. - if (!pending_.empty()) { - size_t to_return = std::min(static_cast(size), pending_.size()); - std::string out = pending_.substr(0, to_return); - pending_.erase(0, to_return); - LOG(INFO) << __func__ << ": returning " << to_return - << " bytes from pending buffer, data=0x" - << HexPreview(out.data(), out.size()); - return ExceptionOr(ByteArray(std::move(out))); - } - - auto poller = Poller::CreateInputPoller(fd); + auto poller = Poller::CreateOutputPoller(fd); while (true) { if (fd_.load() != fd) return {Exception::kIo}; @@ -145,32 +134,17 @@ ExceptionOr BleL2capInputStream::Read(std::int64_t size) { if (result.Raised()) return result; if (fd_.load() != fd) return {Exception::kIo}; - // Peek the next message length without consuming it. - // For seqpacket/dgram, MSG_TRUNC makes recv() return the *full* message length - // even if the buffer is smaller. - ssize_t msg_len = ::recv(fd, nullptr, 0, MSG_PEEK | MSG_TRUNC); - if (msg_len < 0) { - if (errno == EINTR) continue; - if (errno == EAGAIN || errno == EWOULDBLOCK) continue; - if (errno == EBADF) { - LOG(INFO) << __func__ << ": socket was closed during read"; - return {Exception::kIo}; - } - LOG(ERROR) << __func__ << ": error peeking message length: " - << std::strerror(errno); - return {Exception::kIo}; - } - if (msg_len == 0) { - LOG(INFO) << __func__ << ": socket closed (EOF)"; - return {Exception::kIo}; - } - - size_t want = static_cast(msg_len); + // Read one SDU (kernel gives one per recv on SOCK_SEQPACKET). + // Always allocate a buffer up to the receive MTU since the kernel may + // deliver a complete SDU that's larger than the requested size. + // We'll trim to the requested size afterwards. + size_t recv_mtu = GetBleCocRcvMtu(fd); std::string buffer; - buffer.resize(want); + buffer.resize(recv_mtu); if (fd_.load() != fd) return {Exception::kIo}; - ssize_t n = ::recv(fd, buffer.data(), want, 0); + ssize_t n = ::recv(fd, buffer.data(), buffer.size(), 0); + LOG(INFO)<< "Received something"; if (n < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) continue; @@ -182,63 +156,22 @@ ExceptionOr BleL2capInputStream::Read(std::int64_t size) { << std::strerror(errno); return {Exception::kIo}; } - if (n == 0) { - LOG(INFO) << __func__ << ": socket closed (EOF)"; - return {Exception::kIo}; - } + // if (n == 0) { + // LOG(INFO) << __func__ << ": socket closed (EOF)"; + // return {Exception::kIo}; + // } + LOG(INFO)<< "Got this many "<< static_cast(n); buffer.resize(static_cast(n)); - // Swallow BLE L2CAP control packets used to switch to data connection. - // Android sends length-prefixed 0x03 on the L2CAP channel before the - // ConnectionRequestFrame. Respond with length-prefixed 0x17 and continue. - if (buffer.size() == 5 && - static_cast(buffer[0]) == 0x00 && - static_cast(buffer[1]) == 0x00 && - static_cast(buffer[2]) == 0x00 && - static_cast(buffer[3]) == 0x01 && - static_cast(buffer[4]) == 0x03) { - char reply[5] = {0x00, 0x00, 0x00, 0x01, - static_cast(0x17)}; - ssize_t wrote = ::send(fd, reply, sizeof(reply), -#ifdef MSG_NOSIGNAL - MSG_NOSIGNAL -#else - 0 -#endif - ); - if (wrote < 0) { - LOG(WARNING) << __func__ - << ": failed to send data-connection-ready reply: " - << std::strerror(errno); - } - continue; + if (buffer.size() != static_cast(size)) { + LOG(WARNING) << __func__ << ": size mismatch recv=" << buffer.size() + << " expected=" << size; } - - if (buffer.size() == 1 && - static_cast(buffer[0]) == 0x03) { - char reply = static_cast(0x17); - ssize_t wrote = ::send(fd, &reply, sizeof(reply), -#ifdef MSG_NOSIGNAL - MSG_NOSIGNAL -#else - 0 -#endif - ); - if (wrote < 0) { - LOG(WARNING) << __func__ - << ": failed to send data-connection-ready reply: " - << std::strerror(errno); - } - continue; - } - - // If the packet is larger than requested size, buffer the remainder. + // If the packet is larger than requested size, drop the remainder to stay + // close to l2test semantics (one recv consumes one frame). size_t to_return = std::min(static_cast(size), buffer.size()); std::string out = buffer.substr(0, to_return); - if (buffer.size() > to_return) { - pending_.assign(buffer.data() + to_return, buffer.size() - to_return); - } LOG(INFO) << __func__ << ": returning " << to_return << " bytes from SDU size " << buffer.size() << ", data=0x" << HexPreview(out.data(), out.size()); @@ -263,39 +196,31 @@ Exception BleL2capOutputStream::Write(const ByteArray& data) { auto poller = Poller::CreateOutputPoller(fd); - size_t total_wrote = 0; - if (data.Empty()) { return {Exception::kSuccess}; } + size_t max_chunk_size = GetL2capOutputMtu(fd); if (max_chunk_size == 0) { max_chunk_size = kDefaultBleL2capMtu; } + LOG(INFO) << "BleL2capOutputStream::Write bytes=" << data.size() - << " mtu=" << max_chunk_size << " data=0x" - << HexPreview(data.data(), data.size()); - while (total_wrote < data.size()) { + << " mtu=" << max_chunk_size + << " data=0x" << HexPreview(data.data(), data.size()); + + size_t offset = 0; + while (offset < data.size()) { if (fd_.load() != fd) return {Exception::kIo}; - auto result = poller.Ready(); // should wait for POLLOUT/EPOLLOUT + auto result = poller.Ready(); // wait for POLLOUT/EPOLLOUT if (result.Raised()) return result; if (fd_.load() != fd) return {Exception::kIo}; - const char *buf = data.data(); - size_t remaining = data.size() - total_wrote; + size_t chunk = std::min(max_chunk_size, data.size() - offset); - size_t to_write = remaining; - { - // For SEQPACKET/DGRAM, one send() == one packet. - // Cap to discovered “MTU-like” limit to avoid EMSGSIZE. - absl::MutexLock lock(&fd_mutex_); - to_write = std::min(to_write, max_chunk_size); - } - - // Prefer send() to avoid SIGPIPE (MSG_NOSIGNAL is Linux). ssize_t wrote = ::send(fd, - buf + total_wrote, - to_write, + data.data() + offset, + chunk, #ifdef MSG_NOSIGNAL MSG_NOSIGNAL #else @@ -303,25 +228,14 @@ Exception BleL2capOutputStream::Write(const ByteArray& data) { #endif ); - // If send() isn’t appropriate in your environment, you can swap back to write(). - // ssize_t wrote = ::write(fd_.get(), buf + total_wrote, to_write); - if (wrote < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) continue; if (errno == EMSGSIZE) { - // Our packet is too large; shrink max_chunk_ and retry. - { - absl::MutexLock lock(&fd_mutex_); - if (max_chunk_size > 1) { - max_chunk_size = std::max(1, max_chunk_size / 2); - LOG(INFO) << __func__ << ": EMSGSIZE; reducing max_chunk_ to " - << max_chunk_size; - continue; // retry with smaller chunk - } - } - LOG(ERROR) << __func__ << ": EMSGSIZE even at 1 byte"; + LOG(ERROR) << __func__ + << ": EMSGSIZE sending " << chunk + << " bytes; peer MTU " << max_chunk_size; return {Exception::kIo}; } @@ -337,12 +251,17 @@ Exception BleL2capOutputStream::Write(const ByteArray& data) { } if (wrote == 0) { - // For sockets, 0 usually means peer closed. LOG(INFO) << __func__ << ": peer closed during write"; return {Exception::kIo}; } - total_wrote += static_cast(wrote); + if (static_cast(wrote) != chunk) { + LOG(ERROR) << __func__ << ": partial packet write (" + << wrote << "/" << chunk << ")"; + return {Exception::kIo}; + } + + offset += static_cast(wrote); } return {Exception::kSuccess}; diff --git a/internal/platform/implementation/linux/ble_l2cap_socket.h b/internal/platform/implementation/linux/ble_l2cap_socket.h index c5bd4489..ceb0455f 100644 --- a/internal/platform/implementation/linux/ble_l2cap_socket.h +++ b/internal/platform/implementation/linux/ble_l2cap_socket.h @@ -33,12 +33,11 @@ class BleL2capInputStream final : public InputStream { explicit BleL2capInputStream(int fd); ~BleL2capInputStream() override; - ExceptionOr Read(std::int64_t size) override; + ExceptionOr Read(std::int64_t size) override; Exception Close() override; private: std::atomic fd_{-1}; - std::string pending_; // holds unread bytes from full SDUs }; class BleL2capOutputStream final : public OutputStream { diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index 7f20439e..3d5a8b06 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -551,6 +551,9 @@ std::unique_ptr BleV2Medium::ConnectOverL2cap( return nullptr; } + // Set receive MTU before connect (for LE CoC) + + struct sockaddr_l2 addr; std::memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; @@ -561,6 +564,17 @@ std::unique_ptr BleV2Medium::ConnectOverL2cap( addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; } + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOG(INFO) << "Failed to bind L2CAP socket"; + } + + struct l2cap_options opts; + opts.omtu = 0; + opts.imtu = 672; + if (setsockopt(fd, SOL_BLUETOOTH, BT_RCVMTU, &opts.imtu, sizeof(opts.imtu)) < 0) { + LOG(WARNING) << __func__ << ": Failed to set BT_RCVMTU: " + << std::strerror(errno); + } std::string mac_addr = device->GetMacAddress(); if (str2ba(mac_addr.c_str(), &addr.l2_bdaddr) < 0) { LOG(ERROR) << __func__ << ": Invalid Bluetooth address: " << mac_addr;