The dummy IrDA implementation is integrated in the main file. The appropriate implementation is selected using conditional compilation based on the features detect by the autotools build system.
572 lines
13 KiB
C
572 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"
|
|
|
|
#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
|
|
|
|
#ifdef _MSC_VER
|
|
#define snprintf _snprintf
|
|
#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
|
|
}
|