/* torrent.c - create BitTorrent files and calculate BitTorrent  InfoHash (BTIH).
 *
 * Copyright (c) 2010, Aleksey Kravchenko <rhash.admin@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE  INCLUDING ALL IMPLIED WARRANTIES OF  MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT,  OR CONSEQUENTIAL DAMAGES  OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE,  DATA OR PROFITS,  WHETHER IN AN ACTION OF CONTRACT,  NEGLIGENCE
 * OR OTHER TORTIOUS ACTION,  ARISING OUT OF  OR IN CONNECTION  WITH THE USE  OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include "torrent.h"
#include "hex.h"
#include "util.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>  /* time() */

#ifdef USE_OPENSSL
#define SHA1_INIT(ctx) ((pinit_t)ctx->sha1_methods.init)(&ctx->sha1_context)
#define SHA1_UPDATE(ctx, msg, size) ((pupdate_t)ctx->sha1_methods.update)(&ctx->sha1_context, (msg), (size))
#define SHA1_FINAL(ctx, result) ((pfinal_t)ctx->sha1_methods.final)(&ctx->sha1_context, (result))
#else
#define SHA1_INIT(ctx) rhash_sha1_init(&ctx->sha1_context)
#define SHA1_UPDATE(ctx, msg, size) rhash_sha1_update(&ctx->sha1_context, (msg), (size))
#define SHA1_FINAL(ctx, result) rhash_sha1_final(&ctx->sha1_context, (result))
#endif

#define BT_MIN_PIECE_LENGTH 16384
/** size of a SHA1 hash in bytes */
#define BT_HASH_SIZE 20
/** number of SHA1 hashes to store together in one block */
#define BT_BLOCK_SIZE 256
#define BT_BLOCK_SIZE_IN_BYTES (BT_BLOCK_SIZE * BT_HASH_SIZE)

/**
 * Initialize torrent context before calculating hash.
 *
 * @param ctx context to initialize
 */
void bt_init(torrent_ctx* ctx)
{
	memset(ctx, 0, sizeof(torrent_ctx));
	ctx->piece_length = BT_MIN_PIECE_LENGTH;
	assert(BT_MIN_PIECE_LENGTH == bt_default_piece_length(0, 0));

#ifdef USE_OPENSSL
	/* get the methods of the selected SHA1 algorithm */
	assert(rhash_info_table[3].info->hash_id == EXTENDED_SHA1);
	assert(rhash_info_table[3].context_size <= (sizeof(sha1_ctx) + sizeof(unsigned long)));
	rhash_load_sha1_methods(&ctx->sha1_methods, METHODS_SELECTED);
#endif

	SHA1_INIT(ctx);
}

/**
 * Free memory allocated by properties of torrent_vect structure.
 *
 * @param vect vector to clean
 */
static void bt_vector_clean(torrent_vect* vect)
{
	size_t i;
	for (i = 0; i < vect->size; i++) {
		free(vect->array[i]);
	}
	free(vect->array);
}

/**
 * Clean up torrent context by freeing all dynamically
 * allocated memory.
 *
 * @param ctx torrent algorithm context
 */
void bt_cleanup(torrent_ctx* ctx)
{
	assert(ctx != NULL);

	/* destroy arrays */
	bt_vector_clean(&ctx->hash_blocks);
	bt_vector_clean(&ctx->files);
	bt_vector_clean(&ctx->announce);

	free(ctx->program_name);
	free(ctx->content.str);
	ctx->program_name = 0;
	ctx->content.str = 0;
}

static void bt_generate_torrent(torrent_ctx* ctx);

/**
 * Add an item to vector.
 *
 * @param vect vector to add item to
 * @param item the item to add
 * @return non-zero on success, zero on fail
 */
