//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / testing/testadapter/adapter
//
//File: pducoderbase
//
//Version: $Revision: 1.30 $
//
//State: $State: Exp $
//
//Date: $Date: 1999/01/21 13:06:43 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Timo Kokkonen
//      Jussi Turunen
//
//Description:
//      See corresponding header file.
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History:
//

#include <stdio.h>         // sprintf
#include "pf/debug.h"
#include "pf/error.h"
#include "pducoderbase.h"

const string pduCoderBase::UNKNOWN_PDUStr = "UNKNOWN_PDU";
const string pduCoderBase::UNKNOWN_FIELDStr = "UNKNOWN_FIELD";

//Function: constructor
//
//Description:
// 
pduCoderBase :: pduCoderBase(void)
    : _dataBuffer(""),
      _message(),
      _identifier(UNKNOWN_PDUStr),
      _index(0)
{
    return;
}

//
//Function: destructor
//
//Description:
//
//
pduCoderBase :: ~pduCoderBase(void)
{
    return;
}

//----------------------------------------------------------------------

//
//Function: encode
//
//Description:
//      Encodes the otMessage::SerializedMessage given as a parameter
//      to a pfFrame and returns the pfFrame.
//

pfFrame pduCoderBase :: encode(const otMessage::SerializedMessage &message_)
{
    unsigned int index = message_.length(); // to get indexing correct

    while (index-- > 0)
    {
        string identifier = message_[index].identifier.in();
        string stringValue = message_[index].value.in();
        char buffer[512];

        // The tester sends "ignore" fields to delimeter structs.
        // They are stored here since it expects these fields back
        // in the following message which uses structured elements.
        if (identifier.compare((const string) "ignore", 0, 6) == 0)
        {
            debugString("Ignoring a field", identifier);
            continue;
        }

        if (stringValue.length() == 0)
        {
            printWarningMsg("Encode 0 length of " + identifier);
        }

        // This used to control printed debug info length, because
        // the longest fields make it difficult to read the log
        if (stringValue.length() > 200)
        {
            string tmp = stringValue.substr(0,200);
            sprintf(buffer,
                    "%s ... first 200 chars, data length %d",
                    (tmp.c_str()),
                    stringValue.length());
        }
        else if (stringValue.length() > 0)
        {
            sprintf(buffer, 
                    "%s", 
                    (stringValue.c_str()));
        }

        switch (message_[index].kind)
        {
            // some kind of debug printing would nice for EoE and BoE
            case otMessage::BegOfElement:
                debugUser("BegOfElement");
                break;
            case otMessage::EndOfElement:
                debugUser("EndOfElement");
                break;
            case otMessage::BitString:
                debugString("BitString", buffer);
                encodeBitString(stringValue);
                break;
            case otMessage::OctetString:
                debugString("OctetString", buffer);
                encodeOctetString(stringValue);
                break;
            case otMessage::HexString:
                debugString("HexString", buffer);
                // Can use same method as octetstring case
                encodeOctetString(stringValue);
                break;
            case otMessage::CharString:
                debugString("CharString", buffer);
                encodeIA5String(stringValue);
                break;
            default:
                break;
        }
    }        

//    if (_ignoreLength != 0)
//    {
//        debugPfUlong("Number of ignored fields", _ignoreLength);
//    }

    pfFrame encodedFrame = dataBufferToFrame();
    
#if 0
#ifndef __SSCOP_TESTING__
// this hack is used for UNI testing
    pfUlong len = encodedFrame.length() - 9;
    debugPfUlong("modified length of frame", len);
    if(len > 255)
    {
        debugUser("ETS_ERROR: modified length incorrect");
    }
    encodedFrame.write(len, 8);
#endif
#endif

    debugFrame("encoded frame", encodedFrame);
    // _dataBuffer.clear here too

    return encodedFrame;
}

//----------------------------------------------------------------------

