libdc/src/socket.c
Jef Driesen 56d194d377 Use a NULL pointer for the no-op implementation
For most I/O stream implementations the serial communication specific
functions are meaningless. Implementing them as no-ops allows the dive
computer backends the call the I/O stream functions unconditionally.

However, implementing the no-op with a dummy function returning
DC_STATUS_SUCCESS, does not only add some (small) overhead at runtime,
but also requires many such functions. This is inconvenient and the same
result can easily be obtained by using a NULL pointer instead.

The consequence is that the logic is reversed now. To obtain the
previous behaviour of returning the DC_STATUS_UNSUPPORTED error code
again, you'll need to implement a dummy function. But that's fine
because it's the less common case.
2018-04-17 08:18:35 +02:00

296 lines
6.7 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2017 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
*/
#include "socket.h"
#include "common-private.h"
#include "context-private.h"
dc_status_t
dc_socket_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;
}
}
dc_status_t
dc_socket_init (dc_context_t *context)
{
#ifdef _WIN32
// Initialize the winsock dll.
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD (2, 2);
int rc = WSAStartup (wVersionRequested, &wsaData);
if (rc != 0) {
SYSERROR (context, rc);
return DC_STATUS_UNSUPPORTED;
}
// 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.");
return DC_STATUS_UNSUPPORTED;
}
#endif
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_socket_exit (dc_context_t *context)
{
#ifdef _WIN32
// Terminate the winsock dll.
if (WSACleanup () != 0) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (context, errcode);
return dc_socket_syserror(errcode);
}
#endif
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_socket_open (dc_iostream_t *abstract, int family, int type, int protocol)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_socket_t *device = (dc_socket_t *) abstract;
// Default to blocking reads.
device->timeout = -1;
// Initialize the socket library.
status = dc_socket_init (abstract->context);
if (status != DC_STATUS_SUCCESS) {
return status;
}
// Open the socket.
device->fd = socket (family, type, protocol);
if (device->fd == S_INVALID) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (abstract->context, errcode);
status = dc_socket_syserror(errcode);
goto error;
}
return DC_STATUS_SUCCESS;
error:
dc_socket_exit (abstract->context);
return status;
}
dc_status_t
dc_socket_close (dc_iostream_t *abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_socket_t *socket = (dc_socket_t *) abstract;
dc_status_t rc = DC_STATUS_SUCCESS;
// Terminate all send and receive operations.
shutdown (socket->fd, 0);
// Close the socket.
if (S_CLOSE (socket->fd) != 0) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (abstract->context, errcode);
dc_status_set_error(&status, dc_socket_syserror(errcode));
}
// Terminate the socket library.
rc = dc_socket_exit (abstract->context);
if (rc != DC_STATUS_SUCCESS) {
dc_status_set_error(&status, rc);
}
return status;
}
dc_status_t
dc_socket_connect (dc_iostream_t *abstract, const struct sockaddr *addr, s_socklen_t addrlen)
{
dc_socket_t *socket = (dc_socket_t *) abstract;
if (connect (socket->fd, addr, addrlen) != 0) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (abstract->context, errcode);
return dc_socket_syserror(errcode);
}
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_socket_set_timeout (dc_iostream_t *abstract, int timeout)
{
dc_socket_t *socket = (dc_socket_t *) abstract;
socket->timeout = timeout;
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_socket_get_available (dc_iostream_t *abstract, size_t *value)
{
dc_socket_t *socket = (dc_socket_t *) abstract;
#ifdef _WIN32
unsigned long bytes = 0;
#else
int bytes = 0;
#endif
if (S_IOCTL (socket->fd, FIONREAD, &bytes) != 0) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (abstract->context, errcode);
return dc_socket_syserror(errcode);
}
if (value)
*value = bytes;
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_socket_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_socket_t *socket = (dc_socket_t *) abstract;
size_t nbytes = 0;
while (nbytes < size) {
fd_set fds;
FD_ZERO (&fds);
FD_SET (socket->fd, &fds);
struct timeval tvt;
if (socket->timeout > 0) {
tvt.tv_sec = (socket->timeout / 1000);
tvt.tv_usec = (socket->timeout % 1000) * 1000;
} else if (socket->timeout == 0) {
timerclear (&tvt);
}
int rc = select (socket->fd + 1, &fds, NULL, NULL, socket->timeout >= 0 ? &tvt : NULL);
if (rc < 0) {
s_errcode_t errcode = S_ERRNO;
if (errcode == S_EINTR)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = dc_socket_syserror(errcode);
goto out;
} else if (rc == 0) {
break; // Timeout.
}
s_ssize_t n = recv (socket->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 (abstract->context, errcode);
status = dc_socket_syserror(errcode);
goto out;
} else if (n == 0) {
break; // EOF reached.
}
nbytes += n;
}
if (nbytes != size) {
status = DC_STATUS_TIMEOUT;
}
out:
if (actual)
*actual = nbytes;
return status;
}
dc_status_t
dc_socket_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_socket_t *socket = (dc_socket_t *) abstract;
size_t nbytes = 0;
while (nbytes < size) {
fd_set fds;
FD_ZERO (&fds);
FD_SET (socket->fd, &fds);
int rc = select (socket->fd + 1, NULL, &fds, NULL, NULL);
if (rc < 0) {
s_errcode_t errcode = S_ERRNO;
if (errcode == S_EINTR)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = dc_socket_syserror(errcode);
goto out;
} else if (rc == 0) {
break; // Timeout.
}
s_ssize_t n = send (socket->fd, (const 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 (abstract->context, errcode);
status = dc_socket_syserror(errcode);
goto out;
} else if (n == 0) {
break; // EOF.
}
nbytes += n;
}
if (nbytes != size) {
status = DC_STATUS_TIMEOUT;
}
out:
if (actual)
*actual = nbytes;
return status;
}