static int bt_vector_add_ptr(torrent_vect* vect, void* item)
{
	/* check if vector contains enough space for the next item */
	if (vect->size >= vect->allocated) {
		size_t size = (vect->allocated == 0 ? 128 : vect->allocated * 2);
		void* new_array = realloc(vect->array, size * sizeof(void*));
		if (new_array == NULL) return 0; /* failed: no memory */
		vect->array = (void**)new_array;
		vect->allocated = size;
	}
	/* add new item to the vector */
	vect->array[vect->size] = item;
	vect->size++;
	return 1;
}

/**
 * Store a SHA1 hash of a processed file piece.
 *
 * @param ctx torrent algorithm context
 * @return non-zero on success, zero on fail
 */
static int bt_store_piece_sha1(torrent_ctx* ctx)
{
	unsigned char* block;
	unsigned char* hash;

	if ((ctx->piece_count % BT_BLOCK_SIZE) == 0) {
		block = (unsigned char*)malloc(BT_BLOCK_SIZE_IN_BYTES);
		if (!block)
			return 0;
		if (!bt_vector_add_ptr(&ctx->hash_blocks, block)) {
			free(block);
			return 0;
		}
	} else {
		block = (unsigned char*)(ctx->hash_blocks.array[ctx->piece_count / BT_BLOCK_SIZE]);
	}

	hash = &block[BT_HASH_SIZE * (ctx->piece_count % BT_BLOCK_SIZE)];
	SHA1_FINAL(ctx, hash); /* write the hash */
	ctx->piece_count++;
	return 1;
}

/**
 * A filepath and filesize information.
 */
typedef struct bt_file_info
{
	uint64_t size;
	char path[];
} bt_file_info;

/**
 * Add a file info into the batch of files of given torrent.
 *
 * @param ctx torrent algorithm context
 * @param path file path
 * @param filesize file size
 * @return non-zero on success, zero on fail
 */
int bt_add_file(torrent_ctx* ctx, const char* path, uint64_t filesize)
{
	size_t len = strlen(path);
	bt_file_info* info = (bt_file_info*)malloc(sizeof(uint64_t) + len + 1);
	if (info == NULL) {
		ctx->error = 1;
		return 0;
	}

	info->size = filesize;
	memcpy(info->path, path, len + 1);
	if (!bt_vector_add_ptr(&ctx->files, info)) {
		free(info);
		return 0;
	}

	/* recalculate piece length (but only if hashing not started yet) */
	if (ctx->piece_count == 0 && ctx->index == 0) {
		/* note: in case of batch of files should use a total batch size */
		ctx->piece_length = bt_default_piece_length(filesize, ctx->options & BT_OPT_TRANSMISSION);
	}
	return 1;
}

/**
 * Calculate message hash.
 * Can be called repeatedly with chunks of the message to be hashed.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param msg message chunk
 * @param size length of the message chunk
 */
void bt_update(torrent_ctx* ctx, const void* msg, size_t size)
{
	const unsigned char* pmsg = (const unsigned char*)msg;
	size_t rest = (size_t)(ctx->piece_length - ctx->index);
	assert(ctx->index < ctx->piece_length);

	while (size > 0) {
		size_t left = (size < rest ? size : rest);
		SHA1_UPDATE(ctx, pmsg, left);
		if (size < rest) {
			ctx->index += left;
			break;
		}
		bt_store_piece_sha1(ctx);
		SHA1_INIT(ctx);
		ctx->index = 0;

		pmsg += rest;
		size -= rest;
		rest = ctx->piece_length;
	}
}

/**
 * Finalize hashing and optionally store calculated hash into the given array.
 * If the result parameter is NULL, the hash is not stored, but it is
 * accessible by bt_get_btih().
 *
 * @param ctx the algorithm context containing current hashing state
 * @param result pointer to the array store message hash into
 */
void bt_final(torrent_ctx* ctx, unsigned char result[20])
{
	if (ctx->index > 0) {
		bt_store_piece_sha1(ctx); /* flush buffered data */
	}

	bt_generate_torrent(ctx);
	if (result) memcpy(result, ctx->btih, btih_hash_size);
}

