/* fileheader.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020, 2021 Askar
    Almatov

    This file is part of Trafficmeter.

    Trafficmeter is free software: you can redistribute it and/or modify it under the terms of the GNU General
    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License along with Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cassert>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>

#include "endian64.h"
#include "fileheader.h"

using namespace trafficmeter;

static const int    VERSIONS_[] =
{
    0,
    4,  // FLOW
    4,  // STAT
    4   // FLOW2
};

/**************************************************************************************************************/
FileHeader::FileHeader() :
    beHeader_( nullptr )
{
}

/**************************************************************************************************************/
FileHeader::FileHeader( Type type, int nIntervals, time_t group )
{
    size_ = sizeof( BeHeader_ ) + nIntervals * sizeof( long );
    beHeader_ = reinterpret_cast<BeHeader_*>( new uint8_t[size_] );
    memset( beHeader_, 0, size_ );
    beHeader_->magic_ = htonl( 0x544d6668 );  // TMfh
    beHeader_->version_ = htonl( VERSIONS_[static_cast<int>(type)] );
    beHeader_->type_ = htonl( static_cast<int>(type) );
    beHeader_->nIntervals_ = htons( nIntervals );
    beHeader_->groupFactor_ = htons( GROUP_FACTOR );
    beHeader_->fileSize_ = htobe64( size_ );
    beHeader_->oldestGroup_ = beHeader_->newestGroup_ = htonl( group );
}

/**************************************************************************************************************/
FileHeader::~FileHeader()
{
    if ( beHeader_ != nullptr )
    {
        delete[] beHeader_;
    }
}

/**************************************************************************************************************/
void
FileHeader::update( time_t group, long offset, long nBytes )
{
    long*   offsets = reinterpret_cast<long*>( beHeader_ + 1 );
    int     groupNo = ( group - oldestGroup() ) / GROUP_FACTOR;

    offsets[ groupNo ] = offset;
    beHeader_->fileSize_ = htobe64( be64toh(beHeader_->fileSize_) + nBytes );
    beHeader_->newestGroup_ = htonl( group );
}

/**************************************************************************************************************/
bool
FileHeader::isCompatible( Type type ) const
{
    return
    (
        beHeader_ &&
        beHeader_->magic_ == htonl( 0x544d6668 ) &&    // TMfh
        ntohl( beHeader_->type_ ) == static_cast<int>( type ) &&
        ntohl( beHeader_->version_ ) == VERSIONS_[ static_cast<int>(type) ]
    );
}

/**************************************************************************************************************/
bool
FileHeader::isEmpty() const
{
    return fileSize() == size_;
}

/**************************************************************************************************************/
bool
FileHeader::hasGroup( time_t group ) const
{
    time_t  beginGroup = oldestGroup();
    time_t  endGroup = beginGroup + ntohs( beHeader_->nIntervals_ ) * GROUP_FACTOR;

    return group >= beginGroup && group < endGroup;
}

/**************************************************************************************************************/
time_t
FileHeader::oldestGroup() const
{
    return ntohl( beHeader_->oldestGroup_ );
}

/**************************************************************************************************************/
time_t
FileHeader::newestGroup() const
{
    return ntohl( beHeader_->newestGroup_ );
}

/**************************************************************************************************************/
long
FileHeader::groupOffset( time_t group ) const
{
    if ( group <= newestGroup() )
    {
        int     groupNo = std::max( (group - oldestGroup()) / ntohs(beHeader_->groupFactor_), 0L );
        int     nGroups = ntohs( beHeader_->nIntervals_ );
        long*   offsets = reinterpret_cast<long*>( beHeader_ + 1 );

        for ( int i = groupNo; i < nGroups; ++i )
        {
            if ( offsets[i] != 0 )
            {
                return offsets[ i ];
            }
        }
    }

    return fileSize();
}

/**************************************************************************************************************/
long
FileHeader::fileSize() const
{
    return be64toh( beHeader_->fileSize_ );
}

/**************************************************************************************************************/
bool
FileHeader::read( int fd )
{
    struct flock  lockRegion;
    BeHeader_     header;

    lockRegion.l_whence = SEEK_SET;
    lockRegion.l_start = 0;
    lockRegion.l_len = sizeof( header );
    lockRegion.l_type = F_RDLCK;

    if
    (
        lseek( fd, 0, SEEK_SET ) == -1 ||
        fcntl( fd, F_SETLKW, &lockRegion ) == -1 ||
        ::read( fd, &header, sizeof(header) ) == -1
    )
    {
        return false;
    }

    lockRegion.l_type = F_UNLCK;
    assert( fcntl(fd, F_SETLK, &lockRegion) != -1 );

    int  nIntervals = ntohs( header.nIntervals_ );

    lockRegion.l_start = lockRegion.l_len;
    lockRegion.l_len = nIntervals * sizeof( long );
    lockRegion.l_type = F_RDLCK;
    size_ = lockRegion.l_start + lockRegion.l_len;
    beHeader_ = reinterpret_cast<BeHeader_*>( new uint8_t[size_] );
    memcpy( beHeader_, &header, sizeof(header) );

    if
    (
        lseek( fd, lockRegion.l_start, SEEK_SET ) == -1 ||
        fcntl( fd, F_SETLKW, &lockRegion ) == -1 ||
        ::read( fd, beHeader_+1, lockRegion.l_len ) == -1
    )
    {
        return false;
    }

    lockRegion.l_type = F_UNLCK;
    assert( fcntl(fd, F_SETLK, &lockRegion) != -1 );

    return true;
}
/**************************************************************************************************************/
bool
FileHeader::write( int fd ) const
{
    struct flock  lockRegion;

    lockRegion.l_whence = SEEK_SET;
    lockRegion.l_start = 0;
    lockRegion.l_len = size_;
    lockRegion.l_type = F_WRLCK;

    if
    (
        lseek( fd, 0, SEEK_SET ) == -1 ||
        fcntl( fd, F_SETLKW, &lockRegion ) == -1 ||
        ::write( fd, beHeader_, size_ ) == -1
    )
    {
        return false;
    }

    lockRegion.l_type = F_UNLCK;
    assert( fcntl(fd, F_SETLK, &lockRegion) != -1 );

    return true;
}
