//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / pf
//
//File: atmsockdev.cpp
//
//Version: $Revision: 1.2 $
//
//State: $State: Exp $
//
//Date: $Date: 1998/10/19 08:46:02 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Juhana Räsänen
//
//Description:
//      See the corresponding header file
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History: 
//
//

#include "atmsocket.h"
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>	// strerror
#include <errno.h>	// errno
#include <stdio.h>
#include <sys/socket.h>
#include <atm.h>
#include "pf/debug.h"

//
//Function: pfATMsocket constructor
//
//Description:
//    Calls base class constructor and initializes class specific
//    variables. Buffers are allocated in the openDevice method as
//    it can be done only after opening the socket due to the buffer
//    constraints.
//

pfATMsocket :: pfATMsocket(pfUlong bufferSize_)
    : pfDevice(bufferSize_),
      _writeBufferStart(0),
      _realBufferSize(0)
{
    return;
}

//
//Function: pfATMsocket destructor
//
//Description:
//    Closes the device. Buffers are freed in closeDevice() which is
//    implemented for pfATMsocket class, because buffers are
//    allocated in openDevice(). If freeBuffers() were called here,
//    it might cause a memory leak if the user of the class opens
//    and closes the device without destroying it.
//

pfATMsocket :: ~pfATMsocket (void)
{
    closeDevice();
    return;
}

//
//Function: openDevice
//
//Description:
//    Opens a Linux ATM PVC AAL5 socket to the port/VPI/VCI given as
//    constructor parameters. After the socket is opened and connected,
//    buffers are allocated according to buffer constraints for the
//    socket. If anything goes wrong in the opening of the socket,
//    it is not opened and device ERROR status bit is set.
//