/* BitTorrent functions */

/**
 * Grow, if needed, the torrent_str buffer to ensure it contains
 * at least (length + 1) characters.
 *
 * @param ctx the torrent algorithm context
 * @param length length of the string, the allocated buffer must contain
 * @return 1 on success, 0 on error
 */
static int bt_str_ensure_length(torrent_ctx* ctx, size_t length)
{
	char* new_str;
	if (ctx->error)
		return 0;
	if (length >= ctx->content.allocated) {
		length++; /* allocate one character more */
		if (length < 64) length = 64;
		else length = (length + 255) & ~255;
		new_str = (char*)realloc(ctx->content.str, length);
		if (new_str == NULL) {
			ctx->error = 1;
			ctx->content.allocated = 0;
			return 0;
		}
		ctx->content.str = new_str;
		ctx->content.allocated = length;
	}
	return 1;
}

/**
 * Append a null-terminated string to the string string buffer.
 *
 * @param ctx the torrent algorithm context
 * @param text the null-terminated string to append
 */
static void bt_str_append(torrent_ctx* ctx, const char* text)
{
	size_t length = strlen(text);
	if (!bt_str_ensure_length(ctx, ctx->content.length + length + 1))
		return;
	assert(ctx->content.str != 0);
	memcpy(ctx->content.str + ctx->content.length, text, length + 1);
	ctx->content.length += length;
}

/**
 * B-encode given integer.
 *
 * @param ctx the torrent algorithm context
 * @param name B-encoded string to prepend the number or NULL
 * @param number the integer to output
 */
static void bt_bencode_int(torrent_ctx* ctx, const char* name, uint64_t number)
{
	char* p;
	if (name)
		bt_str_append(ctx, name);

	/* add up to 20 digits and 2 letters */
	if (!bt_str_ensure_length(ctx, ctx->content.length + 22))
		return;
	p = ctx->content.str + ctx->content.length;
	*(p++) = 'i';
	p += rhash_sprintI64(p, number);
	*(p++) = 'e';
	*p = '\0'; /* terminate string with \0 */

	ctx->content.length = (p - ctx->content.str);
}

/**
 * B-encode a string.
 *
 * @param ctx the torrent algorithm context
 * @param name B-encoded string to prepend or NULL
 * @param str the string to encode
 */
static void bt_bencode_str(torrent_ctx* ctx, const char* name, const char* str)
{
	const size_t string_length = strlen(str);
	int number_length;
	char* p;

	if (name)
		bt_str_append(ctx, name);
	if (!bt_str_ensure_length(ctx, ctx->content.length + string_length + 21))
		return;
	p = ctx->content.str + ctx->content.length;
	p += (number_length = rhash_sprintI64(p, string_length));
	ctx->content.length += string_length + number_length + 1;

	*(p++) = ':';
	memcpy(p, str, string_length + 1); /* copy with trailing '\0' */
}

/**
 * B-encode array of SHA1 hashes of file pieces.
 *
 * @param ctx pointer to the torrent structure containing SHA1 hashes
 */
static void bt_bencode_pieces(torrent_ctx* ctx)
{
	const size_t pieces_length = ctx->piece_count * BT_HASH_SIZE;
	size_t bytes_left, i;
	int number_length;
	char* p;

	if (!bt_str_ensure_length(ctx, ctx->content.length + pieces_length + 21))
		return;
	p = ctx->content.str + ctx->content.length;
	p += (number_length = rhash_sprintI64(p, pieces_length));
	ctx->content.length += pieces_length + number_length + 1;

	*(p++) = ':';
	p[pieces_length] = '\0'; /* terminate with \0 just in case */

	for (bytes_left = pieces_length, i = 0; bytes_left > 0; i++)
	{
		size_t size = (bytes_left < BT_BLOCK_SIZE_IN_BYTES ? bytes_left : BT_BLOCK_SIZE_IN_BYTES);
		memcpy(p, ctx->hash_blocks.array[i], size);
		bytes_left -= size;
		p += size;
	}
}

