# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024 Eduardo Aguiar
#
# 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 2, 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/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import datetime
import struct
import traceback

import mobius


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Entry
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Entry(object):

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief initialize object
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __init__(self):
        self.path = None
        self.name = None
        self.size = None
        self.seeds = None
        self.filetype = None
        self.hash_sha1 = None
        self.evaluated_hash_sha1 = None
        self.title = None
        self.url = None
        self.comment = None
        self.flag_verified = False


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief get data as UNIX datetime
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_unix_datetime(timestamp):
    if timestamp > 0:
        delta = datetime.timedelta(seconds=timestamp)
        date = datetime.datetime(1970, 1, 1, 0, 0, 0) + delta
    else:
        date = None

    return date


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief decrypt data
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def decrypt(data, seed):
    out = b''
    for c in data:
        out += bytes([c ^ (seed >> 8)])
        seed = ((c + seed) * 23219 + 36126) & 0xffff
    return out


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief get file type according to amime field
# @see const_ares.pas and helper_mimetypes.pas
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_file_type(mime):
    return {
        1: 'Audio',
        2: 'Audio',
        3: 'Software',
        4: 'Audio',
        5: 'Video',
        6: 'Document',
        7: 'Image'}.get(mime, 'Other')


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Decode file
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def decode(f):
    try:
        return decode_file(f)
    except Exception as e:
        mobius.core.logf('WRN %s %s' % (str(e), traceback.format_exc()))


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Decode data from TorrentH.dat
# @see tthread_dht.getMagnetFiles - DHT/thread_dht.pas (line 284)
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def decode_file(f):
    # try to open file
    reader = f.new_reader()
    if not reader:
        return

    # decode header
    decoder = mobius.decoder.data_decoder(reader)
    signature = decoder.get_string_by_size(14)

    # @see DHT/thread_dht.pas (line 319)
    if signature != '__ARESDB1.02H_':
        return

    # get TorrentH.dat metadata
    torrenth_path = f.path.replace('/', '\\')
    torrenth_creation_time = f.creation_time
    torrenth_last_modification_time = f.modification_time

    # decode entries
    pos = decoder.tell()
    size = decoder.get_size()
    entries = []

    while pos < size:

        # decrypt entry header
        # @see DHT/thread_dht.pas (line 335)
        enc_data = decoder.get_bytearray_by_size(39)
        pos += 39
        data = decrypt(enc_data, 12971)

        # decode entry header
        # @see DHT/thread_dht.pas (line 338)
        entry = Entry()
        entry.torrenth_path = torrenth_path
        entry.torrenth_creation_time = torrenth_creation_time
        entry.torrenth_last_modification_time = torrenth_last_modification_time

        timestamp, \
            mime, \
            entry.size, \
            entry.seeds, \
            hash_sha1, \
            details_size = struct.unpack('<IBQI20sH', data)

        # DHT Magnet file added date/time
        # @see BitTorrent/thread_bitTorrent.pas
        entry.timestamp = get_unix_datetime(timestamp)

        entry.filetype = get_file_type(mime)
        entry.hash_sha1 = mobius.encoder.hexstring(hash_sha1)

        # @see https://en.wikipedia.org/wiki/Magnet_URI_scheme
        entry.url = 'magnet:?xt=urn:btih:' + entry.hash_sha1

        # entry metadata
        if details_size > 0:

            # decrypt entry metadata
            # @see DHT/thread_dht.pas (line 355)
            enc_data = decoder.get_bytearray_by_size(details_size)
            data = decrypt(enc_data, 13175)
            pos += details_size

            # decode entry metadata
            # @see DHT/thread_dht.pas (line 359)
            i = 0
            while i + 2 < len(data):
                type_detail, len_detail = struct.unpack('<BH', data[i:i + 3])
                value_detail = data[i + 3:i + 3 + len_detail]

                # @see DHT/thread_dht.pas (line 369)
                if type_detail == 1:
                    entry.title = value_detail.decode('utf-8')
                    entry.name = value_detail.decode('utf-8')

                # @see DHT/thread_dht.pas (line 370)
                elif type_detail == 2:
                    entry.comment = value_detail.replace(b'\0', b' ').decode('utf-8')

                # @see DHT/thread_dht.pas (line 371)
                elif type_detail == 3:
                    entry.evaluated_hash_sha1 = mobius.encoder.hexstring(value_detail)
                    entry.flag_verified = (entry.evaluated_hash_sha1 == entry.hash_sha1)

                else:
                    mobius.core.logf(f'DEV Unknown detail type ({type_detail:d})')

                i = i + 3 + len_detail

        # add entry
        entries.append(entry)

    return entries
