eth_proto.c
author viric@mandarina
Thu, 21 Aug 2008 23:21:22 +0200
changeset 93 7d9b7a6da507
parent 88 a7f546938313
permissions -rw-r--r--
Removing direct references to /usr in the Makefile, for nix.

/*
    Terminal Mixer - multi-point multi-user access to terminal applications
    Copyright (C) 2007  LluĂ­s Batlle i Rossell

    Please find the license in the provided COPYING file.
*/
#include <netinet/in.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>

#include "main.h"
#include "eth_linux.h"

enum {
    MAXPACKET = 1500,
    MAXSEQ = 100,
    HEAD = 13
};

static struct
{
    int socket;
    char partner[6];
    int partner_set;
    int send_acked;
    int send_retries_left;
    unsigned int seq_send;
    unsigned int seq_wait;
    unsigned int wrong_recv;
    char send_buffer[MAXPACKET];
    int send_buffer_size;
    int port;
} edata;

enum Control {
    SEND,
    ACK,
    INIT
};

static char eth_buffer[MAXPACKET];

static void eth_fill_mac(unsigned char *mac, const char *str);

static int make_head(unsigned char *data, unsigned int seq, enum Control c,
        int size)
{
    *((unsigned int *) data) = htonl(seq);
    data[4] = (unsigned char) c;
    *((unsigned int *)(data+5)) = htonl(edata.port);
    *((unsigned int *)(data+5+4)) = htonl(size);
    return HEAD;
}

static int parse_head(unsigned char *data, unsigned int *seq, enum Control *c,
        int *port, int *size)
{
    *seq = ntohl( *((unsigned int*) data) );
    *c = data[4];
    if (port)
        *port = ntohl(*((unsigned int *)(data+5)));
    if (size)
        *size = ntohl(*((unsigned int *)(data+5+4)));
    return HEAD;
}

int eth_proto_max_send()
{
    return MAXPACKET - HEAD;
}

void eth_proto_init()
{
    edata.socket = -1;
    edata.port = command_line.eth_port;
    edata.seq_send = 0;
    edata.seq_wait = 0;
    edata.send_acked = 1; /* Fine at the beginning, as if the last data was acked */
    edata.partner_set = 0;
}

int eth_proto_allow_sending()
{
    return edata.send_acked;
}

static int seq_next(int val)
{
    if (val >= 0 && val < MAXSEQ)
        val = val + 1;
    else
        val = 0;
    return val;
}

static int seq_before(int val)
{
    if (val > 0 && val < MAXSEQ)
        val = val - 1;
    else
        val = MAXSEQ;
    return val;
}

int eth_proto_open(enum Eth_type type)
{
    edata.socket = eth_open(command_line.eth_device, type);
    if (edata.socket == -1)
        error("Cannot open device %s", command_line.eth_device);

    if ( !command_line.is_server)
    {
        if (command_line.c_param.server_address == 0)
            error("You must specify the MAC you want to connect to.");
        eth_fill_mac(edata.partner, command_line.c_param.server_address);
        edata.partner_set = 1;

        make_head(eth_buffer, edata.seq_send, INIT, 0);
        eth_send(command_line.eth_device, edata.partner, eth_buffer, HEAD);
        edata.seq_send = seq_next(edata.seq_send);
    }

    return edata.socket;
}