/**
 * Calculate default torrent piece length, using uTorrent algorithm.
 * Algorithm:
 *   piece_length = 16K for total_size < 16M,
 *   piece_length = 8M for total_size >= 4G,
 *   piece_length = top_bit(total_size) / 512 otherwise.
 *
 * @param total_size total torrent batch size
 * @return piece length used by torrent file
 */
static size_t utorr_piece_length(uint64_t total_size)
{
	size_t size = (size_t)(total_size >> 9) | 16384;
	size_t hi_bit;
	for (hi_bit = 8388608; hi_bit > size; hi_bit >>= 1);
	return hi_bit;
}

#define MB I64(1048576)

/**
 * Calculate default torrent piece length, using transmission algorithm.
 * Algorithm:
 *   piece_length = (size >= 2G ? 2M : size >= 1G ? 1M :
 *       size >= 512M ? 512K : size >= 350M ? 256K :
 *       size >= 150M ? 128K : size >= 50M ? 64K : 32K);
 *
 * @param total_size total torrent batch size
 * @return piece length used by torrent file
 */
static size_t transmission_piece_length(uint64_t total_size)
{
	static const uint64_t sizes[6] = { 50 * MB, 150 * MB, 350 * MB, 512 * MB, 1024 * MB, 2048 * MB };
	int i;
	for (i = 0; i < 6 && total_size >= sizes[i]; i++);
	return (32 * 1024) << i;
}

size_t bt_default_piece_length(uint64_t total_size, int transmission)
{
	return (transmission ?
		transmission_piece_length(total_size) : utorr_piece_length(total_size));
}

/* get file basename */
static const char* bt_get_basename(const char* path)
{
	const char* p = strchr(path, '\0') - 1;
	for (; p >= path && *p != '/' && *p != '\\'; p--);
	return (p + 1);
}

/* extract batchname from the path, modifies the path buffer */
static const char* get_batch_name(char* path)
{
	char* p = (char*)bt_get_basename(path) - 1;
	for (; p > path && (*p == '/' || *p == '\\'); p--) *p = 0;
	if (p <= path) return "BATCH_DIR";
	return bt_get_basename(path);
}

/* write file size and path */
static void bt_file_info_append(torrent_ctx* ctx, const char* length_name,
	const char* path_name, bt_file_info* info)
{
	bt_bencode_int(ctx, length_name, info->size);
	/* store the file basename */
	bt_bencode_str(ctx, path_name, bt_get_basename(info->path));
}

/**
 * Generate torrent file content
 * @see http://wiki.theory.org/BitTorrentSpecification
 *
 * @param ctx the torrent algorithm context
 */
