blob: 71243550c44e0e3d04d7e75c29fb6f83a56f0fef [file] [log] [blame] [edit]
/*
* 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);
}
}
}