mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
linux <-> linux l2cap socket works now
This commit is contained in:
@@ -49,18 +49,15 @@ std::unique_ptr<api::ble_v2::BleL2capSocket> 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<api::ble_v2::BleL2capSocket> 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);
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#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 <sys/ioctl.h>
|
||||
|
||||
#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<ByteArray> BleL2capInputStream::Read(std::int64_t size) {
|
||||
if (size <= 0) {
|
||||
return ExceptionOr<ByteArray>(ByteArray{});
|
||||
}
|
||||
LOG(INFO) << "Reading";
|
||||
|
||||
// Serve any buffered bytes first.
|
||||
if (!pending_.empty()) {
|
||||
size_t to_return = std::min(static_cast<size_t>(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>(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<ByteArray> 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<size_t>(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<ByteArray> 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<size_t>(n);
|
||||
|
||||
buffer.resize(static_cast<size_t>(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<unsigned char>(buffer[0]) == 0x00 &&
|
||||
static_cast<unsigned char>(buffer[1]) == 0x00 &&
|
||||
static_cast<unsigned char>(buffer[2]) == 0x00 &&
|
||||
static_cast<unsigned char>(buffer[3]) == 0x01 &&
|
||||
static_cast<unsigned char>(buffer[4]) == 0x03) {
|
||||
char reply[5] = {0x00, 0x00, 0x00, 0x01,
|
||||
static_cast<char>(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_t>(size)) {
|
||||
LOG(WARNING) << __func__ << ": size mismatch recv=" << buffer.size()
|
||||
<< " expected=" << size;
|
||||
}
|
||||
|
||||
if (buffer.size() == 1 &&
|
||||
static_cast<unsigned char>(buffer[0]) == 0x03) {
|
||||
char reply = static_cast<char>(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_t>(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<size_t>(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<size_t>(wrote);
|
||||
if (static_cast<size_t>(wrote) != chunk) {
|
||||
LOG(ERROR) << __func__ << ": partial packet write ("
|
||||
<< wrote << "/" << chunk << ")";
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
offset += static_cast<size_t>(wrote);
|
||||
}
|
||||
|
||||
return {Exception::kSuccess};
|
||||
|
||||
@@ -33,12 +33,11 @@ class BleL2capInputStream final : public InputStream {
|
||||
explicit BleL2capInputStream(int fd);
|
||||
~BleL2capInputStream() override;
|
||||
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override;
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
std::atomic<int> fd_{-1};
|
||||
std::string pending_; // holds unread bytes from full SDUs
|
||||
};
|
||||
|
||||
class BleL2capOutputStream final : public OutputStream {
|
||||
|
||||
@@ -551,6 +551,9 @@ std::unique_ptr<api::ble_v2::BleL2capSocket> 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<api::ble_v2::BleL2capSocket> 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;
|
||||
|
||||
Reference in New Issue
Block a user