569 lines
13 KiB
C
569 lines
13 KiB
C
/*
|
|
* libdivecomputer
|
|
*
|
|
* Copyright (C) 2008 Jef Driesen
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it 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.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h> // malloc, free
|
|
#include <stdio.h> // snprintf
|
|
#ifdef _WIN32
|
|
#define NOGDI
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#ifdef HAVE_AF_IRDA_H
|
|
#define IRDA
|
|
#include <af_irda.h>
|
|
#endif
|
|
#else
|
|
#include <string.h> // strerror
|
|
#include <errno.h> // errno
|
|
#include <unistd.h> // close
|
|
#include <sys/types.h> // socket, getsockopt
|
|
#include <sys/socket.h> // socket, getsockopt
|
|
#ifdef HAVE_LINUX_IRDA_H
|
|
#define IRDA
|
|
#include <linux/types.h> // irda
|
|
#include <linux/irda.h> // irda
|
|
#endif
|
|
#include <sys/select.h> // select
|
|
#include <sys/ioctl.h> // ioctl
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "irda.h"
|
|
#include "common-private.h"
|
|
#include "context-private.h"
|
|
#include "array.h"
|
|
#include "platform.h"
|
|
|
|
#ifdef _WIN32
|
|
typedef int s_ssize_t;
|
|
typedef DWORD s_errcode_t;
|
|
#define S_ERRNO WSAGetLastError ()
|
|
#define S_EINTR WSAEINTR
|
|
#define S_EAGAIN WSAEWOULDBLOCK
|
|
#define S_ENOMEM WSA_NOT_ENOUGH_MEMORY
|
|
#define S_EINVAL WSAEINVAL
|
|
#define S_EACCES WSAEACCES
|
|
#define S_EAFNOSUPPORT WSAEAFNOSUPPORT
|
|
#define S_INVALID INVALID_SOCKET
|
|
#define S_IOCTL ioctlsocket
|
|
#define S_CLOSE closesocket
|
|
#else
|
|
typedef ssize_t s_ssize_t;
|
|
typedef int s_errcode_t;
|
|
#define S_ERRNO errno
|
|
#define S_EINTR EINTR
|
|
#define S_EAGAIN EAGAIN
|
|
#define S_ENOMEM ENOMEM
|
|
#define S_EINVAL EINVAL
|
|
#define S_EACCES EACCES
|
|
#define S_EAFNOSUPPORT EAFNOSUPPORT
|
|
#define S_INVALID -1
|
|
#define S_IOCTL ioctl
|
|
#define S_CLOSE close
|
|
#endif
|
|
|
|
struct dc_irda_t {
|
|
dc_context_t *context;
|
|
#ifdef _WIN32
|
|
SOCKET fd;
|
|
#else
|
|
int fd;
|
|
#endif
|
|
int timeout;
|
|
};
|
|
|
|
#ifdef IRDA
|
|
static dc_status_t
|
|
syserror(s_errcode_t errcode)
|
|
{
|
|
switch (errcode) {
|
|
case S_EINVAL:
|
|
return DC_STATUS_INVALIDARGS;
|
|
case S_ENOMEM:
|
|
return DC_STATUS_NOMEMORY;
|
|
case S_EACCES:
|
|
return DC_STATUS_NOACCESS;
|
|
case S_EAFNOSUPPORT:
|
|
return DC_STATUS_UNSUPPORTED;
|
|
default:
|
|
return DC_STATUS_IO;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
dc_status_t
|
|
dc_irda_open (dc_irda_t **out, dc_context_t *context)
|
|
{
|
|
#ifdef IRDA
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
dc_irda_t *device = NULL;
|
|
|
|
if (out == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Allocate memory.
|
|
device = (dc_irda_t *) malloc (sizeof (dc_irda_t));
|
|
if (device == NULL) {
|
|
SYSERROR (context, S_ENOMEM);
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// Library context.
|
|
device->context = context;
|
|
|
|
// Default to blocking reads.
|
|
device->timeout = -1;
|
|
|
|
#ifdef _WIN32
|
|
// Initialize the winsock dll.
|
|
WSADATA wsaData;
|
|
WORD wVersionRequested = MAKEWORD (2, 2);
|
|
int rc = WSAStartup (wVersionRequested, &wsaData);
|
|
if (rc != 0) {
|
|
SYSERROR (context, rc);
|
|
status = DC_STATUS_UNSUPPORTED;
|
|
goto error_free;
|
|
}
|
|
|
|
// Confirm that the winsock dll supports version 2.2.
|
|
// Note that if the dll supports versions greater than 2.2 in addition to
|
|
// 2.2, it will still return 2.2 since that is the version we requested.
|
|
if (LOBYTE (wsaData.wVersion) != 2 ||
|
|
HIBYTE (wsaData.wVersion) != 2) {
|
|
ERROR (context, "Incorrect winsock version.");
|
|
status = DC_STATUS_UNSUPPORTED;
|
|
goto error_wsacleanup;
|
|
}
|
|
#endif
|
|
|
|
// Open the socket.
|
|
device->fd = socket (AF_IRDA, SOCK_STREAM, 0);
|
|
if (device->fd == S_INVALID) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (context, errcode);
|
|
status = syserror(errcode);
|
|
goto error_wsacleanup;
|
|
}
|
|
|
|
*out = device;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
error_wsacleanup:
|
|
#ifdef _WIN32
|
|
WSACleanup ();
|
|
error_free:
|
|
#endif
|
|
free (device);
|
|
return status;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_close (dc_irda_t *device)
|
|
{
|
|
#ifdef IRDA
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
|
|
if (device == NULL)
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
// Terminate all send and receive operations.
|
|
shutdown (device->fd, 0);
|
|
|
|
// Close the socket.
|
|
if (S_CLOSE (device->fd) != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (device->context, errcode);
|
|
dc_status_set_error(&status, syserror(errcode));
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Terminate the winsock dll.
|
|
if (WSACleanup () != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (device->context, errcode);
|
|
dc_status_set_error(&status, syserror(errcode));
|
|
}
|
|
#endif
|
|
|
|
// Free memory.
|
|
free (device);
|
|
|
|
return status;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_set_timeout (dc_irda_t *device, int timeout)
|
|
{
|
|
#ifdef IRDA
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Timeout: value=%i", timeout);
|
|
|
|
device->timeout = timeout;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
|
|
#define DISCOVER_MAX_DEVICES 16 // Maximum number of devices.
|
|
#define DISCOVER_MAX_RETRIES 4 // Maximum number of retries.
|
|
|
|
#ifdef _WIN32
|
|
#define DISCOVER_BUFSIZE sizeof (DEVICELIST) + \
|
|
sizeof (IRDA_DEVICE_INFO) * (DISCOVER_MAX_DEVICES - 1)
|
|
#else
|
|
#define DISCOVER_BUFSIZE sizeof (struct irda_device_list) + \
|
|
sizeof (struct irda_device_info) * (DISCOVER_MAX_DEVICES - 1)
|
|
#endif
|
|
|
|
dc_status_t
|
|
dc_irda_discover (dc_irda_t *device, dc_irda_callback_t callback, void *userdata)
|
|
{
|
|
#ifdef IRDA
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
unsigned char data[DISCOVER_BUFSIZE] = {0};
|
|
#ifdef _WIN32
|
|
DEVICELIST *list = (DEVICELIST *) data;
|
|
int size = sizeof (data);
|
|
#else
|
|
struct irda_device_list *list = (struct irda_device_list *) data;
|
|
socklen_t size = sizeof (data);
|
|
#endif
|
|
|
|
int rc = 0;
|
|
unsigned int nretries = 0;
|
|
while ((rc = getsockopt (device->fd, SOL_IRLMP, IRLMP_ENUMDEVICES, (char*) data, &size)) != 0 ||
|
|
#ifdef _WIN32
|
|
list->numDevice == 0)
|
|
#else
|
|
list->len == 0)
|
|
#endif
|
|
{
|
|
// Automatically retry the discovery when no devices were found.
|
|
// On Linux, getsockopt fails with EAGAIN when no devices are
|
|
// discovered, while on Windows it succeeds and sets the number
|
|
// of devices to zero. Both situations are handled the same here.
|
|
if (rc != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
if (errcode != S_EAGAIN) {
|
|
SYSERROR (device->context, errcode);
|
|
return syserror(errcode);
|
|
}
|
|
}
|
|
|
|
// Abort if the maximum number of retries is reached.
|
|
if (nretries++ >= DISCOVER_MAX_RETRIES)
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
// Restore the size parameter in case it was
|
|
// modified by the previous getsockopt call.
|
|
size = sizeof (data);
|
|
|
|
#ifdef _WIN32
|
|
Sleep (1000);
|
|
#else
|
|
sleep (1);
|
|
#endif
|
|
}
|
|
|
|
if (callback) {
|
|
#ifdef _WIN32
|
|
for (unsigned int i = 0; i < list->numDevice; ++i) {
|
|
const char *name = list->Device[i].irdaDeviceName;
|
|
unsigned int address = array_uint32_le (list->Device[i].irdaDeviceID);
|
|
unsigned int charset = list->Device[i].irdaCharSet;
|
|
unsigned int hints = (list->Device[i].irdaDeviceHints1 << 8) +
|
|
list->Device[i].irdaDeviceHints2;
|
|
#else
|
|
for (unsigned int i = 0; i < list->len; ++i) {
|
|
const char *name = list->dev[i].info;
|
|
unsigned int address = list->dev[i].daddr;
|
|
unsigned int charset = list->dev[i].charset;
|
|
unsigned int hints = array_uint16_be (list->dev[i].hints);
|
|
#endif
|
|
|
|
INFO (device->context,
|
|
"Discover: address=%08x, name=%s, charset=%02x, hints=%04x",
|
|
address, name, charset, hints);
|
|
|
|
callback (address, name, charset, hints, userdata);
|
|
}
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_connect_name (dc_irda_t *device, unsigned int address, const char *name)
|
|
{
|
|
#ifdef IRDA
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Connect: address=%08x, name=%s", address, name ? name : "");
|
|
|
|
#ifdef _WIN32
|
|
SOCKADDR_IRDA peer;
|
|
peer.irdaAddressFamily = AF_IRDA;
|
|
peer.irdaDeviceID[0] = (address ) & 0xFF;
|
|
peer.irdaDeviceID[1] = (address >> 8) & 0xFF;
|
|
peer.irdaDeviceID[2] = (address >> 16) & 0xFF;
|
|
peer.irdaDeviceID[3] = (address >> 24) & 0xFF;
|
|
if (name)
|
|
strncpy (peer.irdaServiceName, name, 25);
|
|
else
|
|
memset (peer.irdaServiceName, 0x00, 25);
|
|
#else
|
|
struct sockaddr_irda peer;
|
|
peer.sir_family = AF_IRDA;
|
|
peer.sir_addr = address;
|
|
if (name)
|
|
strncpy (peer.sir_name, name, 25);
|
|
else
|
|
memset (peer.sir_name, 0x00, 25);
|
|
#endif
|
|
|
|
if (connect (device->fd, (struct sockaddr *) &peer, sizeof (peer)) != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (device->context, errcode);
|
|
return syserror(errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_connect_lsap (dc_irda_t *device, unsigned int address, unsigned int lsap)
|
|
{
|
|
#ifdef IRDA
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Connect: address=%08x, lsap=%u", address, lsap);
|
|
|
|
#ifdef _WIN32
|
|
SOCKADDR_IRDA peer;
|
|
peer.irdaAddressFamily = AF_IRDA;
|
|
peer.irdaDeviceID[0] = (address ) & 0xFF;
|
|
peer.irdaDeviceID[1] = (address >> 8) & 0xFF;
|
|
peer.irdaDeviceID[2] = (address >> 16) & 0xFF;
|
|
peer.irdaDeviceID[3] = (address >> 24) & 0xFF;
|
|
snprintf (peer.irdaServiceName, 25, "LSAP-SEL%u", lsap);
|
|
#else
|
|
struct sockaddr_irda peer;
|
|
peer.sir_family = AF_IRDA;
|
|
peer.sir_addr = address;
|
|
peer.sir_lsap_sel = lsap;
|
|
memset (peer.sir_name, 0x00, 25);
|
|
#endif
|
|
|
|
if (connect (device->fd, (struct sockaddr *) &peer, sizeof (peer)) != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (device->context, errcode);
|
|
return syserror(errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_get_available (dc_irda_t *device, size_t *value)
|
|
{
|
|
#ifdef IRDA
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
#ifdef _WIN32
|
|
unsigned long bytes = 0;
|
|
#else
|
|
int bytes = 0;
|
|
#endif
|
|
|
|
if (S_IOCTL (device->fd, FIONREAD, &bytes) != 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
SYSERROR (device->context, errcode);
|
|
return syserror(errcode);
|
|
}
|
|
|
|
if (value)
|
|
*value = bytes;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_read (dc_irda_t *device, void *data, size_t size, size_t *actual)
|
|
{
|
|
#ifdef IRDA
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
size_t nbytes = 0;
|
|
|
|
if (device == NULL) {
|
|
status = DC_STATUS_INVALIDARGS;
|
|
goto out_invalidargs;
|
|
}
|
|
|
|
while (nbytes < size) {
|
|
fd_set fds;
|
|
FD_ZERO (&fds);
|
|
FD_SET (device->fd, &fds);
|
|
|
|
struct timeval tvt;
|
|
if (device->timeout > 0) {
|
|
tvt.tv_sec = (device->timeout / 1000);
|
|
tvt.tv_usec = (device->timeout % 1000) * 1000;
|
|
} else if (device->timeout == 0) {
|
|
timerclear (&tvt);
|
|
}
|
|
|
|
int rc = select (device->fd + 1, &fds, NULL, NULL, device->timeout >= 0 ? &tvt : NULL);
|
|
if (rc < 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
if (errcode == S_EINTR)
|
|
continue; // Retry.
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror(errcode);
|
|
goto out;
|
|
} else if (rc == 0) {
|
|
break; // Timeout.
|
|
}
|
|
|
|
s_ssize_t n = recv (device->fd, (char*) data + nbytes, size - nbytes, 0);
|
|
if (n < 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
if (errcode == S_EINTR || errcode == S_EAGAIN)
|
|
continue; // Retry.
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror(errcode);
|
|
goto out;
|
|
} else if (n == 0) {
|
|
break; // EOF reached.
|
|
}
|
|
|
|
nbytes += n;
|
|
}
|
|
|
|
if (nbytes != size) {
|
|
status = DC_STATUS_TIMEOUT;
|
|
}
|
|
|
|
out:
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
|
|
|
|
out_invalidargs:
|
|
if (actual)
|
|
*actual = nbytes;
|
|
|
|
return status;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
dc_status_t
|
|
dc_irda_write (dc_irda_t *device, const void *data, size_t size, size_t *actual)
|
|
{
|
|
#ifdef IRDA
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
size_t nbytes = 0;
|
|
|
|
if (device == NULL) {
|
|
status = DC_STATUS_INVALIDARGS;
|
|
goto out_invalidargs;
|
|
}
|
|
|
|
while (nbytes < size) {
|
|
fd_set fds;
|
|
FD_ZERO (&fds);
|
|
FD_SET (device->fd, &fds);
|
|
|
|
int rc = select (device->fd + 1, NULL, &fds, NULL, NULL);
|
|
if (rc < 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
if (errcode == S_EINTR)
|
|
continue; // Retry.
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror(errcode);
|
|
goto out;
|
|
} else if (rc == 0) {
|
|
break; // Timeout.
|
|
}
|
|
|
|
s_ssize_t n = send (device->fd, (char*) data + nbytes, size - nbytes, 0);
|
|
if (n < 0) {
|
|
s_errcode_t errcode = S_ERRNO;
|
|
if (errcode == S_EINTR || errcode == S_EAGAIN)
|
|
continue; // Retry.
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror(errcode);
|
|
goto out;
|
|
} else if (n == 0) {
|
|
break; // EOF.
|
|
}
|
|
|
|
nbytes += n;
|
|
}
|
|
|
|
if (nbytes != size) {
|
|
status = DC_STATUS_TIMEOUT;
|
|
}
|
|
|
|
out:
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Write", (unsigned char *) data, nbytes);
|
|
|
|
out_invalidargs:
|
|
if (actual)
|
|
*actual = nbytes;
|
|
|
|
return status;
|
|
#else
|
|
return DC_STATUS_UNSUPPORTED;
|
|
#endif
|
|
}
|