| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #define _WIN32_WINNT _WIN32_WINNT_VISTA |
| #define __USE_W32_SOCKETS |
| |
| #pragma pack(push,2) |
| #include <winsock2.h> |
| #include <ws2bth.h> |
| #include <nspapi.h> |
| #pragma pack(pop) |
| |
| #include "prologue.h" |
| |
| #include <string.h> |
| #include <errno.h> |
| |
| #if defined(AF_BTH) |
| #ifndef PF_BTH |
| #define PF_BTH AF_BTH |
| #endif /* PF_BTH */ |
| |
| #elif defined(PF_BTH) |
| #ifndef AF_BTH |
| #define AF_BTH PF_BTH |
| #endif /* AF_BTH */ |
| |
| #else /* bluetooth address/protocol family definitions */ |
| #define AF_BTH 32 |
| #define PF_BTH AF_BTH |
| #warning using hard-coded value for Bluetooth socket address and protocol family constants |
| #endif /* bluetooth address/protocol family definitions */ |
| |
| #ifndef NS_BTH |
| #define NS_BTH 16 |
| #warning using hard-coded value for Bluetooth namespace constant |
| #endif /* NS_BTH */ |
| |
| #if defined(__CYGWIN__) && !defined(__CYGWIN32__) |
| #define IOCTLSOCKET_ARGUMENT_TYPE unsigned int |
| #else /* ioctlcontrol argument type */ |
| #define IOCTLSOCKET_ARGUMENT_TYPE u_long |
| #endif /* ioctlcontrol argument type */ |
| |
| #include "log.h" |
| #include "timing.h" |
| #include "bitfield.h" |
| #include "io_bluetooth.h" |
| #include "bluetooth_internal.h" |
| |
| struct BluetoothConnectionExtensionStruct { |
| SOCKET socket; |
| SOCKADDR_BTH local; |
| SOCKADDR_BTH remote; |
| }; |
| |
| static void |
| bthSetErrno (DWORD error, const char *action, const DWORD *exceptions) { |
| if (exceptions) { |
| const DWORD *exception = exceptions; |
| |
| while (*exception != NO_ERROR) { |
| if (error == *exception) goto isException; |
| exception += 1; |
| } |
| } |
| |
| logWindowsError(error, action); |
| isException: |
| setErrno(error); |
| } |
| |
| static DWORD |
| bthSocketError (const char *action, const DWORD *exceptions) { |
| DWORD error = WSAGetLastError(); |
| |
| bthSetErrno(error, action, exceptions); |
| return error; |
| } |
| |
| static int |
| bthStartSockets (void) { |
| WSADATA wsa; |
| int result = WSAStartup(MAKEWORD(2, 2), &wsa); |
| |
| if (!result) return 1; |
| bthSetErrno(result, "WSA startup", NULL); |
| return 0; |
| } |
| |
| BluetoothConnectionExtension * |
| bthNewConnectionExtension (uint64_t bda) { |
| BluetoothConnectionExtension *bcx; |
| |
| if ((bcx = malloc(sizeof(*bcx)))) { |
| memset(bcx, 0, sizeof(*bcx)); |
| |
| bcx->local.addressFamily = AF_BTH; |
| bcx->local.btAddr = BTH_ADDR_NULL; |
| bcx->local.port = 0; |
| |
| bcx->remote.addressFamily = AF_BTH; |
| bcx->remote.btAddr = bda; |
| bcx->remote.port = 0; |
| |
| bcx->socket = INVALID_SOCKET; |
| return bcx; |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| void |
| bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) { |
| if (bcx->socket != INVALID_SOCKET) { |
| closesocket(bcx->socket); |
| bcx->socket = INVALID_SOCKET; |
| } |
| |
| free(bcx); |
| } |
| |
| int |
| bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) { |
| bcx->remote.port = channel; |
| |
| if (bthStartSockets()) { |
| if ((bcx->socket = socket(PF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM)) != INVALID_SOCKET) { |
| if (bind(bcx->socket, (SOCKADDR *)&bcx->local, sizeof(bcx->local)) != SOCKET_ERROR) { |
| if (connect(bcx->socket, (SOCKADDR *)&bcx->remote, sizeof(bcx->remote)) != SOCKET_ERROR) { |
| IOCTLSOCKET_ARGUMENT_TYPE nonblocking = 1; |
| |
| if (ioctlsocket(bcx->socket, FIONBIO, &nonblocking) != SOCKET_ERROR) { |
| return 1; |
| } else { |
| bthSocketError("RFCOMM nonblocking", NULL); |
| } |
| } else { |
| static const DWORD exceptions[] = { |
| ERROR_HOST_DOWN, |
| ERROR_HOST_UNREACHABLE, |
| NO_ERROR |
| }; |
| |
| bthSocketError("RFCOMM connect", exceptions); |
| } |
| } else { |
| bthSocketError("RFCOMM bind", NULL); |
| } |
| |
| closesocket(bcx->socket); |
| bcx->socket = INVALID_SOCKET; |
| } else { |
| bthSocketError("RFCOMM socket", NULL); |
| } |
| } |
| |
| return 0; |
| } |
| |
| typedef union { |
| double ensureCorrectAlignment; |
| unsigned char ensureAdequateSize[0X1000]; |
| WSAQUERYSET querySet; |
| } BluetoothServiceLookupResult; |
| |
| static int |
| bthPerformServiceLookup ( |
| BluetoothServiceLookupResult *result, |
| ULONGLONG address, GUID *guid, |
| DWORD beginFlags, DWORD nextFlags |
| ) { |
| int found = 0; |
| |
| if (bthStartSockets()) { |
| SOCKADDR_BTH socketAddress = { |
| .addressFamily = AF_BTH, |
| .btAddr = address |
| }; |
| |
| char addressString[0X100]; |
| DWORD addressLength = sizeof(addressString); |
| |
| if (WSAAddressToString((SOCKADDR *)&socketAddress, sizeof(socketAddress), |
| NULL, |
| addressString, &addressLength) != SOCKET_ERROR) { |
| HANDLE handle; |
| |
| CSADDR_INFO csa[] = { |
| { |
| .RemoteAddr = { |
| .lpSockaddr = (SOCKADDR *)&socketAddress, |
| .iSockaddrLength = sizeof(socketAddress) |
| } |
| } |
| }; |
| |
| WSAQUERYSET restrictions = { |
| .dwNameSpace = NS_BTH, |
| .lpszContext = addressString, |
| .lpcsaBuffer = csa, |
| .dwNumberOfCsAddrs = ARRAY_COUNT(csa), |
| .lpServiceClassId = guid, |
| .dwSize = sizeof(restrictions) |
| }; |
| |
| if (WSALookupServiceBegin(&restrictions, (LUP_FLUSHCACHE | beginFlags), &handle) != SOCKET_ERROR) { |
| DWORD resultLength = sizeof(*result); |
| |
| if (WSALookupServiceNext(handle, nextFlags, &resultLength, &result->querySet) != SOCKET_ERROR) { |
| found = 1; |
| } else { |
| static const DWORD exceptions[] = { |
| #ifdef WSA_E_NO_MORE |
| WSA_E_NO_MORE, |
| #endif /* WSA_E_NO_MORE */ |
| |
| #ifdef WSAENOMORE |
| WSAENOMORE, |
| #endif /* WSAENOMORE */ |
| |
| NO_ERROR |
| }; |
| |
| bthSocketError("WSALookupServiceNext", exceptions); |
| } |
| |
| if (WSALookupServiceEnd(handle) == SOCKET_ERROR) { |
| bthSocketError("WSALookupServiceEnd", NULL); |
| } |
| } else { |
| static const DWORD exceptions[] = { |
| WSASERVICE_NOT_FOUND, |
| NO_ERROR |
| }; |
| |
| bthSocketError("WSALookupServiceBegin", exceptions); |
| } |
| } else { |
| bthSocketError("WSAAddressToString", NULL); |
| } |
| } |
| |
| return found; |
| } |
| |
| int |
| bthDiscoverChannel ( |
| uint8_t *channel, BluetoothConnectionExtension *bcx, |
| const void *uuidBytes, size_t uuidLength, |
| int timeout |
| ) { |
| GUID guid; |
| |
| memcpy(&guid, uuidBytes, sizeof(guid)); |
| putNativeEndian32((uint32_t *)&guid.Data1, getBigEndian32(guid.Data1)); |
| putNativeEndian16((uint16_t *)&guid.Data2, getBigEndian16(guid.Data2)); |
| putNativeEndian16((uint16_t *)&guid.Data3, getBigEndian16(guid.Data3)); |
| |
| #if 1 |
| { |
| BluetoothServiceLookupResult result; |
| |
| if (bthPerformServiceLookup(&result, bcx->remote.btAddr, &guid, 0, LUP_RETURN_ADDR)) { |
| SOCKADDR_BTH *bth = (SOCKADDR_BTH *)result.querySet.lpcsaBuffer[0].RemoteAddr.lpSockaddr; |
| |
| if (bth->port) { |
| *channel = bth->port; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| #else /* discover channel */ |
| memcpy(&bcx->remote.serviceClassId, &guid, sizeof(bcx->remote.serviceClassId)); |
| *channel = 0; |
| return 1; |
| #endif /* discover channel */ |
| } |
| |
| int |
| bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) { |
| return 0; |
| } |
| |
| int |
| bthPollInput (BluetoothConnectionExtension *bcx, int timeout) { |
| fd_set input; |
| TIMEVAL time; |
| |
| FD_ZERO(&input); |
| FD_SET(bcx->socket, &input); |
| |
| memset(&time, 0, sizeof(time)); |
| time.tv_sec = timeout / MSECS_PER_SEC; |
| time.tv_usec = (timeout % MSECS_PER_SEC) * USECS_PER_MSEC; |
| |
| switch (select(bcx->socket+1, &input, NULL, NULL, &time)) { |
| case SOCKET_ERROR: |
| bthSocketError("RFCOMM wait", NULL); |
| break; |
| |
| case 0: |
| errno = EAGAIN; |
| break; |
| |
| default: |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| ssize_t |
| bthGetData ( |
| BluetoothConnectionExtension *bcx, void *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| size_t count = size; |
| |
| if (count) { |
| char *to = buffer; |
| |
| while (1) { |
| int result = recv(bcx->socket, to, count, 0); |
| |
| if (result != SOCKET_ERROR) { |
| to += result; |
| if (!(count -= result)) break; |
| } else { |
| static const DWORD exceptions[] = { |
| WSAEWOULDBLOCK, |
| NO_ERROR |
| }; |
| |
| DWORD error = bthSocketError("RFCOMM read", exceptions); |
| if (error != WSAEWOULDBLOCK) return -1; |
| } |
| |
| { |
| int timeout = (to == buffer)? initialTimeout: subsequentTimeout; |
| |
| if (!timeout) break; |
| if (!bthPollInput(bcx, timeout)) return -1; |
| } |
| } |
| } |
| |
| if (size == count) errno = EAGAIN; |
| return size - count; |
| } |
| |
| ssize_t |
| bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) { |
| const char *from = buffer; |
| size_t count = size; |
| |
| while (count) { |
| int result = send(bcx->socket, from, count, 0); |
| |
| if (result == SOCKET_ERROR) { |
| bthSocketError("RFCOMM write", NULL); |
| return -1; |
| } |
| |
| from += result; |
| count -= result; |
| } |
| |
| return size; |
| } |
| |
| char * |
| bthObtainDeviceName (uint64_t bda, int timeout) { |
| BluetoothServiceLookupResult result; |
| |
| if (bthPerformServiceLookup(&result, bda, NULL, LUP_CONTAINERS, LUP_RETURN_NAME)) { |
| char *name = strdup(result.querySet.lpszServiceInstanceName); |
| |
| if (name) { |
| return name; |
| } else { |
| logMallocError(); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void |
| bthProcessDiscoveredDevices ( |
| DiscoveredBluetoothDeviceTester *testDevice, void *data |
| ) { |
| if (bthStartSockets()) { |
| HANDLE handle; |
| |
| WSAQUERYSET restrictions = { |
| .dwNameSpace = NS_BTH, |
| .dwSize = sizeof(restrictions) |
| }; |
| |
| if (WSALookupServiceBegin(&restrictions, LUP_CONTAINERS, &handle) != SOCKET_ERROR) { |
| while (1) { |
| BluetoothServiceLookupResult result; |
| DWORD resultLength = sizeof(result); |
| |
| if (WSALookupServiceNext(handle, |
| (LUP_RETURN_ADDR | LUP_RETURN_NAME | LUP_RETURN_BLOB), |
| &resultLength, &result.querySet) == SOCKET_ERROR) { |
| static const DWORD exceptions[] = { |
| #ifdef WSA_E_NO_MORE |
| WSA_E_NO_MORE, |
| #endif /* WSA_E_NO_MORE */ |
| |
| #ifdef WSAENOMORE |
| WSAENOMORE, |
| #endif /* WSAENOMORE */ |
| |
| NO_ERROR |
| }; |
| |
| bthSocketError("WSALookupServiceNext", exceptions); |
| break; |
| } |
| |
| if (result.querySet.dwNumberOfCsAddrs == 1) { |
| DiscoveredBluetoothDevice device; |
| memset(&device, 0, sizeof(device)); |
| |
| { |
| BTH_ADDR address = ((SOCKADDR_BTH *)result.querySet.lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr; |
| device.address = ((uint64_t)GET_NAP(address) << 0X20) | (uint64_t)GET_SAP(address); |
| } |
| |
| { |
| char *name = result.querySet.lpszServiceInstanceName; |
| if (name && *name) device.name = name; |
| } |
| |
| { |
| BTH_DEVICE_INFO *info = (BTH_DEVICE_INFO *)result.querySet.lpBlob->pBlobData; |
| device.paired = !!(info->flags & BDIF_PAIRED); |
| } |
| |
| if (testDevice(&device, data)) break; |
| } |
| } |
| |
| if (WSALookupServiceEnd(handle) == SOCKET_ERROR) { |
| bthSocketError("WSALookupServiceEnd", NULL); |
| } |
| } else { |
| static const DWORD exceptions[] = { |
| WSASERVICE_NOT_FOUND, |
| NO_ERROR |
| }; |
| |
| bthSocketError("WSALookupServiceBegin", exceptions); |
| } |
| } |
| } |