//
//Function: encodeBitString
//
//Description:
//      Encodes a received bitString to dataBuffer.
//

void pduCoderBase :: encodeBitString(const string &value_)
{
    _dataBuffer.putFirstBits(value_);
    debugPfUlong("databuffer length", _dataBuffer.length());
    return;
}

//
//Function: encodeOctetString
//
//Description:
//      Encodes a received octetString to dataBuffer.
//

void pduCoderBase :: encodeOctetString(const string &value_)
{
    pfBitString octetValue = octetStringToBitString(value_);
    _dataBuffer.putFirstBits(octetValue.toString());
    debugPfUlong("databuffer length", _dataBuffer.length());
    return;
}

//
//Function: encodeIA5String
//
//Description:
//      Encodes a received IA5String to dataBuffer.
//

void pduCoderBase :: encodeIA5String(const string &value_)
{
    pfUlong IA5value;
    pfUlong i = value_.length();

    while (i > 0)
    {
        IA5value = value_[i-1];

        _dataBuffer.putFirstBits(IA5value, 7);
        _dataBuffer.putFirstBits(0, 1);
        --i;
    }
    debugPfUlong("databuffer length", _dataBuffer.length());
    return;
}

//----------------------------------------------------------------------

//
//Function: decodeBitString
//
//Description:
//      Decodes bitString length_ bits from _dataBuffer to _message.
//

void pduCoderBase :: decodeBitString(const string &name_,
                                     pfUlong length_)
{
    if (_dataBuffer.length() < length_)
    {
        // if dataBuffer has less data than what we are trying to read
        // the read length value is set to that
        length_ = _dataBuffer.length();
        printErrorMsg("Not enough data in dataBuffer");
    }
    
    if (length_ != 0)
    {
        string value = _dataBuffer.toString(length_);
        insertToMessage(name_, value, otMessage::BitString);
        removeData(length_);
    }
    else
    {
        printErrorMsg("Data buffer empty. Field not inserted " + name_);
    }

    return;
}

//
//Function: decodeOctetString
//
//Description:
//      Decodes octetString from _dataBuffer to message.
//

void pduCoderBase :: decodeOctetString(const string &name_,
                                       pfUlong length_)
{
    pfBitString tmp;
    tmp = alignLength(length_, OCTETSIZE);

    if (length_ != 0)
    {
        string octetValue = bitStringToOctetString(tmp);
        insertToMessage(name_, octetValue, otMessage::OctetString);
        removeData(length_);
    }
    else
    {
        printErrorMsg("Data buffer empty. Field not inserted " + name_);
    }
    
    return;
}

//
//Function: decodeHexString
//
//Description:
//      Decodes hexString from _dataBuffer to message.
//

void pduCoderBase :: decodeHexString(const string &name_,
                                     pfUlong length_)
{
    pfBitString tmp;
    
    tmp = alignLength(length_, HEXSIZE);

    if (length_ != 0)
    {
        string hexValue = bitStringToOctetString(tmp);
        insertToMessage(name_, hexValue, otMessage::HexString);
        removeData(length_);
    }
    else
    {
        printErrorMsg("Data buffer empty. Field not inserted " + name_);
    }
    
    return;
}

//
//Function: decodeIA5String
//
//Description:
//      Decodes IA5String from _dataBuffer to message.
//

void pduCoderBase :: decodeIA5String(const string &name_,
                                     pfUlong length_)
{
    pfBitString tmp;
    char IA5char;

    tmp = alignLength(length_, OCTETSIZE);

    if (length_ != 0)
    {
        string IA5value("");
        while (tmp.length() != 0)
        {
            tmp.getFirstBits(1);
            IA5char = (char)tmp.getFirstBits(7);
            IA5value += IA5char;
        }
        insertToMessage(name_, IA5value, otMessage::CharString);
        removeData(length_);
    }
    else
    {
        printErrorMsg("Data buffer empty. Field not inserted " + name_);
    }

    return;
}

