//
// tardy - a tar post-processor
// Copyright (C) 2011 Peter Miller
//
// This program 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.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
//

#include <libtardy/ac/string.h>

#include <libtardy/tar/input/cpio/crc.h>
#include <libtardy/tar/output/cpio/crc.h>


tar_input_cpio_crc::~tar_input_cpio_crc()
{
}


tar_input_cpio_crc::tar_input_cpio_crc(const file_input::pointer &a_ifp) :
    tar_input_cpio(a_ifp, 4),
    checksum_expected(0),
    running_checksum(0)
{
}


tar_input_cpio_crc::pointer
tar_input_cpio_crc::create(const file_input::pointer &a_ifp)
{
    return pointer(new tar_input_cpio_crc(a_ifp));
}


tar_output::pointer
tar_input_cpio_crc::tar_output_factory(const file_output::pointer &ofp)
    const
{
    return tar_output_cpio_crc::create(ofp);
}


bool
tar_input_cpio_crc::candidate(const file_input::pointer &fp)
{
    char buffer[6];
    int nbytes = fp->peek(buffer, sizeof(buffer));
    return
        (
            nbytes == sizeof(buffer)
        &&
            rcstring(buffer, sizeof(buffer)) == "070702"
        );
}


bool
tar_input_cpio_crc::read_header(tar_header &h)
{
    assert(sizeof(cpio_header) == 110);
    char buffer[sizeof(cpio_header)];
    size_t nbytes = read_deeper(buffer, 110);
    if (nbytes == 0)
        return false;
    if (nbytes != 110)
        fatal("short header read");
    cpio_header *hp = (cpio_header *)buffer;

    if (hp->get_magic() != "070702")
        fatal("bad header magic");
    h.device_major = hp->devmajor_get();
    h.device_minor = hp->devminor_get();
    h.group_id = hp->gid_get();
    h.inode_number = hp->ino_get();
    h.link_count = hp->nlink_get();
    int pmode = hp->mode_get();
    h.mode = pmode & 07777;
    h.mtime = hp->mtime_get();
    h.rdevice_major = hp->rdevmajor_get();
    h.rdevice_minor = hp->rdevminor_get();
    h.size = hp->filesize_get();
    h.type = type_from_mode(pmode);
    h.user_id = hp->uid_get();
    checksum_expected = hp->check_get();
    running_checksum = 0;
    unsigned name_size = hp->namesize_get();
    h.name = read_name(name_size);
    return !is_end(h);
}


rcstring
tar_input_cpio_crc::cpio_header::get_magic(void)
    const
{
    return rcstring(magic, sizeof(magic));
}


unsigned long
tar_input_cpio_crc::cpio_header::devmajor_get(void)
    const
{
    return hex(devmajor, sizeof(devmajor));
}


unsigned long
tar_input_cpio_crc::cpio_header::devminor_get(void)
    const
{
    return hex(devminor, sizeof(devminor));
}


unsigned long
tar_input_cpio_crc::cpio_header::gid_get(void)
    const
{
    return hex(gid, sizeof(gid));
}


unsigned long
tar_input_cpio_crc::cpio_header::ino_get(void)
    const
{
    return hex(ino, sizeof(ino));
}


unsigned long
tar_input_cpio_crc::cpio_header::nlink_get(void)
    const
{
    return hex(nlink, sizeof(nlink));
}


unsigned long
tar_input_cpio_crc::cpio_header::mode_get(void)
    const
{
    return hex(mode, sizeof(mode));
}


unsigned long
tar_input_cpio_crc::cpio_header::mtime_get(void)
    const
{
    return hex(mtime, sizeof(mtime));
}


unsigned long
tar_input_cpio_crc::cpio_header::rdevmajor_get(void)
    const
{
    return hex(rdevmajor, sizeof(rdevmajor));
}


unsigned long
tar_input_cpio_crc::cpio_header::rdevminor_get(void)
    const
{
    return hex(rdevminor, sizeof(rdevminor));
}


unsigned long
tar_input_cpio_crc::cpio_header::filesize_get(void)
    const
{
    return hex(filesize, sizeof(filesize));
}


unsigned long
tar_input_cpio_crc::cpio_header::uid_get(void)
    const
{
    return hex(uid, sizeof(uid));
}


unsigned long
tar_input_cpio_crc::cpio_header::namesize_get(void)
    const
{
    return hex(namesize, sizeof(namesize));
}


unsigned long
tar_input_cpio_crc::cpio_header::check_get(void)
    const
{
    return hex(check, sizeof(check));
}


unsigned long
tar_input_cpio_crc::cpio_header::hex(const char *data, size_t data_size)
{
    long value = 0;
    while (data_size > 0 && *data == ' ')
    {
        ++data;
        --data_size;
    }
    if (data_size <= 0)
        return -1;
    while (data_size > 0)
    {
        unsigned char c = *data++;
        --data_size;

        switch (c)
        {
        case '\0':
        case ' ':
            goto done;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            c -= '0';
            break;

        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
            c -= 'a' - 10;
            break;

        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
            c -= 'A' - 10;
            break;

        default:
            return -1;
        }

        //
        // Limit the range to 0..2^31-1.
        // Must test for overflow *before* the shift.
        //
        if (value & 0xF0000000)
        {
            return -1;
        }

        value = (value << 4) + c;
    }
    done:
    return value;
}


const char *
tar_input_cpio_crc::get_format_name(void)
    const
{
    return "cpio-crc";
}


int
tar_input_cpio_crc::read_data(void *data, int data_size)
{
    size_t nbytes = read_deeper(data, data_size);
    const unsigned char *p = (const unsigned char *)data;
    for (size_t j = 0; j < nbytes; ++j)
        running_checksum += *p++;
    return nbytes;
}


void
tar_input_cpio_crc::read_data_padding(void)
{
    if ((running_checksum & 0xFFFFFFFF) != (checksum_expected & 0xFFFFFFFF))
    {
        fatal
        (
            "checksum mismatch (header %08lX, data %08lX)",
            checksum_expected,
            running_checksum
        );
    }
    checksum_expected = 0;
    running_checksum = 0;
    tar_input_cpio::read_data_padding();
}