static void bt_generate_torrent(torrent_ctx* ctx)
{
	uint64_t total_size = 0;
	size_t info_start_pos;

	assert(ctx->content.str == NULL);

	if (ctx->piece_length == 0) {
		if (ctx->files.size == 1) {
			total_size = ((bt_file_info*)ctx->files.array[0])->size;
		}
		ctx->piece_length = bt_default_piece_length(total_size, ctx->options & BT_OPT_TRANSMISSION);
	}

	if ((ctx->options & BT_OPT_INFOHASH_ONLY) == 0) {
		/* write the torrent header */
		bt_str_append(ctx, "d");
		if (ctx->announce.array && ctx->announce.size > 0) {
			bt_bencode_str(ctx, "8:announce", ctx->announce.array[0]);

			/* if more than one announce url */
			if (ctx->announce.size > 1) {
				/* add the announce-list key-value pair */
				size_t i;
				bt_str_append(ctx, "13:announce-listll");

				for (i = 0; i < ctx->announce.size; i++) {
					if (i > 0) {
						bt_str_append(ctx, "el");
					}
					bt_bencode_str(ctx, 0, ctx->announce.array[i]);
				}
				bt_str_append(ctx, "ee");
			}
		}

		if (ctx->program_name) {
			bt_bencode_str(ctx, "10:created by", ctx->program_name);
		}
		bt_bencode_int(ctx, "13:creation date", (uint64_t)time(NULL));

		bt_str_append(ctx, "8:encoding5:UTF-8");
	}

	/* write the essential for BTIH part of the torrent file */

	bt_str_append(ctx, "4:infod"); /* start the info dictionary */
	info_start_pos = ctx->content.length - 1;

	if (ctx->files.size > 1) {
		size_t i;

		/* process batch torrent */
		bt_str_append(ctx, "5:filesl"); /* start list of files */

		/* write length and path for each file in the batch */
		for (i = 0; i < ctx->files.size; i++) {
			bt_file_info_append(ctx, "d6:length", "4:pathl",
				(bt_file_info*)ctx->files.array[i]);
			bt_str_append(ctx, "ee");
		}
		/* note: get_batch_name modifies path, so should be called here */
		bt_bencode_str(ctx, "e4:name", get_batch_name(
			((bt_file_info*)ctx->files.array[0])->path));
	}
	else if (ctx->files.size > 0) {
		/* write size and basename of the first file */
		/* in the non-batch mode other files are ignored */
		bt_file_info_append(ctx, "6:length", "4:name",
			(bt_file_info*)ctx->files.array[0]);
	}

	bt_bencode_int(ctx, "12:piece length", ctx->piece_length);
	bt_str_append(ctx, "6:pieces");
	bt_bencode_pieces(ctx);

	if (ctx->options & BT_OPT_PRIVATE) {
		bt_str_append(ctx, "7:privatei1e");
	} else if (ctx->options & BT_OPT_TRANSMISSION) {
		bt_str_append(ctx, "7:privatei0e");
	}
	bt_str_append(ctx, "ee");

	/* calculate BTIH */
	SHA1_INIT(ctx);
	if (ctx->content.str) {
		SHA1_UPDATE(ctx, (unsigned char*)ctx->content.str + info_start_pos,
			ctx->content.length - info_start_pos - 1);
	}
	SHA1_FINAL(ctx, ctx->btih);
}

/* Getters/Setters */

/**
 * Get BTIH (BitTorrent Info Hash) value.
 *
 * @param ctx the torrent algorithm context
 * @return the 20-bytes long BTIH value
 */
unsigned char* bt_get_btih(torrent_ctx* ctx)
{
	return ctx->btih;
}

/**
 * Set the torrent algorithm options.
 *
 * @param ctx the torrent algorithm context
 * @param options the options to set
 */
void bt_set_options(torrent_ctx* ctx, unsigned options)
{
	ctx->options = options;
}

#if defined(__STRICT_ANSI__)
/* define strdup for gcc -ansi */
static char* bt_strdup(const char* str)
{
	size_t len = strlen(str);
	char* res = (char*)malloc(len + 1);
	if (res) memcpy(res, str, len + 1);
	return res;
}
#define strdup bt_strdup
#endif /* __STRICT_ANSI__ */

/**
 * Set optional name of the program generating the torrent
 * for storing into torrent file.
 *
 * @param ctx the torrent algorithm context
 * @param name the program name
 * @return non-zero on success, zero on error
 */
int bt_set_program_name(torrent_ctx* ctx, const char* name)
{
	ctx->program_name = strdup(name);
	return (ctx->program_name != NULL);
}

/**
 * Set length of a file piece.
 *
 * @param ctx the torrent algorithm context
 * @param piece_length the piece length in bytes
 */
void bt_set_piece_length(torrent_ctx* ctx, size_t piece_length)
{
	ctx->piece_length = piece_length;
}

/**
 * Set length of a file piece by the total batch size.
 *
 * @param ctx the torrent algorithm context
 * @param total_size total batch size
 */
void bt_set_total_batch_size(torrent_ctx* ctx, uint64_t total_size)
{
	ctx->piece_length = bt_default_piece_length(total_size, ctx->options & BT_OPT_TRANSMISSION);
}