// Function:
//     decodeBegOfElement
//     decodeEndOfElement
//
// Description:
//     The BeginningOElement and EndOfElement fields have no value, just
//     type and name.

void pduCoderBase :: decodeBegOfElement(const string &name_)
{
    debugString("Begin struct", name_);
    insertToMessage(name_, "", otMessage::BegOfElement);
    return;
}

void pduCoderBase :: decodeEndOfElement(const string &name_)
{
    debugString("End struct", name_);
    insertToMessage(name_, "", otMessage::EndOfElement);
    return;
}


//----------------------------------------------------------------------

//
//Function: alignLength
//
//Description:
//      Copy _dataBuffer to tmp length_ of blockSize_ blocks. If 
//      _dataBuffer has less data than length_*blockSize_ then 
//      method inserts all data from databuffer to tmp_ and makes 
//      tmp_ to dividing by blockSize_.
//

pfBitString pduCoderBase :: alignLength(pfUlong &length_,
                                        const pfUlong blockSize_)
{
    pfBitString tmp;

    if (_dataBuffer.length() < (length_ * blockSize_))
    {
        printErrorMsg("Not enough data in dataBuffer");
        length_ = _dataBuffer.length();
        tmp = pfBitString(_dataBuffer.toString(length_));

	// the remaining data is aligned to blockSize_
        if ((length_ % blockSize_) != 0)
        {
            forceToBlockSize(tmp, blockSize_);
        }
    }
    else
    {
        length_ = length_ * blockSize_;
        tmp = pfBitString(_dataBuffer.toString(length_));
    }

    return tmp;
}

//----------------------------------------------------------------------

//
//Function: forceToBlockSize
//
//Description:
//      Forces given pfBitString to divided by blockSize_.
//      Used to ensure that no blockSize_-bit aligned data goes to
//      bitStringToOctetString. This we avoid an exception there.
//
void pduCoderBase :: forceToBlockSize(pfBitString &bitString_,
                                      const pfUlong blockSize_)
{
    pfUlong padLength = blockSize_ - bitString_.length() % blockSize_;

    if (padLength != blockSize_)
    {
        printErrorMsg("data needeed force to block (pad's added)");
        bitString_.putFirstBits(0, padLength);
    }

    return;
}
//----------------------------------------------------------------------

//
//Function: insertToMessage
//
//Description:
//      Used in decode() methods to insert a field into a decoded otM::SM
//      NOTE! At the moment no int values can be inserted, so the value
//      of the number field in message is 0 by default.
//

void pduCoderBase :: insertToMessage(const string &name_,
                                     const string &value_,
                                     otMessage::ElementKind kind_)
{
    try
    {
        _message.length(_index + 1);

        _message[_index].kind = kind_;
        _message[_index].identifier = CORBA_string_dup(name_.c_str());
        _message[_index].value = CORBA_string_dup(value_.c_str());
        _message[_index].number = 0;
        ++_index;
    }
    catch(...)
    {
        printErrorMsg("Couldn't insert to message" + name_);
    }

    return;
}

//----------------------------------------------------------------------

//
//Function: frameToDataBuffer
//
//Description:
//      The handling of the received pfFrame is done using the 
//      _dataBuffer. The received data is moved from the frame to bitstring
//      and handled in that bitstring (_dataBuffer). This is done before the
//      start of the decoding of the received frame. This is used in decode().
//      _dataBuffer.clear is called to ensure that _dataBuffer is empty at 
//      the start of the decoding.

void pduCoderBase :: frameToDataBuffer(pfFrame frame_)
{
    otMessage::SerializedMessage emptyMessage;
    pfUlong length = frame_.length();
    
    // If using a derived class as a data member for an another class,
    // _dataBuffer retains it's previous value in every call to this
    // this method if it isn't cleared.
    // NOTE! _message needs also to be cleared.
    clearDataBuffer();
    _message = emptyMessage;
    _index = 0;

    // ++TODO consider moving the _dataBuffer.clear thing to a separate
    // method and using it in pduCoderBase::decode after the call to 
    // frameToDataBuffer

    while (length > 0)
    {
        _dataBuffer.putLastBits((pfUlong)frame_.getFirst(), OCTETSIZE);
        --length;
    }

    return;
}

