jancity/src/net/win9x/src/serial.c

449 lines
9.7 KiB
C

#include <net.h>
#include <net_private.h>
#include <windows.h>
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct net_host_domain
{
HANDLE com;
enum net_role role;
struct transport_handle h;
struct net_event ev;
struct req
{
const void *buf;
size_t n;
OVERLAPPED ov;
enum req_type
{
REQ_TYPE_READ,
REQ_TYPE_WRITE
} type;
} *reqs;
size_t n_reqs;
};
int net_write_serial(struct net_host_domain *const h, const net_peer p,
const void *const buf, const size_t n)
{
return transport_send(&h->h, buf, n);
}
int net_close_serial(struct net_host_domain *const h)
{
if (h)
{
if (h->com != INVALID_HANDLE_VALUE)
CloseHandle(h->com);
if (h->reqs)
for (size_t i = 0; i < h->n_reqs; i++)
{
struct req *const r = &h->reqs[i];
if (r->ov.hEvent)
CloseHandle(r->ov.hEvent);
}
free(h->reqs);
}
free(h);
return 0;
}
int net_update_serial(struct net_host_domain *const h)
{
return transport_update(&h->h);
}
static void on_received(const struct transport_event *const ev,
void *const arg)
{
struct net_host_domain *const h = arg;
net_serial_on_received(ev, &h->ev);
}
static struct req *find_req(struct net_host_domain *const h,
const void *const buf, const size_t n, const enum req_type t)
{
for (size_t i = 0; i < h->n_reqs; i++)
{
struct req *const r = &h->reqs[i];
if (r->buf == buf && r->n == n && r->type == t)
return r;
}
return NULL;
}
static int free_req(struct net_host_domain *const h, struct req *const r)
{
for (size_t i = 0; i < h->n_reqs; i++)
if (&h->reqs[i] == r)
{
const HANDLE ev = r->ov.hEvent;
if (ev)
CloseHandle(ev);
for (size_t j = i + 1; j < h->n_reqs; j++)
h->reqs[j - 1] = h->reqs[j];
const size_t n = h->n_reqs - 1;
if (n)
{
if (!(h->reqs = realloc(h->reqs, n * sizeof *h->reqs)))
{
fprintf(stderr, "%s: realloc(3) failed: %s\n",
__func__, strerror(errno));
return -1;
}
}
else
{
free(h->reqs);
h->reqs = NULL;
}
h->n_reqs = n;
return 0;
}
return -1;
}
static struct req *alloc_req(struct net_host_domain *const h,
const void *const buf, const size_t n, const enum req_type t)
{
if (!(h->reqs = realloc(h->reqs, (h->n_reqs + 1) * sizeof *h->reqs)))
{
fprintf(stderr, "%s: realloc(3) failed: %s\n",
__func__, strerror(errno));
return NULL;
}
struct req *const r = &h->reqs[h->n_reqs++];
*r = (const struct req)
{
.buf = buf,
.n = n,
.type = t,
.ov =
{
.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)
}
};
if (!r->ov.hEvent)
{
fprintf(stderr, "%s: CreateEvent failed: %#lx\n", __func__,
GetLastError());
free_req(h, r);
return NULL;
}
return r;
}
static int get_result(struct net_host_domain *const h, struct req *const r)
{
DWORD res;
if (GetOverlappedResult(h->com, &r->ov, &res, FALSE) == FALSE)
{
const DWORD error = GetLastError();
if (error != ERROR_IO_INCOMPLETE)
{
fprintf(stderr, "%s: GetOverlappedResult failed: %#lx\n",
__func__, error);
return -1;
}
else
return 0;
}
else if (ResetEvent(r->ov.hEvent) == FALSE)
{
fprintf(stderr, "%s: ResetEvent failed: %#lx\n", __func__,
GetLastError());
return -1;
}
else if (free_req(h, r))
{
fprintf(stderr, "%s: free_req failed\n", __func__);
return -1;
}
return res;
}
static int on_read(void *const buf, const size_t n, void *const arg)
{
const enum req_type t = REQ_TYPE_READ;
struct net_host_domain *const h = arg;
struct req *r = find_req(h, buf, n, t);
if (!r)
{
DWORD res;
if (!(r = alloc_req(h, buf, n, t)))
{
fprintf(stderr, "%s: alloc_req failed\n", __func__);
goto failure;
}
else if (ReadFile(h->com, buf, n, &res, &r->ov) == FALSE
&& GetLastError() != ERROR_IO_PENDING)
{
fprintf(stderr, "%s: ReadFile failed: %#lx\n",
__func__, GetLastError());
goto failure;
}
}
return get_result(h, r);
failure:
if (r)
free_req(h, r);
return -1;
}
static int on_write(const void *const buf, const size_t n, void *const arg)
{
const enum req_type t = REQ_TYPE_WRITE;
struct net_host_domain *const h = arg;
struct req *r = find_req(h, buf, n, t);
if (!r)
{
DWORD res;
if (!(r = alloc_req(h, buf, n, t)))
{
fprintf(stderr, "%s: alloc_req failed\n", __func__);
goto failure;
}
else if (WriteFile(h->com, buf, n, &res, &r->ov) == FALSE
&& GetLastError() != ERROR_IO_PENDING)
{
fprintf(stderr, "%s: WriteFile failed: %#lx\n",
__func__, GetLastError());
goto failure;
}
printf("%s: outgoing packet, buf: %p, n: %u: [", __func__, buf, (unsigned)n);
for (size_t i = 0; i < n; i++)
{
const char b = ((const char *)buf)[i];
printf("%02hhx", b);
if (isprint((unsigned char)b))
printf(" (%c)", b);
if (i + 1 < n)
printf(", ");
}
printf("]\n");
}
return get_result(h, r);
failure:
if (r)
free_req(h, r);
return -1;
}
static struct transport_cfg get_transport_cfg(void *const arg)
{
return (const struct transport_cfg)
{
.arg = arg,
.received = on_received,
.read = on_read,
.write = on_write
};
}
static HANDLE init_com(const char *const dev)
{
static const char *const prefix = "\\\\.\\";
/* snprintf(NULL, 0, ...) could have been used to determine the
* total length of the string. However, this is a C99 feature that
* must be supported by the underlying libc, which is not the case
* for Win9x, where only ANSI C is supported. */
char *const fulldev = calloc(1, strlen(prefix) + strlen(dev) + 1);
HANDLE ret = INVALID_HANDLE_VALUE;
if (!fulldev)
{
fprintf(stderr, "%s: calloc(3) failed: %s\n", __func__,
strerror(errno));
goto end;
}
strcat(fulldev, prefix);
strcat(fulldev, dev);
ret = CreateFile(
fulldev,
GENERIC_READ | GENERIC_WRITE,
0, /* No sharing. */
NULL, /* No security. */
OPEN_EXISTING, /* Open existing port only. */
FILE_FLAG_OVERLAPPED, /* Asynchronous I/O. */
NULL /* NULL for COM devices. */);
if (ret == INVALID_HANDLE_VALUE)
fprintf(stderr, "%s: CreateFile failed: %#lx\n", __func__,
GetLastError());
end:
free(fulldev);
return ret;
}
static int config_baud(const HANDLE com, const unsigned long baud)
{
DCB dcb =
{
.DCBlength = sizeof dcb,
.BaudRate = baud,
.ByteSize = 8,
.StopBits = ONESTOPBIT,
.Parity = NOPARITY,
.fBinary = TRUE
};
if (SetCommState(com, &dcb) == FALSE)
{
fprintf(stderr, "%s: SetCommState failed: %#lx\n",
__func__, GetLastError());
return -1;
}
return 0;
}
struct net_host_domain *net_connect_serial(const union net_connect *const c)
{
struct net_host_domain *const h = malloc(sizeof *h);
const struct net_serial_cfg *const cfg = &c->serial.cfg;
if (!h)
goto failure;
*h = (const struct net_host_domain)
{
.role = NET_ROLE_CLIENT,
.com = init_com(cfg->dev),
.h =
{
.cfg = get_transport_cfg(h)
}
};
if (h->com == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "%s: init_com failed\n", __func__);
goto failure;
}
else if (config_baud(h->com, cfg->baud))
{
fprintf(stderr, "%s: config_com_baud failed\n", __func__);
goto failure;
}
else if (transport_connect(&h->h))
{
fprintf(stderr, "%s: transport_connect failed\n", __func__);
goto failure;
}
return h;
failure:
net_close_serial(h);
return NULL;
}
struct net_host_domain *net_server_serial(const union net_server *const s)
{
struct net_host_domain *const h = malloc(sizeof *h);
const struct net_serial_cfg *const cfg = &s->serial.cfg;
if (!h)
goto failure;
*h = (const struct net_host_domain)
{
.role = NET_ROLE_SERVER,
.com = init_com(cfg->dev),
.h =
{
.cfg = get_transport_cfg(h)
}
};
if (h->com == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "%s: init_com failed\n", __func__);
goto failure;
}
else if (config_baud(h->com, cfg->baud))
{
fprintf(stderr, "%s: config_com_baud failed\n", __func__);
goto failure;
}
return h;
failure:
net_close_serial(h);
return NULL;
}
int net_init_serial(void)
{
return 0;
}
void net_deinit_serial(void)
{
}
int net_set_event_serial(struct net_host_domain *const d,
const struct net_event *const ev)
{
d->ev = *ev;
return 0;
}
enum net_role net_role_serial(const struct net_host_domain *const h)
{
return h->role;
}
bool net_serial_unique(void)
{
return false;
}