/*
 * pgpHeader.c -- write out a file header.
 *
 * currently, the header is: 10101000b \003 P G P == "\250\003PGP".  In
 * other words, this is an OLD literal-1 packet which PGP 2.6.2 supports
 * for backwards compatibility with pre-release versions of PGP 2.0, but
 * have never been generated by any released version of PGP.  PGP 2.6.2
 * will see this as a literal packet of type 'P', which is an unknown type,
 * therefore complaining that it needs a newer version of PGP.
 *
 * PGPlib, on the other hand, will know to just ignore this completely.
 *
 * This module will wait for input.  It will then check the first byte
 * of the message.  If it is an old-style PGP message (10xxxxxx) then it
 * does nothing.  If it is a new-style PGP message (11xxxxxx) then it will
 * emit the header.
 *
 * Written by:	Derek Atkins <warlord@MIT.EDU>
 *
 * $Id: pgpHeader.c,v 1.17 1997/07/26 19:37:25 lloyd Exp $
 */

#include "pgpConfig.h"

#include <stdio.h>
#include <string.h>

#include "pgpDebug.h"
#include "pgpHeader.h"
#include "pgpPktByte.h"
#include "pgpMem.h"
#include "pgpErrors.h"
#include "pgpPipeline.h"
#include "pgpContext.h"

#define HEADERMAGIC	0x4eade640

static PGPByte const pgpheader[] = { 168, 3, 'P', 'G', 'P' };

typedef struct HeaderContext {
	PGPPipeline	pipe;
	
	PGPByte header[sizeof(pgpheader)];
	size_t hdrlen;
	size_t offset;
	PGPPipeline *tail;
	PGPByte done;
	DEBUG_STRUCT_CONSTRUCTOR( HeaderContext )
} HeaderContext;

static PGPError
DoFlush (HeaderContext *context)
{
	PGPError	error = kPGPError_NoErr;
	size_t retlen;

	/* Try to flush anything that we have buffered */
	while (context->hdrlen) {
		retlen = context->tail->write (context->tail,
					       context->header+context->offset,
					       context->hdrlen,
					       &error);
		context->hdrlen -= retlen;
		context->offset += retlen;
		if (error)
			return error;
	}
	return error;
}

static PGPError
Flush (PGPPipeline *myself)
{
	HeaderContext *context;
	PGPError	error;

	pgpAssert (myself);
	pgpAssert (myself->magic == HEADERMAGIC);

	context = (HeaderContext *)myself->priv;
	pgpAssert (context);
	pgpAssert (context->tail);

	error = DoFlush (context);
	if (error)
		return error;

	return context->tail->flush (context->tail);
}

static size_t
Write (PGPPipeline *myself, PGPByte const *buf, size_t size, PGPError *error)
{
	HeaderContext *context;

	pgpAssert (myself);
	pgpAssert (myself->magic == HEADERMAGIC);
	pgpAssert (error);

	context = (HeaderContext *)myself->priv;
	pgpAssert (context);
	pgpAssert (context->tail);

	if (!context->done) {
		if (!size)
			return 0;

		if (IS_NEW_PKTBYTE (*buf))
			switch (NEW_PKTBYTE_TYPE (*buf)) {
				/* XXX: These should match pgpFileType.c */
			case PKTBYTE_ESK:
			case PKTBYTE_SIG:
			case PKTBYTE_CONVESK:
			case PKTBYTE_1PASSSIG:
			case PKTBYTE_SECKEY:
			case PKTBYTE_PUBKEY:
			case PKTBYTE_SECSUBKEY:
			case PKTBYTE_PUBSUBKEY:
			case PKTBYTE_COMPRESSED:
			case PKTBYTE_CONVENTIONAL:
			case PKTBYTE_LITERAL:
				memcpy (context->header, pgpheader,
					sizeof(pgpheader));
				context->hdrlen = sizeof (pgpheader);
				break;
			}

		context->done = 1;
	}

	*error = DoFlush (context);
	if (*error)
		return 0;

	return context->tail->write (context->tail, buf, size, error);
}

static PGPError
Annotate (PGPPipeline *myself, PGPPipeline *origin, int type,
	  PGPByte const *string, size_t size)
{
	HeaderContext *context;
	PGPError	error;

	pgpAssert (myself);
	pgpAssert (myself->magic == HEADERMAGIC);

	context = (HeaderContext *)myself->priv;
	pgpAssert (context);
	pgpAssert (context->tail);

	error = DoFlush (context);
	if (error)
		return error;

	return context->tail->annotate (context->tail, origin, type,
					string, size);
}

static PGPError
SizeAdvise (PGPPipeline *myself, unsigned long bytes)
{
	HeaderContext *context;
	PGPError	error;

	pgpAssert (myself);
	pgpAssert (myself->magic == HEADERMAGIC);

	context = (HeaderContext *)myself->priv;
	pgpAssert (context);
	pgpAssert (context->tail);

	/* Can't send the sizeAdvise until we've figured out who we are */
	   
	if (!context->done)
		return kPGPError_NoErr;

	error = DoFlush (context);
	if (error)
		return error;

	return context->tail->sizeAdvise (context->tail, bytes);
}

static PGPError
Teardown (PGPPipeline *myself)
{
	HeaderContext *context;
	PGPContextRef	cdkContext;
	
	pgpAssertAddrValid( myself, PGPPipeline );
	cdkContext	= myself->cdkContext;

	pgpAssert (myself);
	pgpAssert (myself->magic == HEADERMAGIC);

	context = (HeaderContext *)myself->priv;
	pgpAssert (context);

	if (context->tail)
		context->tail->teardown (context->tail);

	pgpClearMemory( context,  sizeof (*context));
	pgpContextMemFree( cdkContext, context);
	
	return kPGPError_NoErr;
}

PGPPipeline **
pgpHeaderCreate (
	PGPContextRef cdkContext,
	PGPPipeline **head)
{
	PGPPipeline *mod;
	HeaderContext *context;

	if (!head)
		return NULL;

	context = (HeaderContext *)pgpContextMemAlloc( cdkContext,
		sizeof (*context), kPGPMemoryFlags_Clear );
	if (!context)
		return NULL;
	mod = &context->pipe;

	mod->magic = HEADERMAGIC;
	mod->write = Write;
	mod->flush = Flush;
	mod->sizeAdvise = SizeAdvise;
	mod->annotate = Annotate;
	mod->teardown = Teardown;
	mod->name = "File Header Module";
	mod->priv = context;
	mod->cdkContext	= cdkContext;

	context->tail = *head;
	*head = mod;
	return &context->tail;
}