//
//Function: dataBufferToFrame
//
//Description:
//      The handling of the received otMessage::SerializedMessage is done
//      by moving the message to a bitString (_dataBuffer) and the encoding
//      it to a frame. The frame is returned after this. This used in encode().
//

pfFrame pduCoderBase :: dataBufferToFrame(void)
{
    pfFrame frame;
    // number of octets
    pfUlong octets = _dataBuffer.length() / OCTETSIZE;

    debugPfUlong("The length of the databuffer when moving to a frame", 
                 _dataBuffer.length());

    while (octets > 0)
    {
        frame.putFirst((pfByte)_dataBuffer.getLastBits(OCTETSIZE));
        --octets;
    }
    if (_dataBuffer.length() != 0)
    {
        printErrorMsg(
            "dataBufferToFrame: _dataBuffer length not 8-bit aligned");
    }
    // To ensure that dataBuffer is really emptied.
    clearDataBuffer();

    return frame;
}

void pduCoderBase :: clearDataBuffer(void)
{
    _dataBuffer.clear();
    return;
}

// Function: getBitField
//
// Description:
//
//        Returns a string of length_ length, which has the data for a
//        bit field in the header (in SSCOP PL, rsvd or PDU_type).
//

string pduCoderBase :: getBitField(const pfUlong length_)
{
    string field = _dataBuffer.toString(length_);
    removeData(length_);

    return field;
}

//----------------------------------------------------------------------

//Function: storePDUname
//          getPDUname
//
//Description:
//      PDU name needs to be stored in decoding, because it is needed
//      when sending a otM::SM to the tester. 

void pduCoderBase :: storePDUname(const string &pduName_)
{
    _identifier = pduName_;
    return;
}

string pduCoderBase :: getPDUname(void)
{
    return _identifier;
}

//----------------------------------------------------------------------

//
//Function: octetStringToBitString
//
//Description:
//      Makes octetString to bitString. e.g "A1" -> "10100001"
//      Used because useHex didn't seem to work.
//

pfBitString pduCoderBase :: octetStringToBitString(const string &value_)
{
    pfBitString result("");

    for (pfUlong i = 0; i < value_.length(); ++i)
    {
        if (value_[i] == '0')
        {
            result.putLastBits(0, 4);
        }
        else if (value_[i] == '1')
        {
            result.putLastBits(1, 4);
        }
        else if (value_[i] == '2')
        {
            result.putLastBits(2, 4);
        }
        else if (value_[i] == '3')
        {
            result.putLastBits(3, 4);
        }
        else if (value_[i] == '4')
        {
            result.putLastBits(4, 4);
        }
        else if (value_[i] == '5')
        {
            result.putLastBits(5, 4);
        }
        else if (value_[i] == '6')
        {
            result.putLastBits(6, 4);
        }
        else if (value_[i] == '7')
        {
            result.putLastBits(7, 4);
        }
        else if (value_[i] == '8')
        {
            result.putLastBits(8, 4);
        }
        else if (value_[i] == '9')
        {
            result.putLastBits(9, 4);
        }
        else if (value_[i] == 'A')
        {
            result.putLastBits(10, 4);
        }
        else if (value_[i] == 'B')
        {
            result.putLastBits(11, 4);
        }
        else if (value_[i] == 'C')
        {
            result.putLastBits(12, 4);
        }
        else if (value_[i] == 'D')
        {
            result.putLastBits(13, 4);
        }
        else if (value_[i] == 'E')
        {
            result.putLastBits(14, 4);
        }
        else if (value_[i] == 'F')
        {
            result.putLastBits(15, 4);
        }
        else
        {
            printErrorMsg("octetStringToBitString: Not HEX value");
            THROW_TEMPORARY_FAILURE;
        }
    }

    return result;
}