/**
 * Add a tracker announce-URL to the torrent file.
 *
 * @param ctx the torrent algorithm context
 * @param announce_url the announce URL of the tracker
 * @return non-zero on success, zero on error
 */
int bt_add_announce(torrent_ctx* ctx, const char* announce_url)
{
	char* url_copy;
	if (!announce_url || announce_url[0] == '\0') return 0;
	url_copy = strdup(announce_url);
	if (!url_copy) return 0;
	if (bt_vector_add_ptr(&ctx->announce, url_copy))
		return 1;
	free(url_copy);
	return 0;
}

/**
 * Get the content of generated torrent file.
 *
 * @param ctx the torrent algorithm context
 * @param pstr pointer to pointer receiving the buffer with file content
 * @return length of the torrent file content
 */
size_t bt_get_text(torrent_ctx* ctx, char** pstr)
{
	assert(ctx->content.str);
	*pstr = ctx->content.str;
	return ctx->content.length;
}

#if !defined(NO_IMPORT_EXPORT)

# define EXPORT_ALIGNER 7
# define GET_EXPORT_ALIGNED(size) (((size) + EXPORT_ALIGNER) & ~EXPORT_ALIGNER)
# define GET_EXPORT_PADDING(size) (-(size) & EXPORT_ALIGNER)
# define GET_EXPORT_STR_LEN(length) GET_EXPORT_ALIGNED((length) + 1)
# define GET_EXPORT_SIZED_STR_LEN(length) GET_EXPORT_STR_LEN((length) + sizeof(size_t))
# define IS_EXPORT_ALIGNED(size) (((size) & EXPORT_ALIGNER) == 0)
# define BT_CTX_OSSL_FLAG 0x10

static void bt_export_str(char* out, const char* str, size_t length)
{
	assert(!!out);
	*(size_t*)(out) = length;
	out += sizeof(size_t);
	memcpy(out, str, length + 1);
}

typedef struct bt_export_header {
	size_t torrent_ctx_size;
	size_t files_size;
	size_t announce_size;
	size_t program_name_length;
	size_t content_length;
} bt_export_header;

/**
 * Export algorithm context to a memory region, or calculate the
 * size required for context export.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param out pointer to the memory region or NULL
 * @param size size of memory region
 * @return the size of the exported data on success, 0 on fail.
 */
