/*
 * Copyright (c) 2017, Matias Fontanini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following disclaimer
 *   in the documentation and/or other materials provided with the
 *   distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <limits>
#include <tins/rawpdu.h>
#include <tins/tcp_stream.h>

using std::numeric_limits;
using std::make_pair;

namespace Tins {

// As defined by RFC 1982 - 2 ^ (SERIAL_BITS - 1)
static const uint32_t seq_number_diff = 2147483648U;

// Adds sequence numbers
uint32_t add_sequence_numbers(uint32_t seq1, uint32_t seq2) {
    return seq1 + seq2;
}

// Subtract sequence numbers
uint32_t subtract_sequence_numbers(uint32_t seq1, uint32_t seq2) {
    return seq1 - seq2;
}

// Compares sequence numbers as defined by RFC 1982.
int compare_seq_numbers(uint32_t seq1, uint32_t seq2) {
    if (seq1 == seq2) {
        return 0;
    }
    if (seq1 < seq2) {
        return (seq2 - seq1 < seq_number_diff) ? -1 : 1;
    }
    else {
        return (seq1 - seq2 > seq_number_diff) ? -1 : 1;
    }
}

template<typename Iterator, typename Container>
Iterator erase_iterator(Iterator it, Container& cont) {
    Iterator output = it;
    ++output;
    cont.erase(it);
    if (output == cont.end()) {
        output = cont.begin();
    }
    return output;
}


// TCPStreamFollower

TCPStreamFollower::TCPStreamFollower()
: last_identifier_(0) {
    
}



TCPStream::StreamInfo::StreamInfo(IPv4Address client, 
                                  IPv4Address server,
                                  uint16_t cport,
                                  uint16_t sport) 
: client_addr(client), server_addr(server), client_port(cport), 
  server_port(sport) {
}




TCPStream::TCPStream(IP* ip, TCP* tcp, uint64_t identifier) 
: client_seq_(tcp->seq()), server_seq_(0), info_(ip->src_addr(), 
  ip->dst_addr(), tcp->sport(), tcp->dport()), identifier_(identifier), 
  syn_ack_sent_(false), fin_sent_(false) {
}

TCPStream::TCPStream(const TCPStream& rhs) {
    *this = rhs;
}

TCPStream& TCPStream::operator=(const TCPStream& rhs) {
    client_seq_ = rhs.client_seq_;
    server_seq_ = rhs.server_seq_;
    info_ = rhs.info_;
    identifier_ = rhs.identifier_;
    syn_ack_sent_ = rhs.syn_ack_sent_;
    fin_sent_ = rhs.fin_sent_;
    client_payload_ = rhs.client_payload_;
    server_payload_ = rhs.server_payload_;
    client_frags_ = clone_fragments(rhs.client_frags_);
    server_frags_ = clone_fragments(rhs.server_frags_);
    return* this;
}

TCPStream::~TCPStream() {
    free_fragments(client_frags_);
    free_fragments(server_frags_);
}

void TCPStream::free_fragments(fragments_type& frags) {
    for (fragments_type::iterator it = frags.begin(); it != frags.end(); ++it) {
        delete it->second;
    }
}

TCPStream::fragments_type TCPStream::clone_fragments(const fragments_type& frags) {
    fragments_type new_frags;
    for (fragments_type::const_iterator it = frags.begin(); it != frags.end(); ++it) {
        new_frags.insert(make_pair(it->first, it->second->clone()));
    }
    return new_frags;
}

void TCPStream::safe_insert(fragments_type& frags, uint32_t seq, RawPDU* raw) {
    RawPDU*& stored_raw = frags[seq];
    // New segment, insert it
    if (stored_raw == 0) {
        stored_raw = raw;
    }
    else {
        // There was a segment in this position. Keep the largest one.
        if (stored_raw->payload_size() > raw->payload_size()) {
            delete raw;
        }
        else {
            delete stored_raw;
            stored_raw = raw;
        }
    }
}

bool TCPStream::generic_process(uint32_t& my_seq,
                                uint32_t& /*other_seq*/,
                                payload_type& pload,
                                fragments_type& frags,
                                TCP* tcp) {
    bool added_some(false);
    if (tcp->get_flag(TCP::FIN) || tcp->get_flag(TCP::RST)) {
        fin_sent_ = true;
    }
    RawPDU* raw = static_cast<RawPDU*>(tcp->release_inner_pdu()); 
    if (raw) {
        const uint32_t chunk_end = add_sequence_numbers(tcp->seq(), raw->payload_size());
        // If the end of the chunk ends after our current sequence number, process it.
        if (compare_seq_numbers(chunk_end, my_seq) >= 0) {
            uint32_t seq = tcp->seq();
            // If it starts before our sequence number, slice it
            if (compare_seq_numbers(seq, my_seq) < 0) {
                const uint32_t diff = subtract_sequence_numbers(my_seq, seq);
                raw->payload().erase(
                    raw->payload().begin(),
                    raw->payload().begin() + diff
                );
                seq = my_seq;
            }
            safe_insert(frags, seq, raw);
            fragments_type::iterator it = frags.find(my_seq);
            // Keep looping while the fragments seq is lower or equal to our seq
            while (it != frags.end() && compare_seq_numbers(it->first, my_seq) <= 0) {
                // Does this fragment start before our sequence number?
                if (compare_seq_numbers(it->first, my_seq) < 0) {
                    uint32_t fragment_end = add_sequence_numbers(it->first, it->second->payload_size());
                    int comparison = compare_seq_numbers(fragment_end, my_seq);
                    // Does it end after our sequence number? 
                    if (comparison > 0) {
                        // Then slice it
                        RawPDU::payload_type& payload = it->second->payload();
                        payload.erase(
                            payload.begin(),
                            payload.begin() + subtract_sequence_numbers(my_seq, it->first)
                        );
                        safe_insert(frags, my_seq, it->second);
                        it = erase_iterator(it, frags);
                    }
                    else {
                        // Otherwise, we've seen this part of the payload. Erase it.
                        delete it->second;
                        it = erase_iterator(it, frags);
                    }
                }
                else {
                    // They're equal. Add this payload.
                    pload.insert(
                        pload.end(),
                        it->second->payload().begin(), 
                        it->second->payload().end()
                    );
                    my_seq += it->second->payload_size();
                    delete it->second;
                    it = erase_iterator(it, frags);
                    added_some = true;
                    if (frags.empty()) {
                        break;
                    }
                }
            }
        }
        else {
            delete raw;
        }
    }
    return added_some;
}

bool TCPStream::update(IP* ip, TCP* tcp) {
    if (!syn_ack_sent_) {
        if (tcp->has_flags(TCP::SYN | TCP::ACK)) {
            server_seq_ = tcp->seq() + 1;
            client_seq_ = tcp->ack_seq();
            syn_ack_sent_ = true;
        }
        return false;
    }
    else {
        if (ip->src_addr() == info_.client_addr && tcp->sport() == info_.client_port) {
            return generic_process(client_seq_, server_seq_, client_payload_, client_frags_, tcp);
        }
        else {
            return generic_process(server_seq_, client_seq_, server_payload_, server_frags_, tcp);
        }
    }
}

bool TCPStream::StreamInfo::operator<(const StreamInfo& rhs) const {
    if (client_addr == rhs.client_addr) {
        if (server_addr == rhs.server_addr) {
            if (client_port == rhs.client_port) {
                return server_port < rhs.server_port;
            }
            else {
                return client_port < rhs.client_port;
            }
        }
        else {
            return server_addr < rhs.server_addr;
        }
    }
    else {
        return client_addr < rhs.client_addr;
    }
}

} // Tins