//
//Function: bitStringToOctetString
//
//Description:
//      Makes bitString to octetString. e.g "10100001" -> "A1"
//
string pduCoderBase :: bitStringToOctetString(pfBitString value_)
{
    string result("");
    pfUlong value;

    while (value_.length() > 0)
    {
        value = value_.getFirstBits(4);

        switch (value)
        {
            case 0:
                result += '0';
                break;
            case 1:
                result += '1';
                break;
            case 2:
                result += '2';
                break;
            case 3:
                result += '3';
                break;
            case 4:
                result += '4';
                break;
            case 5:
                result += '5';
                break;
            case 6:
                result += '6';
                break;
            case 7:
                result += '7';
                break;
            case 8:
                result += '8';
                break;
            case 9:
                result += '9';
                break;
            case 10:
                result += 'A';
                break;
            case 11:
                result += 'B';
                break;
            case 12:
                result += 'C';
                break;
            case 13:
                result += 'D';
                break;
            case 14:
                result += 'E';
                break;
            case 15:
                result += 'F';
                break;
            default:
                // this never happens because 4 bit can give only values by 0 to 15
                break;
        }
    }

    return result;
}

//
//Function: addUlongToString
//
//Description:
//      Adds pfUlong end of given string.
//
void pduCoderBase :: addUlongToString(string &name_, pfUlong value_)
{
    string valueStr = ulongToString(value_);

    name_ += valueStr;

    return;
}

//
//Function: ulongToString
//
//Description:
//      Makes Ulong to string.
//
string pduCoderBase :: ulongToString(pfUlong value_)
{
    string result("");

    if (value_ == 0)
    {
        result = (char)(48) + result;
    }
    else
    {
        while (value_ > 0)
        {
            result = (char)((value_ % 10) + 48) + result;
            value_ /= 10;
        }
    }

    return result;
}

//----------------------------------------------------------------------    

//
//Function: removeData
//
//Description:
//      Removes data from databuffer. This is because pfBitString::
//      getFirstBits throws exception if given parameter is greater than 32.
//      

void pduCoderBase :: removeData(pfUlong bits_)
{
    while (bits_ > 32)
    {
        _dataBuffer.getFirstBits(32);
        bits_ -= 32;
    }
    if (bits_ != 0)
    {
        _dataBuffer.getFirstBits(bits_);
    }    

    return;
}

//----------------------------------------------------------------------    

//
//Function: storeRemainingData
//
//Description:
//      Store remaining data from dataBuffer to _message name UNKNOWN_FIELDStr.
//      Method checks whether to store  data as an octetString or a bitString.
//

void pduCoderBase :: storeUnknownField(pfUlong length_)
{
    if (length_ != 0)
    {
        printWarningMsg("Storing unknown field");

        pfUlong modulo = length_ % OCTETSIZE;

        if (modulo != 0)
        {
            decodeBitString(UNKNOWN_FIELDStr, length_);
        }
        else
        {
            decodeOctetString(UNKNOWN_FIELDStr, (length_ / OCTETSIZE));
        }
    }

    return;
}

//----------------------------------------------------------------------    

//
//Function: printWarningMsg
//
//Description:
//      Method to print coding warning
//

void pduCoderBase :: printWarningMsg(const string &warningMsg_)
{
    debugString("ETS_WARNING", warningMsg_);

    return;
}

//----------------------------------------------------------------------    

//
//Function: printErrorMsg
//
//Description:
//      method to print coring error
//

void pduCoderBase :: printErrorMsg(const string &errorMsg_)
{
    debugString("ETS_ERROR", errorMsg_);

    return;
}

//----------------------------------------------------------------------    
