summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2022-01-12 05:43:28 +0100
committerSven Gothel <[email protected]>2022-01-12 05:43:28 +0100
commitfd4c43ff56b5ca83b6e225a108f1eb751f5c6203 (patch)
treed7790181481791fc14d530a8bccc28ca6adbecc0
parent83c7e0459e381e2cd412959ff5917bfce2b80ecf (diff)
Refine L2CAPComm and add L2CAPServer (listen for connecting remote devices)
-rw-r--r--api/direct_bt/L2CAPComm.hpp106
-rw-r--r--src/direct_bt/L2CAPComm.cpp324
2 files changed, 344 insertions, 86 deletions
diff --git a/api/direct_bt/L2CAPComm.hpp b/api/direct_bt/L2CAPComm.hpp
index e9bb96d8..d5b0e2fe 100644
--- a/api/direct_bt/L2CAPComm.hpp
+++ b/api/direct_bt/L2CAPComm.hpp
@@ -107,15 +107,17 @@ namespace direct_bt {
}
};
+ class L2CAPServer; // fwd
+
/**
- * Read/Write L2CAP communication channel.
+ * L2CAP read/write communication channel to remote device
*/
class L2CAPComm {
public:
enum class Defaults : int {
L2CAP_CONNECT_MAX_RETRY = 3
};
- static constexpr int number(const Defaults d) { return static_cast<int>(d); }
+ static constexpr int number(const Defaults d) noexcept { return static_cast<int>(d); }
/**
* Exit code for read() and write() operations
@@ -141,12 +143,12 @@ namespace direct_bt {
return getRWExitCodeString( toRWExitCode( ecn ) );
}
- static std::string getStateString(bool isOpen, bool hasIOError) {
+ static std::string getStateString(bool isOpen, bool hasIOError) noexcept {
return "State[open "+std::to_string(isOpen)+
", ioError "+std::to_string(hasIOError)+
", errno "+std::to_string(errno)+" ("+std::string(strerror(errno))+")]";
}
- static std::string getStateString(bool isOpen, bool isInterrupted, bool hasIOError) {
+ static std::string getStateString(bool isOpen, bool isInterrupted, bool hasIOError) noexcept {
return "State[open "+std::to_string(isOpen)+
", isIRQed "+std::to_string(isInterrupted)+
", ioError "+std::to_string(hasIOError)+
@@ -154,31 +156,42 @@ namespace direct_bt {
}
private:
- static int l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid);
- static int l2cap_close_dev(int dd);
+ friend class L2CAPServer;
+
+ static int l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
+ static int l2cap_close_dev(int dd) noexcept;
const L2CAPEnv & env;
- const BDAddressAndType adapterAddressAndType;
+
+ public:
+ const BDAddressAndType localAddressAndType;
const L2CAP_PSM psm;
const L2CAP_CID cid;
+ private:
std::recursive_mutex mtx_write;
- BDAddressAndType deviceAddressAndType;
- std::atomic<int> socket_descriptor; // the l2cap socket
+ BDAddressAndType remoteAddressAndType;
+ std::atomic<int> client_socket; // the l2cap socket
std::atomic<bool> is_open; // reflects state
std::atomic<bool> has_ioerror; // reflects state
std::atomic<bool> interrupt_flag; // for forced disconnect
std::atomic<pthread_t> tid_connect;
std::atomic<pthread_t> tid_read;
- bool setBTSecurityLevelImpl(const BTSecurityLevel sec_level);
- BTSecurityLevel getBTSecurityLevelImpl();
+ bool setBTSecurityLevelImpl(const BTSecurityLevel sec_level) noexcept;
+ BTSecurityLevel getBTSecurityLevelImpl() noexcept;
public:
/**
* Constructing a non connected L2CAP channel instance for the pre-defined PSM and CID.
*/
- L2CAPComm(const BDAddressAndType& adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid);
+ L2CAPComm(const BDAddressAndType& adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
+
+ /**
+ * Constructing a connected L2CAP channel instance for the pre-defined PSM and CID.
+ */
+ L2CAPComm(const BDAddressAndType& adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid,
+ const BDAddressAndType& remoteAddressAndType, int client_socket) noexcept;
L2CAPComm(const L2CAPComm&) = delete;
void operator=(const L2CAPComm&) = delete;
@@ -196,39 +209,42 @@ namespace direct_bt {
* @param sec_level sec_level < BTSecurityLevel::NONE will not set security level
* @return true if connection has been established, otherwise false
*/
- bool open(const BTDevice& device, const BTSecurityLevel sec_level=BTSecurityLevel::NONE);
+ bool open(const BTDevice& device, const BTSecurityLevel sec_level=BTSecurityLevel::NONE) noexcept;
- bool isOpen() const { return is_open; }
+ bool isOpen() const noexcept { return is_open; }
+
+ const BDAddressAndType& getRemoteAddressAndType() const noexcept { return remoteAddressAndType; }
/** Closing the L2CAP channel, locking {@link #mutex_write()}. */
bool close() noexcept;
/** Return this L2CAP socket descriptor. */
- inline int getSocketDescriptor() const noexcept { return socket_descriptor; }
+ inline int getSocket() const noexcept { return client_socket; }
- bool hasIOError() const { return has_ioerror; }
- std::string getStateString() const { return getStateString(is_open, interrupt_flag, has_ioerror); }
+ bool hasIOError() const noexcept { return has_ioerror; }
+ std::string getStateString() const noexcept { return getStateString(is_open, interrupt_flag, has_ioerror); }
/** Return the recursive write mutex for multithreading access. */
- std::recursive_mutex & mutex_write() { return mtx_write; }
+ std::recursive_mutex & mutex_write() noexcept { return mtx_write; }
/**
* If sec_level > ::BTSecurityLevel::UNSET, sets the BlueZ's L2CAP socket BT_SECURITY sec_level, determining the SMP security mode per connection.
- * <p>
+ *
* To unset security, the L2CAP socket should be closed and opened again.
- * </p>
*
- * @param sec_level sec_level < ::BTSecurityLevel::NONE will not set security level and returns false.
+ * If setting the security level fails, close() will be called.
+ *
+ * @param sec_level sec_level == ::BTSecurityLevel::UNSET will not set security level and returns true.
* @return true if a security level > ::BTSecurityLevel::UNSET has been set successfully, false if no security level has been set or if it failed.
*/
- bool setBTSecurityLevel(const BTSecurityLevel sec_level);
+ bool setBTSecurityLevel(const BTSecurityLevel sec_level) noexcept;
/**
* Fetches the current BlueZ's L2CAP socket BT_SECURITY sec_level.
*
* @return ::BTSecurityLevel sec_level value, ::BTSecurityLevel::UNSET if failure
*/
- BTSecurityLevel getBTSecurityLevel();
+ BTSecurityLevel getBTSecurityLevel() noexcept;
/**
* Generic read, w/o locking suitable for a unique ringbuffer sink. Using L2CAPEnv::L2CAP_READER_POLL_TIMEOUT.
@@ -236,7 +252,7 @@ namespace direct_bt {
* @param capacity
* @return number of bytes read if >= 0, otherwise L2CAPComm::ExitCode error code.
*/
- jau::snsize_t read(uint8_t* buffer, const jau::nsize_t capacity);
+ jau::snsize_t read(uint8_t* buffer, const jau::nsize_t capacity) noexcept;
/**
* Generic write, locking {@link #mutex_write()}.
@@ -244,7 +260,47 @@ namespace direct_bt {
* @param length
* @return number of bytes written if >= 0, otherwise L2CAPComm::ExitCode error code.
*/
- jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length);
+ jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length) noexcept;
+
+ std::string toString();
+ };
+
+ /**
+ * L2CAP server socket to listen for connecting remote devices
+ */
+ class L2CAPServer {
+ private:
+ const BDAddressAndType localAddressAndType;
+ const L2CAP_PSM psm;
+ const L2CAP_CID cid;
+
+ std::recursive_mutex mtx_open;
+ std::atomic<int> server_socket; // the server socket
+ std::atomic<bool> is_open; // reflects state
+ std::atomic<bool> interrupt_flag; // for forced disconnect
+ std::atomic<pthread_t> tid_accept;
+
+ public:
+ L2CAPServer(const BDAddressAndType& localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
+
+ L2CAPServer(const L2CAPServer&) = delete;
+ void operator=(const L2CAPServer&) = delete;
+
+ /** Destructor closing the L2CAP channel, see {@link #disconnect()}. */
+ ~L2CAPServer() noexcept { close(); }
+
+ bool open() noexcept;
+
+ bool isOpen() const noexcept { return is_open; }
+
+ bool close() noexcept;
+
+ /** Return this L2CAP socket descriptor. */
+ inline int getSocket() const noexcept { return server_socket; }
+
+ std::unique_ptr<L2CAPComm> accept() noexcept;
+
+ std::string toString();
};
} // namespace direct_bt
diff --git a/src/direct_bt/L2CAPComm.cpp b/src/direct_bt/L2CAPComm.cpp
index 0e2fce90..2a060198 100644
--- a/src/direct_bt/L2CAPComm.cpp
+++ b/src/direct_bt/L2CAPComm.cpp
@@ -61,7 +61,7 @@ L2CAPEnv::L2CAPEnv() noexcept
{
}
-int L2CAPComm::l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) {
+int L2CAPComm::l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept {
sockaddr_l2 a;
int fd, err;
@@ -96,7 +96,7 @@ failed:
return -1;
}
-int L2CAPComm::l2cap_close_dev(int dd)
+int L2CAPComm::l2cap_close_dev(int dd) noexcept
{
return ::close(dd);
}
@@ -106,15 +106,24 @@ int L2CAPComm::l2cap_close_dev(int dd)
// *************************************************
// *************************************************
-L2CAPComm::L2CAPComm(const BDAddressAndType& adapterAddressAndType_, const L2CAP_PSM psm_, const L2CAP_CID cid_)
+L2CAPComm::L2CAPComm(const BDAddressAndType& adapterAddressAndType_, const L2CAP_PSM psm_, const L2CAP_CID cid_) noexcept
: env(L2CAPEnv::get()),
- adapterAddressAndType(adapterAddressAndType_),
+ localAddressAndType(adapterAddressAndType_),
psm(psm_), cid(cid_),
- deviceAddressAndType(BDAddressAndType::ANY_BREDR_DEVICE),
- socket_descriptor(-1),
+ remoteAddressAndType(BDAddressAndType::ANY_BREDR_DEVICE),
+ client_socket(-1),
is_open(false), has_ioerror(false), interrupt_flag(false), tid_connect(0), tid_read(0)
{ }
+L2CAPComm::L2CAPComm(const BDAddressAndType& adapterAddressAndType_, const L2CAP_PSM psm_, const L2CAP_CID cid_,
+ const BDAddressAndType& remoteAddressAndType_, int client_socket_) noexcept
+: env(L2CAPEnv::get()),
+ localAddressAndType(adapterAddressAndType_),
+ psm(psm_), cid(cid_),
+ remoteAddressAndType(remoteAddressAndType_),
+ client_socket(client_socket_),
+ is_open(0 <= client_socket_), has_ioerror(false), interrupt_flag(false), tid_connect(0), tid_read(0)
+{ }
/**
* Setting BT_SECURITY within open() after bind() and before connect()
@@ -127,13 +136,13 @@ L2CAPComm::L2CAPComm(const BDAddressAndType& adapterAddressAndType_, const L2CAP
*/
#define SET_BT_SECURITY_POST_CONNECT 1
-bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) {
+bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) noexcept {
bool expOpen = false; // C++11, exp as value since C++20
if( !is_open.compare_exchange_strong(expOpen, true) ) {
DBG_PRINT("L2CAPComm::open(%s, %s): Already open: dd %d, %s, psm %s, cid %s; %s",
device.getAddressAndType().toString().c_str(), to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return false;
@@ -157,7 +166,7 @@ bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) {
* - l2cap_connect with destination address
* -- connect(fd, ..)
*/
- deviceAddressAndType = device.getAddressAndType();
+ remoteAddressAndType = device.getAddressAndType();
/** BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ */
sockaddr_l2 req;
@@ -165,13 +174,13 @@ bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) {
int to_retry_count=0; // ETIMEDOUT retry count
DBG_PRINT("L2CAPComm::open: Start Connect: dd %d, %s, psm %s, cid %s, sec_level %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(), to_string(sec_level).c_str(),
getStateString().c_str());
- socket_descriptor = l2cap_open_dev(adapterAddressAndType, psm, cid);
+ client_socket = l2cap_open_dev(localAddressAndType, psm, cid);
- if( 0 > socket_descriptor ) {
+ if( 0 > client_socket ) {
goto failure; // open failed
}
@@ -191,17 +200,17 @@ bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) {
bzero((void *)&req, sizeof(req));
req.l2_family = AF_BLUETOOTH;
req.l2_psm = jau::cpu_to_le(direct_bt::number(psm));
- req.l2_bdaddr = jau::cpu_to_le(deviceAddressAndType.address);
+ req.l2_bdaddr = jau::cpu_to_le(remoteAddressAndType.address);
req.l2_cid = jau::cpu_to_le(direct_bt::number(cid));
- req.l2_bdaddr_type = ::number(deviceAddressAndType.type);
+ req.l2_bdaddr_type = ::number(remoteAddressAndType.type);
while( !interrupt_flag ) {
// blocking
- res = ::connect(socket_descriptor, (struct sockaddr*)&req, sizeof(req));
+ res = ::connect(client_socket, (struct sockaddr*)&req, sizeof(req));
- DBG_PRINT("L2CAPComm::open: Connect Result: %d, errno 0%X %s, %s, psm %s, cid %s",
+ DBG_PRINT("L2CAPComm::open: Connect Result: %d, errno 0x%X %s, %s, psm %s, cid %s",
res, errno, strerror(errno),
- deviceAddressAndType.toString().c_str(),
+ remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str());
if( !res )
@@ -213,26 +222,28 @@ bool L2CAPComm::open(const BTDevice& device, const BTSecurityLevel sec_level) {
if( to_retry_count < number(Defaults::L2CAP_CONNECT_MAX_RETRY) ) {
WORDY_PRINT("L2CAPComm::open: Connect timeout, retry %d: dd %d, %s, psm %s, cid %s, sec_level %s; %s",
to_retry_count,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(), to_string(sec_level).c_str(),
getStateString().c_str());
continue;
} else {
ERR_PRINT("L2CAPComm::open: Connect timeout, retried %d: dd %d, %s, psm %s, cid %s, sec_level %s; %s",
to_retry_count,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(), to_string(sec_level).c_str(),
getStateString().c_str());
goto failure; // exit
}
- } else {
+ } else if( !interrupt_flag ) {
// EALREADY == errno || ENETUNREACH == errno || EHOSTUNREACH == errno || ..
ERR_PRINT("L2CAPComm::open: Connect failed: dd %d, %s, psm %s, cid %s, sec_level %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(), to_string(sec_level).c_str(),
getStateString().c_str());
goto failure; // exit
+ } else {
+ goto failure; // exit on interrupt
}
}
// success
@@ -261,7 +272,7 @@ bool L2CAPComm::close() noexcept {
bool expOpen = true; // C++11, exp as value since C++20
if( !is_open.compare_exchange_strong(expOpen, false) ) {
DBG_PRINT("L2CAPComm::close: Not connected: dd %d, %s, psm %s, cid %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
has_ioerror = false; // always clear last ioerror flag (should be redundant)
@@ -271,7 +282,7 @@ bool L2CAPComm::close() noexcept {
has_ioerror = false;
DBG_PRINT("L2CAPComm::close: Start: dd %d, %s, psm %s, cid %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
PERF_TS_T0();
@@ -290,7 +301,7 @@ bool L2CAPComm::close() noexcept {
if( 0 != ( kerr = pthread_kill(_tid_read, SIGALRM) ) ) {
ERR_PRINT("L2CAPComm::close: pthread_kill read %p FAILED: %d; dd %d, %s, psm %s, cid %s; %s",
(void*)_tid_read, kerr,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
@@ -302,43 +313,51 @@ bool L2CAPComm::close() noexcept {
if( 0 != ( kerr = pthread_kill(_tid_connect, SIGALRM) ) ) {
ERR_PRINT("L2CAPComm::close: Start: pthread_kill connect %p FAILED: %d; dd %d, %s, psm %s, cid %s; %s",
(void*)_tid_connect, kerr,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
}
}
- l2cap_close_dev(socket_descriptor);
- socket_descriptor = -1;
+ l2cap_close_dev(client_socket);
+ client_socket = -1;
interrupt_flag = false;
PERF_TS_TD("L2CAPComm::close");
DBG_PRINT("L2CAPComm::close: End: dd %d, %s, psm %s, cid %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return true;
}
-bool L2CAPComm::setBTSecurityLevel(const BTSecurityLevel sec_level) {
+bool L2CAPComm::setBTSecurityLevel(const BTSecurityLevel sec_level) noexcept {
if( !is_open ) {
DBG_PRINT("L2CAPComm::setBTSecurityLevel(%s): Not connected: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return false;
}
const std::lock_guard<std::recursive_mutex> lock(mtx_write); // RAII-style acquire and relinquish via destructor
-
- return setBTSecurityLevelImpl(sec_level);
+ if( BTSecurityLevel::UNSET < sec_level ) {
+ if( setBTSecurityLevelImpl(sec_level) ) {
+ return true;
+ } else {
+ close();
+ return false;
+ }
+ } else {
+ return true;
+ }
}
-bool L2CAPComm::setBTSecurityLevelImpl(const BTSecurityLevel sec_level) {
+bool L2CAPComm::setBTSecurityLevelImpl(const BTSecurityLevel sec_level) noexcept {
if( BTSecurityLevel::NONE > sec_level ) {
DBG_PRINT("L2CAPComm::setBTSecurityLevel: sec_level %s not set: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return false;
@@ -352,18 +371,18 @@ bool L2CAPComm::setBTSecurityLevelImpl(const BTSecurityLevel sec_level) {
if( old_sec_level != sec_level ) {
bzero(&bt_sec, sizeof(bt_sec));
bt_sec.level = direct_bt::number(sec_level);
- result = setsockopt(socket_descriptor, SOL_BLUETOOTH, BT_SECURITY, &bt_sec, sizeof(bt_sec));
+ result = setsockopt(client_socket, SOL_BLUETOOTH, BT_SECURITY, &bt_sec, sizeof(bt_sec));
if ( 0 == result ) {
DBG_PRINT("L2CAPComm::setBTSecurityLevel: Success: sec_level %s -> %s: dd %d, %s, psm %s, cid %s; %s",
to_string(old_sec_level).c_str(), to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return true;
} else {
ERR_PRINT("L2CAPComm::setBTSecurityLevel: Failed: sec_level %s -> %s: dd %d, %s, psm %s, cid %s; %s",
to_string(old_sec_level).c_str(), to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return false;
@@ -371,7 +390,7 @@ bool L2CAPComm::setBTSecurityLevelImpl(const BTSecurityLevel sec_level) {
} else {
DBG_PRINT("L2CAPComm::setBTSecurityLevel: Unchanged: sec_level %s -> %s: dd %d, %s, psm %s, cid %s; %s",
to_string(old_sec_level).c_str(), to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return true;
@@ -379,17 +398,17 @@ bool L2CAPComm::setBTSecurityLevelImpl(const BTSecurityLevel sec_level) {
#else
DBG_PRINT("L2CAPComm::setBTSecurityLevel: Not implemented: sec_level %s: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return false;
#endif
}
-BTSecurityLevel L2CAPComm::getBTSecurityLevel() {
+BTSecurityLevel L2CAPComm::getBTSecurityLevel() noexcept {
if( !is_open ) {
DBG_PRINT("L2CAPComm::getBTSecurityLevel: Not connected: dd %d, %s, psm %s, cid %s; %s",
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
return BTSecurityLevel::UNSET;
@@ -399,7 +418,7 @@ BTSecurityLevel L2CAPComm::getBTSecurityLevel() {
return getBTSecurityLevelImpl();
}
-BTSecurityLevel L2CAPComm::getBTSecurityLevelImpl() {
+BTSecurityLevel L2CAPComm::getBTSecurityLevelImpl() noexcept {
BTSecurityLevel sec_level = BTSecurityLevel::UNSET;
#if USE_LINUX_BT_SECURITY
struct bt_security bt_sec;
@@ -407,33 +426,33 @@ BTSecurityLevel L2CAPComm::getBTSecurityLevelImpl() {
int result;
bzero(&bt_sec, sizeof(bt_sec));
- result = getsockopt(socket_descriptor, SOL_BLUETOOTH, BT_SECURITY, &bt_sec, &optlen);
+ result = getsockopt(client_socket, SOL_BLUETOOTH, BT_SECURITY, &bt_sec, &optlen);
if ( 0 == result ) {
if( optlen == sizeof(bt_sec) ) {
sec_level = static_cast<BTSecurityLevel>(bt_sec.level);
DBG_PRINT("L2CAPComm::getBTSecurityLevel: Success: sec_level %s: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
} else {
ERR_PRINT("L2CAPComm::getBTSecurityLevel: Failed: sec_level %s, size %zd returned != %zd bt_sec: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(), optlen, sizeof(bt_sec),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
} else {
ERR_PRINT("L2CAPComm::getBTSecurityLevel: Failed: sec_level %s, result %d: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(), result,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
#else
DBG_PRINT("L2CAPComm::getBTSecurityLevel: Not implemented: sec_level %s: dd %d, %s, psm %s, cid %s; %s",
to_string(sec_level).c_str(),
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
#endif
@@ -463,7 +482,7 @@ std::string L2CAPComm::getRWExitCodeString(const RWExitCode ec) noexcept {
return "Unknown ExitCode";
}
-jau::snsize_t L2CAPComm::read(uint8_t* buffer, const jau::nsize_t capacity) {
+jau::snsize_t L2CAPComm::read(uint8_t* buffer, const jau::nsize_t capacity) noexcept {
const int32_t timeoutMS = env.L2CAP_READER_POLL_TIMEOUT;
jau::snsize_t len = 0;
jau::snsize_t err_res = 0;
@@ -476,7 +495,7 @@ jau::snsize_t L2CAPComm::read(uint8_t* buffer, const jau::nsize_t capacity) {
err_res = number(RWExitCode::INTERRUPTED);
goto errout;
}
- if( 0 > socket_descriptor ) {
+ if( 0 > client_socket ) {
err_res = number(RWExitCode::INVALID_SOCKET_DD);
goto errout;
}
@@ -490,7 +509,7 @@ jau::snsize_t L2CAPComm::read(uint8_t* buffer, const jau::nsize_t capacity) {
struct pollfd p;
int n;
- p.fd = socket_descriptor; p.events = POLLIN;
+ p.fd = client_socket; p.events = POLLIN;
while ( is_open && !interrupt_flag && ( n = poll( &p, 1, timeoutMS ) ) < 0 ) {
if( !is_open ) {
err_res = number(RWExitCode::NOT_OPEN);
@@ -514,7 +533,7 @@ jau::snsize_t L2CAPComm::read(uint8_t* buffer, const jau::nsize_t capacity) {
}
}
- while ( is_open && !interrupt_flag && ( len = ::read(socket_descriptor, buffer, capacity) ) < 0 ) {
+ while ( is_open && !interrupt_flag && ( len = ::read(client_socket, buffer, capacity) ) < 0 ) {
if( !is_open ) {
err_res = number(RWExitCode::NOT_OPEN);
goto errout;
@@ -541,7 +560,7 @@ errout:
// closed or intentionally interrupted
WORDY_PRINT("L2CAPComm::read: IRQed res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
} else {
@@ -551,7 +570,7 @@ errout:
if( err_res != number(RWExitCode::POLL_TIMEOUT) ) { // expected POLL_TIMEOUT if idle
DBG_PRINT("L2CAPComm::read: Timeout res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
@@ -562,13 +581,13 @@ errout:
if( env.L2CAP_RESTART_COUNT_ON_ERROR < 0 ) {
ABORT("L2CAPComm::read: Error res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
} else {
IRQ_PRINT("L2CAPComm::read: Error res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
@@ -577,7 +596,7 @@ errout:
return err_res;
}
-jau::snsize_t L2CAPComm::write(const uint8_t * buffer, const jau::nsize_t length) {
+jau::snsize_t L2CAPComm::write(const uint8_t * buffer, const jau::nsize_t length) noexcept {
const std::lock_guard<std::recursive_mutex> lock(mtx_write); // RAII-style acquire and relinquish via destructor
jau::snsize_t len = 0;
jau::snsize_t err_res = 0;
@@ -590,7 +609,7 @@ jau::snsize_t L2CAPComm::write(const uint8_t * buffer, const jau::nsize_t length
err_res = number(RWExitCode::INTERRUPTED);
goto errout;
}
- if( 0 > socket_descriptor ) {
+ if( 0 > client_socket ) {
err_res = number(RWExitCode::INVALID_SOCKET_DD);
goto errout;
}
@@ -598,7 +617,7 @@ jau::snsize_t L2CAPComm::write(const uint8_t * buffer, const jau::nsize_t length
goto done;
}
- while ( is_open && !interrupt_flag && ( len = ::write(socket_descriptor, buffer, length) ) < 0 ) {
+ while ( is_open && !interrupt_flag && ( len = ::write(client_socket, buffer, length) ) < 0 ) {
if( !is_open ) {
err_res = number(RWExitCode::NOT_OPEN);
goto errout;
@@ -623,7 +642,7 @@ errout:
// closed or intentionally interrupted
WORDY_PRINT("L2CAPComm::write: IRQed res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
} else {
@@ -633,13 +652,13 @@ errout:
if( env.L2CAP_RESTART_COUNT_ON_ERROR < 0 ) {
ABORT("L2CAPComm::write: Error res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
} else {
IRQ_PRINT("L2CAPComm::write: Error res %d (%s), len %d; dd %d, %s, psm %s, cid %s; %s",
err_res, getRWExitCodeString(err_res).c_str(), len,
- socket_descriptor.load(), deviceAddressAndType.toString().c_str(),
+ client_socket.load(), remoteAddressAndType.toString().c_str(),
to_string(psm).c_str(), to_string(cid).c_str(),
getStateString().c_str());
}
@@ -647,3 +666,186 @@ errout:
return err_res;
}
+std::string L2CAPComm::toString() {
+ return "L2CAPComm[dd "+std::to_string(client_socket.load())+
+ ", psm "+to_string(psm)+
+ ", cid "+to_string(cid)+
+ ", local "+localAddressAndType.toString()+
+ ", remote "+remoteAddressAndType.toString()+
+ ", "+getStateString()+"]";
+}
+
+// *************************************************
+// *************************************************
+// *************************************************
+
+L2CAPServer::L2CAPServer(const BDAddressAndType& localAddressAndType_, const L2CAP_PSM psm_, const L2CAP_CID cid_) noexcept
+: localAddressAndType(localAddressAndType_),
+ psm(psm_), cid(cid_),
+ server_socket(-1),
+ is_open(false), interrupt_flag(false), tid_accept(0)
+{ }
+
+bool L2CAPServer::open() noexcept {
+ bool expOpen = false; // C++11, exp as value since C++20
+ if( !is_open.compare_exchange_strong(expOpen, true) ) {
+ DBG_PRINT("L2CAPServer::open: Already open: dd %d, psm %s, cid %s, local %s",
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+ return false;
+ }
+ const std::lock_guard<std::recursive_mutex> lock(mtx_open); // RAII-style acquire and relinquish via destructor
+ int res;
+
+ DBG_PRINT("L2CAPServer::open: Start: dd %d, psm %s, cid %s, local %s",
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+
+ server_socket = L2CAPComm::l2cap_open_dev(localAddressAndType, psm, cid);
+
+ if( 0 > server_socket ) {
+ goto failure; // open failed
+ }
+
+#if !SET_BT_SECURITY_POST_CONNECT
+ #if USE_LINUX_BT_SECURITY
+ if( BTSecurityLevel::UNSET < sec_level ) {
+ if( !setBTSecurityLevelImpl(sec_level) ) {
+ goto failure; // sec_level failed
+ }
+ }
+ #endif
+#endif
+
+ res = ::listen(server_socket, 10);
+
+ DBG_PRINT("L2CAPServer::open: End: res %d, dd %d, psm %s, cid %s, local %s",
+ res, server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+
+ if( res < 0 ) {
+ goto failure;
+ }
+
+ return true;
+
+failure:
+ const int err = errno;
+ close();
+ errno = err;
+ return false;
+}
+
+bool L2CAPServer::close() noexcept {
+ bool expOpen = true; // C++11, exp as value since C++20
+ if( !is_open.compare_exchange_strong(expOpen, false) ) {
+ DBG_PRINT("L2CAPServer::close: Not connected: dd %d, psm %s, cid %s, local %s",
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+ return true;
+ }
+ const std::lock_guard<std::recursive_mutex> lock(mtx_open); // RAII-style acquire and relinquish via destructor
+
+ DBG_PRINT("L2CAPServer::close: Start: dd %d, psm %s, cid %s, local %s",
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+ PERF_TS_T0();
+
+ interrupt_flag = true;
+ {
+ pthread_t tid_self = pthread_self();
+ pthread_t _tid_accept = tid_accept;
+ tid_accept = 0;
+
+ // interrupt connect(..) and , avoiding prolonged hang
+ interrupt_flag = true;
+ if( 0 != _tid_accept && tid_self != _tid_accept ) {
+ int kerr;
+ if( 0 != ( kerr = pthread_kill(_tid_accept, SIGALRM) ) ) {
+ ERR_PRINT("L2CAPServer::close: Start: pthread_kill connect %p FAILED: %d; dd %d, psm %s, cid %s, local %s",
+ (void*)_tid_accept, kerr,
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+ }
+ }
+ }
+
+ L2CAPComm::l2cap_close_dev(server_socket);
+ server_socket = -1;
+ interrupt_flag = false;
+ PERF_TS_TD("L2CAPServer::close");
+ DBG_PRINT("L2CAPServer::close: End: dd %d, psm %s, cid %s, local %s",
+ server_socket.load(), to_string(psm).c_str(), to_string(cid).c_str(),
+ localAddressAndType.toString().c_str());
+ return true;
+}
+
+std::unique_ptr<L2CAPComm> L2CAPServer::accept() noexcept {
+ sockaddr_l2 peer;
+ int to_retry_count=0; // ETIMEDOUT retry count
+
+ tid_accept = pthread_self(); // temporary safe tid to allow interruption
+
+ while( !interrupt_flag ) {
+ // blocking
+ bzero((void *)&peer, sizeof(peer));
+ socklen_t addrlen = sizeof(peer); // on return it will contain the actual size of the peer address
+ int client_socket = ::accept(server_socket, (struct sockaddr*)&peer, &addrlen);
+
+ BDAddressAndType remoteAddressAndType(jau::le_to_cpu(peer.l2_bdaddr), static_cast<BDAddressType>(peer.l2_bdaddr_type));
+ L2CAP_PSM c_psm = static_cast<L2CAP_PSM>(jau::le_to_cpu(peer.l2_psm));
+ L2CAP_CID c_cid = static_cast<L2CAP_CID>(jau::le_to_cpu(peer.l2_cid));
+
+ if( 0 <= client_socket )
+ {
+ DBG_PRINT("L2CAPServer::accept: Success: dd[s %d, c %d], errno 0x%X %s, psm %s -> %s, cid %s -> %s, local %s -> remote %s",
+ server_socket.load(), client_socket, errno, strerror(errno),
+ to_string(psm).c_str(), to_string(c_psm).c_str(),
+ to_string(cid).c_str(), to_string(c_cid).c_str(),
+ localAddressAndType.toString().c_str(),
+ remoteAddressAndType.toString().c_str());
+ // success
+ tid_accept = 0;
+ return std::make_unique<L2CAPComm>(localAddressAndType, c_psm, c_cid, remoteAddressAndType, client_socket);
+ } else if( ETIMEDOUT == errno ) {
+ to_retry_count++;
+ if( to_retry_count < L2CAPComm::number(L2CAPComm::Defaults::L2CAP_CONNECT_MAX_RETRY) ) {
+ WORDY_PRINT("L2CAPServer::accept: Timeout # %d (retry): dd[s %d, c %d], errno 0x%X %s, psm %s -> %s, cid %s -> %s, local %s -> remote %s",
+ to_retry_count, server_socket.load(), client_socket, errno, strerror(errno),
+ to_string(psm).c_str(), to_string(c_psm).c_str(),
+ to_string(cid).c_str(), to_string(c_cid).c_str(),
+ localAddressAndType.toString().c_str(),
+ remoteAddressAndType.toString().c_str());
+ continue;
+ } else {
+ ERR_PRINT("L2CAPServer::accept: Timeout # %d (done): dd[s %d, c %d], errno 0x%X %s, psm %s -> %s, cid %s -> %s, local %s -> remote %s",
+ to_retry_count, server_socket.load(), client_socket, errno, strerror(errno),
+ to_string(psm).c_str(), to_string(c_psm).c_str(),
+ to_string(cid).c_str(), to_string(c_cid).c_str(),
+ localAddressAndType.toString().c_str(),
+ remoteAddressAndType.toString().c_str());
+ break; // exit
+ }
+
+ } else if( !interrupt_flag ) {
+ // EALREADY == errno || ENETUNREACH == errno || EHOSTUNREACH == errno || ..
+ ERR_PRINT("L2CAPServer::accept: Failed: dd[s %d, c %d], errno 0x%X %s, psm %s -> %s, cid %s -> %s, local %s -> remote %s",
+ server_socket.load(), client_socket, errno, strerror(errno),
+ to_string(psm).c_str(), to_string(c_psm).c_str(),
+ to_string(cid).c_str(), to_string(c_cid).c_str(),
+ localAddressAndType.toString().c_str(),
+ remoteAddressAndType.toString().c_str());
+ break; // exit
+ }
+ }
+ // failure
+ tid_accept = 0;
+ return nullptr;
+}
+
+std::string L2CAPServer::toString() {
+ return "L2CAPServer[dd "+std::to_string(server_socket.load())+
+ ", psm "+to_string(psm)+
+ ", cid "+to_string(cid)+
+ ", local "+localAddressAndType.toString()+"]";
+}