legend/
legend/area/
legend/player/
/* Copyright (c) 2005 Mackenzie Straight           <eiz@codealchemy.org> */
/* Use of this software for any purpose is allowed, provided this notice */
/* is preserved. It is provided as-is. If it breaks, you get to keep     */
/* both pieces.                                                          */

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/select.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "opas.h"

static opas_client_t *_opas_new_client(int sock) {
    opas_client_t *rv = malloc(sizeof(opas_client_t));

    rv->sock = sock;
    rv->in_len = 0;
    rv->state = opas_state_header;
    rv->st_hdr = 0;
    rv->st_len = 4;

    return rv;
}

opas_client_t *opas_open(char *server, int port) {
    int sock;
    struct hostent *host;
    struct sockaddr_in addr;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return NULL;

    if ((host = gethostbyname(server)) == NULL)
        return NULL;

    memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length);
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;
    
    if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
        return NULL;

    return _opas_new_client(sock);
}

void opas_set_dispatch(opas_client_t *client, dispatch_func func) {
    client->dispatch = func;
}

static void _opas_init_header(opas_header_t *hdr, 
                              uint32_t length, 
                              uint32_t flags) {
    hdr->bits = OPAS_VERSION;
    hdr->bits |= length-4;
    hdr->bits |= flags;
    hdr->bits = htonl(hdr->bits);
}

void opas_send_req4(opas_client_t *client, uint32_t user_data, struct in_addr ipaddr) {
    opas_request4_t req;

    _opas_init_header(&req.header, sizeof(opas_request4_t), 0);
    req.user_data = user_data;
    req.ipaddr = ipaddr;

    send(client->sock, &req, sizeof(opas_request4_t), 0);
}

void opas_send_ping(opas_client_t *client) {
    opas_header_t hdr;

    _opas_init_header(&hdr, sizeof(opas_header_t), BIT(14));
    send(client->sock, &hdr, sizeof(opas_header_t), 0);
}

static int _opas_has_input(opas_client_t *client, int dowait) {
    fd_set fds;
    struct timeval tv;

    FD_SET(client->sock, &fds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    if (select(client->sock+1, &fds, NULL, NULL, dowait ? NULL : &tv) < 0)
        return -1;

    return FD_ISSET(client->sock, &fds);
}

static opas_header_t *_opas_cons_packet(opas_client_t *client) {
    int len = 4 + (ntohl(client->st_hdr)&0xFF);
    opas_header_t *rv = malloc(clamp(len, 0, client->in_len+4));

    memcpy(4+(char*)rv, client->inbuf, client->in_len);
    rv->bits = client->st_hdr;
    client->st_hdr = 0;

    return rv;
}

int opas_wait(opas_client_t *client) {
    return _opas_has_input(client, 1);
}

int opas_dispatch(opas_client_t *client) {
    while (_opas_has_input(client, 0) > 0) {
        char buf[1024];
        int i, len = recv(client->sock, buf, 1024, 0);

        if (len < 0)
            return -1;
        else if (len == 0)
            return 0;

        for (i = 0; i < len; ++i) {
            if (client->state == opas_state_header) {
                client->st_hdr |= buf[i] << ((4 - client->st_len) << 3);
                if (!--client->st_len) {
                    client->state = opas_state_packet;
                    client->st_len = ntohl(client->st_hdr) & 0xFF;
                }
            } else if (client->state == opas_state_packet) {
                if (client->in_len < BUF_SIZE-1)
                    client->inbuf[client->in_len++] = buf[i];
                if (!--client->st_len) {
                    client->dispatch(client, _opas_cons_packet(client));
                    client->state = opas_state_header;
                    client->st_len = 4;
                }
            }
        }
    }

    return 1;
}


opas_packet_type_t opas_packet_type (opas_header_t *packet) {
    int hdr = ntohl(packet->bits);

    if (hdr & BIT(11) && hdr & BIT(10))
        return opas_packet_reply_proxy;
    else if (hdr & BIT(11))
        return opas_packet_reply_neg;
    else if (hdr & BIT(8))
        return opas_packet_error;
    else
        return opas_invalid_packet;
}

int opas_packet_length (opas_header_t *packet) {
    return ntohl(packet->bits) & 0xFF;
}