size_t bt_export(const torrent_ctx* ctx, void* out, size_t size)
{
	const size_t head_size = sizeof(bt_export_header);
	const size_t ctx_head_size = offsetof(torrent_ctx, hash_blocks);
	const size_t hashes_size = ctx->piece_count * BT_HASH_SIZE;
	size_t exported_size = head_size + ctx_head_size + hashes_size;
	const size_t padding_size = GET_EXPORT_PADDING(exported_size);
	const size_t program_name_length = (ctx->program_name ? strlen(ctx->program_name) : 0);
	char* out_ptr = (char*)out;
	size_t i;
	assert((exported_size + padding_size) == GET_EXPORT_ALIGNED(exported_size));
	if (out_ptr) {
		bt_export_header* header = (bt_export_header*)out_ptr;
		size_t hash_data_left = hashes_size;
		if (size < exported_size)
			return 0;
		header->torrent_ctx_size = sizeof(torrent_ctx);
		header->files_size = ctx->files.size;
		header->announce_size = ctx->announce.size;
		header->program_name_length = program_name_length;
		header->content_length = ctx->content.length;
		out_ptr += head_size;

		memcpy(out_ptr, ctx, ctx_head_size);
		out_ptr += ctx_head_size;

		for (i = 0; i < ctx->hash_blocks.size && hash_data_left; i++) {
			size_t left = (hash_data_left < BT_BLOCK_SIZE_IN_BYTES ? hash_data_left : BT_BLOCK_SIZE_IN_BYTES);
			memcpy(out_ptr, ctx->hash_blocks.array[i], left);
			out_ptr += left;
			hash_data_left -= left;
		}
		out_ptr += padding_size;
	}
	exported_size += padding_size;
	assert(IS_EXPORT_ALIGNED(exported_size));

	for (i = 0; i < ctx->files.size; i++) {
		bt_file_info* info = (bt_file_info*)(ctx->files.array[i]);
		size_t length = strlen(info->path);
		const size_t aligned_length = GET_EXPORT_SIZED_STR_LEN(length);
		if (!length)
			continue;
		exported_size += sizeof(uint64_t) + aligned_length;
		if (out_ptr) {
			if (size < exported_size)
				return 0;
			*(uint64_t*)out_ptr = info->size;
			out_ptr += sizeof(uint64_t);
			bt_export_str(out_ptr, info->path, length);
			out_ptr += aligned_length;
		}
	}
	assert(IS_EXPORT_ALIGNED(exported_size));

	for (i = 0; i < ctx->announce.size; i++) {
		size_t length = strlen(ctx->announce.array[i]);
		const size_t aligned_length = GET_EXPORT_SIZED_STR_LEN(length);
		if (!length)
			continue;
		exported_size += aligned_length;
		if (out_ptr) {
			if (size < exported_size)
				return 0;
			bt_export_str(out_ptr, ctx->announce.array[i], length);
			out_ptr += aligned_length;
		}
	}
	assert(IS_EXPORT_ALIGNED(exported_size));

	if (program_name_length > 0) {
		const size_t aligned_length = GET_EXPORT_STR_LEN(program_name_length);
		exported_size += aligned_length;
		if (out_ptr) {
			if (size < exported_size)
				return 0;
			strcpy(out_ptr, ctx->program_name);
			out_ptr += aligned_length;
		}
		assert(IS_EXPORT_ALIGNED(exported_size));
	}

	if (ctx->content.length > 0) {
		const size_t aligned_length = GET_EXPORT_STR_LEN(ctx->content.length);
		exported_size += aligned_length;
		if (out_ptr) {
			if (size < exported_size)
				return 0;
			assert(ctx->content.str != NULL);
			memcpy(out_ptr, ctx->content.str, ctx->content.length + 1);
			out_ptr += aligned_length;
		}
		assert(IS_EXPORT_ALIGNED(exported_size));
	}
	assert(!out || (size_t)(out_ptr - (char*)out) == exported_size);

#if defined(USE_OPENSSL)
	if (out_ptr && ARE_OPENSSL_METHODS(ctx->sha1_methods)) {
		size_t* error_ptr = (size_t*)((char*)out + head_size + offsetof(torrent_ctx, error));
		*error_ptr |= BT_CTX_OSSL_FLAG;
		RHASH_ASSERT(sizeof(*error_ptr) == sizeof(ctx->error));
	}
#endif
	return exported_size;
}

/**
 * Import algorithm context from a memory region.
 *
 * @param ctx pointer to the algorithm context
 * @param in pointer to the data to import
 * @param size size of data to import
 * @return the size of the imported data on success, 0 on fail.
 */