bool pfATMsocket :: openDevice(int port_, int vpi_, int vci_)
{
    if (_fd != NO_FD)
    {
	closeDevice();
    }

    struct sockaddr_atmpvc addr;
    struct atm_qos qos;
    int descriptor = socket(PF_ATMPVC, SOCK_DGRAM, ATM_AAL5);
    if (descriptor < 0)
    {
        setStatusError();
        debugString("pfATMsocket: Error in openDevice : socket", strerror (errno));
        return 1;
    }

    setFD(descriptor);
    memset(&qos, 0, sizeof(qos));
    qos.txtp.traffic_class = ATM_UBR;
    qos.txtp.max_sdu = _bufferSize;
    qos.rxtp = qos.txtp;
    if (setsockopt(_fd, SOL_ATM, SO_ATMQOS, &qos, sizeof(qos)) < 0)
    {
        setStatusError();
        debugString("pfATMsocket: Error in openDevice : setsockopt", strerror(errno));
        closeDevice();
        return 1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sap_family = AF_ATMPVC;
    addr.sap_addr.itf = port_;
    addr.sap_addr.vpi = vpi_;
    addr.sap_addr.vci = vci_;
    if (::connect(_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0)
    {
        setStatusError();
        debugString("pfATMsocket: Error in openDevice : connect", strerror(errno));
        closeDevice();
        return 1;
    }

#if 0
    debugPfUlong("SOL_SOCKET", SOL_SOCKET);
    debugPfUlong("SOL_ATM", SOL_ATM);
    debugPfUlong("SO_ATMQOS", SO_ATMQOS);
    debugPfUlong("SO_BCRXOPT", SO_BCRXOPT);

    debugPfUlong("PF_ATMPVC", PF_ATMPVC);
    debugPfUlong("SOCK_DGRAM", SOCK_DGRAM);
    debugPfUlong("ATM_AAL5", ATM_AAL5);
    debugPfUlong("AF_ATMPVC", AF_ATMPVC);
    debugPfUlong("ATM_UBR", ATM_UBR);

    size_t length = sizeof(_bc);

    debugPfUlong("bc_length", length);

#ifndef OLD_ATM_VERSION
    if (0 > getsockopt(_fd,
                       SOL_SOCKET,
                       SO_BCRXOPT,
                       (void *) &_bc,
                       &length))
#else
    if (0 > getsockopt(_fd,
                       SOL_SOCKET,
                       SO_BCRXOPT,
                       (char *) &_bc,
                       (int *) &length))
#endif // OLD_ATM_VERSION
    {
        setStatusError();
        debugString("pfATMsocket: Error in openDevice : getsockopt", strerror(errno));
        closeDevice();
        return 1;
    }
    // The buffer allocation magic is copied from the Linux ATM
    // interface example code.
    size_t buf_len = _bufferSize - _bc.size_off + _bc.size_fac - 1;
    buf_len = buf_len - (buf_len % _bc.size_fac) + _bc.size_off;
    if (buf_len < _bc.min_size)
    {
        buf_len = _bc.min_size;
    }
    _readBuffer = new char[buf_len + _bc.size_fac - 1];
    _writeBuffer = new char[buf_len + _bc.size_fac - 1];
#endif

    _readBuffer = new char[_bufferSize];
    _writeBuffer = new char[_bufferSize];

    if ((_readBuffer == 0) || (_writeBuffer == 0))
    {
        setStatusError();
        cerr << "pfATMsocket: Error in openDevice" << endl;
        cerr << "Couldn't allocate memory!" << endl;
        closeDevice();
        return 1;
    }
    _realBufferSize = _bufferSize;
    
    return 0;
}

//
//Function: pfATMsocket :: callbackWrite
//
//Description:
//    Redefined for ATM sockets because due to buffer alignment constraints
//    the data may not be located in the beginning of the buffer, but at
//    the position pointed by _writeBufferStart.
//

void pfATMsocket :: writeCallback(void)
{
    int nbytes;

    if (_writeBuffer != 0)
    {
        assert((pfUlong) _writeFrame.length() <= _bufferSize);
        _writeBufferLength = _writeFrame.length();

#if 0
        ptrdiff_t pos =
            (ptrdiff_t) (_writeBuffer - _bc.buf_off + _bc.buf_fac - 1);
        _writeBufferStart =
            (char *) (pos - (pos % _bc.buf_fac) + _bc.buf_off);
#endif
	_writeBufferStart = _writeBuffer;
        if (_writeBufferLength != (int) _realBufferSize)
        {
            memset(_writeBufferStart + _writeBufferLength, 0,
                   (_realBufferSize - _writeBufferLength));
        }
        _writeFrame.copyData((unsigned char *) _writeBufferStart);

        if ((nbytes = ::write(_fd,
                              _writeBufferStart,
                              _writeBufferLength)) == -1)
        {
            setStatusError();
            debugString("pfATMsocket: Error in callbackWrite : write", strerror(errno));
            writeAction(errno);
        }
        else
        {
            _writeBufferStart += nbytes;
            _writeBufferLength -= nbytes;
            if (_writeBufferLength == 0)
            {
                // write completed
                writeAction(0);
                clearStatusWrite();
                _writePending = 0;
            }
            // If the whole frame was not written at once, we will just
            // return here and leave the device WRITE status bit set, in
            // which case OVOPS will reschedule it for write later, but
            // in meantime some other tasks may get executed.
        }
    }
    else
    {
        setStatusError();
        cerr << "pfATMsocket: Error in callbackWrite: No buffer" << endl;

        // Call writeAction with nonzero value to indicate error
        writeAction(1);
    }
    return;
}

//
//Function: closeDevice
//
//Description:
//    Closes the file.
//    Returns zero if the closing was successful. Otherwise, returns
//    non-zero value. Frees also the buffers allocated in openDevice,
//    which is the reason why closeDevice is re-implemented.
//

bool pfATMsocket :: closeDevice(void)
{
    bool failure = 0;
    if (_fd != NO_FD)
    {
        if (::close(_fd) == -1)
        {
            debugString("pfDevice: Error in closeDevice() : close", strerror(errno));
            failure = 1;
        }
        else
        {
            setFD(NO_FD);
            _readPending = 0;
            _writePending = 0;
        }
    }

    freeBuffers();
    return failure;
}

//
//Function: freeBuffers
//
//Description:
//    Method to free the buffers
//

void pfATMsocket :: freeBuffers(void)
{
    delete [] _readBuffer;
    delete [] _writeBuffer;
    return;
}