int eth_proto_recv(char *data, int size)
{
    int res;
    int seq;
    int data_length;
    int port;
    enum Control c;
    char partner[6];

    do {
            res = eth_recv(eth_buffer, sizeof(eth_buffer), partner);
            edata.partner_set = 1;
    } while(res < HEAD);
    parse_head(eth_buffer, &seq, &c, &port, &data_length);
    if (port != edata.port)
        return -1; /* Nothing the parent should care about. Not a packet for us. */

    /* We admit any first connection */
    if (seq == 0 && c == INIT)
    {
      edata.seq_wait = 1; /* next of the just receive 0 */
      edata.seq_send = 0; /* New partner we send to, so 0 sent */
      memcpy(edata.partner, partner, sizeof(edata.partner));
      return -1; /* Nothing the parent should care about */
    }
    if (c == SEND)
    {
        if (seq != edata.seq_wait && seq != seq_before(edata.seq_wait))
        {
            dump_line("Wrong data packet seq received. Recvd: %i Expected: %i\n",
                    seq, edata.seq_wait);
            edata.wrong_recv++;
            return -1;
        }

        if (seq == seq_before(edata.seq_wait))
            dump_line("Repeated data seq received: %i\n", seq);

        if (seq == edata.seq_wait)
        {
            if (data_length == 0)
            {
                edata.partner_set = 0;
                edata.seq_wait = 0;
                edata.seq_send = 0;
                /* We should send ACK anyway */
            }
            else
            {
                memcpy(data, eth_buffer + HEAD, data_length);
                edata.seq_wait = seq_next(edata.seq_wait);
            }
        }

        /* Ack the packed we received. In these conditions:
         * - We received the packed we expected
         * - We received a repeat of the old packet. The
         *   ACK was lost probably, so we resend it */
        make_head(eth_buffer, seq, ACK, 0);
        eth_send(command_line.eth_device, edata.partner, eth_buffer, HEAD);
    }
    else if (c == ACK)
    {
        if (seq == edata.seq_send)
        {
            edata.send_acked = 1;
            edata.seq_send = seq_next(edata.seq_send);
            unprogram_timeout();
        }
        else
        {
            dump_line("Wrong ack received. Recvd: %i Expected: %i\n",
                    seq, edata.seq_send);
        }
        return -1; /* not data */
    }
    else
        return -1;

    return data_length;
}

static int eth_proto_link_send()
{
    int sent;
    sent = eth_send(command_line.eth_device,
            edata.partner,
            edata.send_buffer,
            edata.send_buffer_size);

    if (sent >= 0) /* expected */
    {
        edata.send_retries_left -= 1;
        edata.send_acked = 0;
        program_timeout(1);
    }
    else /* strange case, data not sent */
    {
        sent = 0;
        edata.send_acked = 1;
    }
    return sent;
}

int eth_proto_send(const char *data, int size)
{
    int sent;

    assert(edata.send_acked);

    if (!edata.partner_set)
    {
        if (edata.seq_send == 0 && !command_line.is_server
                && command_line.c_param.server_address != 0)
        {
            eth_fill_mac(edata.partner, command_line.c_param.server_address);
            edata.partner_set = 1;
        }
        else
            return 0;
    }

    edata.send_retries_left = 3;

    /* Prepare packet */
    make_head(edata.send_buffer, edata.seq_send, SEND, size);
    memcpy(edata.send_buffer+HEAD, data, size);
    edata.send_buffer_size = size + HEAD;

    sent = eth_proto_link_send();
    sent -= HEAD;
    return sent;
}

int eth_proto_process_timeouts()
{
    if (!edata.send_acked && did_timeout_happen())
    {
        unprogram_timeout();
        if (edata.send_retries_left > 0)
        {
            dump_line("Retrying. Left:%i\n", edata.send_retries_left);
            eth_proto_link_send();
        }
        else
        {
            /* The connection has been lost */
            dump_line("Connection lost");
            edata.send_acked = 1;
            edata.partner_set = 0;
            edata.seq_wait = 0;
            edata.seq_send = 0;
            return 0; /* FAIL */
        }
    }
    return 1; /* OK */
}

static void eth_fill_mac(unsigned char *mac, const char *str)
{
  int res;
  int imac[6];
  res = sscanf(str, "%x:%x:%x:%x:%x:%x",
      &imac[0], &imac[1], &imac[2], &imac[3], &imac[4], &imac[5]);
  if (res != 6)
  {
    error("Error parsing mac: %02x:%02x:%02x:%02x:%02x:%02x\n",
      imac[0], imac[1], imac[2], imac[3], imac[4], imac[5]);
  }
  mac[0] = imac[0];  mac[1] = imac[1];  mac[2] = imac[2];
  mac[3] = imac[3];  mac[4] = imac[4];  mac[5] = imac[5];
}