size_t bt_import(torrent_ctx* ctx, const void* in, size_t size)
{
	const size_t head_size = sizeof(bt_export_header);
	const size_t ctx_head_size = offsetof(torrent_ctx, hash_blocks);
	size_t imported_size = head_size + ctx_head_size;
	const char* in_ptr = (const char*)in;
	size_t padding_size;
	size_t hash_data_left;
	size_t length;
	size_t i;
	const bt_export_header* header = (const bt_export_header*)in_ptr;
	if (size < imported_size)
		return 0;
	if (header->torrent_ctx_size != sizeof(torrent_ctx))
		return 0;
	in_ptr += sizeof(bt_export_header);

	memset(ctx, 0, sizeof(torrent_ctx));
	memcpy(ctx, in_ptr, ctx_head_size);
	in_ptr += ctx_head_size;

	hash_data_left = ctx->piece_count * BT_HASH_SIZE;
	imported_size += hash_data_left;
	padding_size = GET_EXPORT_PADDING(imported_size);
	imported_size += padding_size;
	assert(IS_EXPORT_ALIGNED(imported_size));
	if (size < imported_size)
		return 0;

	while (hash_data_left) {
		size_t left = (hash_data_left < BT_BLOCK_SIZE_IN_BYTES ? hash_data_left : BT_BLOCK_SIZE_IN_BYTES);
		unsigned char* block = (unsigned char*)malloc(BT_BLOCK_SIZE_IN_BYTES);
		if (!block)
			return 0;
		if (!bt_vector_add_ptr(&ctx->hash_blocks, block)) {
			free(block);
			return 0;
		}
		memcpy(block, in_ptr, left);
		in_ptr += left;
		hash_data_left -= left;
	}
	in_ptr += padding_size;
	assert((size_t)(in_ptr - (char*)in) == imported_size);
	assert(IS_EXPORT_ALIGNED(imported_size));

	for (i = 0; i < header->files_size; i++) {
		uint64_t filesize;
		imported_size += sizeof(uint64_t);
		if (size < (imported_size + sizeof(size_t)))
			return 0;
		filesize = *(uint64_t*)in_ptr;
		in_ptr += sizeof(uint64_t);
		length = *(size_t*)in_ptr;
		imported_size += GET_EXPORT_SIZED_STR_LEN(length);
		if (!length || size < imported_size)
			return 0;
		if (!bt_add_file(ctx, in_ptr + sizeof(size_t), filesize))
			return 0;
		in_ptr += GET_EXPORT_SIZED_STR_LEN(length);
	}
	assert((size_t)(in_ptr - (char*)in) == imported_size);
	assert(IS_EXPORT_ALIGNED(imported_size));

	for (i = 0; i < header->announce_size; i++) {
		if (size < (imported_size + sizeof(size_t)))
			return 0;
		length = *(size_t*)in_ptr;
		imported_size += GET_EXPORT_SIZED_STR_LEN(length);
		if (!length || size < imported_size)
			return 0;
		if (!bt_add_announce(ctx, in_ptr + sizeof(size_t)))
			return 0;
		in_ptr += GET_EXPORT_SIZED_STR_LEN(length);
	}
	assert((size_t)(in_ptr - (char*)in) == imported_size);
	assert(IS_EXPORT_ALIGNED(imported_size));

	length = header->program_name_length;
	if (length) {
		imported_size += GET_EXPORT_STR_LEN(length);
		if (size < imported_size)
			return 0;
		if (!bt_set_program_name(ctx, in_ptr))
			return 0;
		in_ptr += GET_EXPORT_STR_LEN(length);
		assert((size_t)(in_ptr - (char*)in) == imported_size);
		assert(IS_EXPORT_ALIGNED(imported_size));
	}

#if defined(USE_OPENSSL)
	/* must restore ctx->error flag before calling bt_str_ensure_length() */
	if ((ctx->error & BT_CTX_OSSL_FLAG) != 0) {
		ctx->error &= ~BT_CTX_OSSL_FLAG;
		rhash_load_sha1_methods(&ctx->sha1_methods, METHODS_OPENSSL);
	} else {
		rhash_load_sha1_methods(&ctx->sha1_methods, METHODS_RHASH);
	}
#endif

	length = header->content_length;
	if (length) {
		imported_size += GET_EXPORT_STR_LEN(length);
		if (size < imported_size)
			return 0;
		if (!bt_str_ensure_length(ctx, length))
			return 0;
		memcpy(ctx->content.str, in_ptr, length);
		in_ptr += GET_EXPORT_STR_LEN(length);
		assert((size_t)(in_ptr - (char*)in) == imported_size);
		assert(IS_EXPORT_ALIGNED(imported_size));
	}
	return imported_size;
}
#endif /* !defined(NO_IMPORT_EXPORT) */
