#include "UsbHelperApple.h"
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOReturn.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/serial/ioss.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <fmt/core.h>
namespace SimpleBLE {
namespace Dongl {
namespace USB {
template <typename T, typename Deleter>
class ScopedResource {
public:
ScopedResource(T resource = nullptr) : resource_(resource) {}
~ScopedResource() {
if (resource_) deleter_(resource_);
}
T get() const { return resource_; }
T operator->() const { return resource_; }
operator bool() const { return resource_ != nullptr; }
private:
T resource_;
Deleter deleter_;
};
struct CFReleaser {
void operator()(CFTypeRef obj) const { CFRelease(obj); }
};
struct IOReleaser {
void operator()(io_object_t obj) const { IOObjectRelease(obj); }
};
using ScopedCFRef = ScopedResource<CFTypeRef, CFReleaser>;
using ScopedIOObject = ScopedResource<io_object_t, IOReleaser>;
UsbHelperApple::UsbHelperApple(const std::string& device_path) : UsbHelperImpl(device_path) {
if (!_open_serial_port()) {
throw std::runtime_error("Failed to open serial port: " + _device_path);
}
_running = true;
_thread = std::thread(&UsbHelperApple::_run, this);
}
UsbHelperApple::~UsbHelperApple() {
_running = false;
if (_thread.joinable()) {
_thread.join();
}
_close_serial_port();
}
void UsbHelperApple::tx(const kvn::bytearray& data) {
const ssize_t written = write(_serial_fd, data.data(), data.size());
if (written != data.size()) {
throw std::runtime_error("Failed to write to serial port: " + std::string(strerror(errno)));
}
}
void UsbHelperApple::set_rx_callback(std::function<void(const kvn::bytearray&)> callback) {
_rx_callback.load(callback);
}
std::vector<std::string> UsbHelperApple::get_dongl_devices() {
std::vector<std::string> dongle_devices;
io_iterator_t raw_iterator = 0;
if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching(kIOSerialBSDServiceValue), &raw_iterator) !=
kIOReturnSuccess) {
return {};
}
ScopedIOObject iterator(raw_iterator);
io_service_t raw_service;
while ((raw_service = IOIteratorNext(iterator.get()))) {
ScopedIOObject service_guard(raw_service);
char raw_path[PATH_MAX];
ScopedCFRef pathCF((CFStringRef)IORegistryEntryCreateCFProperty(raw_service, CFSTR(kIOCalloutDeviceKey),
kCFAllocatorDefault, 0));
if (!pathCF) {
continue;
}
if (!CFStringGetCString((CFStringRef)pathCF.get(), raw_path, sizeof(raw_path), kCFStringEncodingUTF8)) {
continue;
}
std::string path(raw_path);
ScopedCFRef vidCF((CFNumberRef)IORegistryEntrySearchCFProperty(
raw_service, kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault,
kIORegistryIterateParents | kIORegistryIterateRecursively));
ScopedCFRef pidCF((CFNumberRef)IORegistryEntrySearchCFProperty(
raw_service, kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault,
kIORegistryIterateParents | kIORegistryIterateRecursively));
if (!vidCF || !pidCF) {
continue;
}
uint16_t vid, pid;
CFNumberGetValue((CFNumberRef)vidCF.get(), kCFNumberSInt16Type, &vid);
CFNumberGetValue((CFNumberRef)pidCF.get(), kCFNumberSInt16Type, &pid);
if (vid == UsbHelperImpl::DONGL_VENDOR_ID && pid == UsbHelperImpl::DONGL_PRODUCT_ID) {
dongle_devices.push_back(path);
}
}
return dongle_devices;
}
bool UsbHelperApple::_open_serial_port() {
_serial_fd = open(_device_path.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (_serial_fd < 0) {
std::cerr << "Failed to open serial port " << _device_path << ": " << strerror(errno) << std::endl;
return false;
}
_configure_serial_port();
return true;
}
void UsbHelperApple::_configure_serial_port() {
struct termios tty;
if (tcgetattr(_serial_fd, &tty) != 0) {
throw std::runtime_error("Failed to get serial port attributes: " + std::string(strerror(errno)));
}
tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~CRTSCTS; tty.c_cflag |= CREAD | CLOCAL;
tty.c_lflag &= ~ICANON; tty.c_lflag &= ~ECHO; tty.c_lflag &= ~ECHOE; tty.c_lflag &= ~ECHONL; tty.c_lflag &= ~ISIG;
tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
tty.c_oflag &= ~OPOST; tty.c_oflag &= ~ONLCR;
tty.c_cc[VMIN] = 0; tty.c_cc[VTIME] = 0;
if (tcsetattr(_serial_fd, TCSANOW, &tty) != 0) {
throw std::runtime_error("Failed to set serial port attributes: " + std::string(strerror(errno)));
}
speed_t speed = 1000000; if (ioctl(_serial_fd, IOSSIOSPEED, &speed) == -1) {
throw std::runtime_error("Failed to set serial port speed: " + std::string(strerror(errno)));
}
tcflush(_serial_fd, TCIOFLUSH);
}
void UsbHelperApple::_close_serial_port() {
if (_serial_fd >= 0) {
close(_serial_fd);
_serial_fd = -1;
}
}
void UsbHelperApple::_run() {
const size_t BUFFER_SIZE = 256;
char buffer[BUFFER_SIZE];
fd_set read_fds;
struct timeval timeout;
while (_running) {
FD_ZERO(&read_fds);
FD_SET(_serial_fd, &read_fds);
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
int ready = select(_serial_fd + 1, &read_fds, nullptr, nullptr, &timeout);
if (ready > 0) {
if (FD_ISSET(_serial_fd, &read_fds)) {
ssize_t bytes_read = read(_serial_fd, buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
kvn::bytearray data(buffer, bytes_read);
_rx_callback(data);
} else if (bytes_read == 0) {
std::cerr << "Serial port disconnected" << std::endl;
break;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
std::cerr << "Error reading from serial port: " << strerror(errno) << std::endl;
break;
}
}
}
} else if (ready == 0) {
continue;
} else {
if (errno != EINTR) {
std::cerr << "Error in select: " << strerror(errno) << std::endl;
break;
}
}
}
}
} } }