#ifndef PUTTY_MARSHAL_H
#define PUTTY_MARSHAL_H

#include "defs.h"

#include <stdio.h>
#include <stdarg.h>

/*
 * A sort of 'abstract base class' or 'interface' or 'trait' which is
 * the common feature of all types that want to accept data formatted
 * using the SSH binary conventions of uint32, string, mpint etc.
 */
struct BinarySink {
    void (*write)(BinarySink *sink, const void *data, size_t len);
    void (*writefmtv)(BinarySink *sink, const char *fmt, va_list ap);
    BinarySink *binarysink_;
};

/*
 * To define a structure type as a valid target for binary formatted
 * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when
 * an instance is set up, use 'BinarySink_INIT' to initialise the
 * 'base class' state, providing a function pointer to be the
 * implementation of the write() call above.
 */
#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1]
#define BinarySink_INIT(obj, writefn) \
    ((obj)->binarysink_->write = (writefn), \
     (obj)->binarysink_->writefmtv = NULL, \
     (obj)->binarysink_->binarysink_ = (obj)->binarysink_)

/*
 * To define a larger structure type as a valid BinarySink in such a
 * way that it will delegate the write method to some other object,
 * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and
 * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point
 * at the object it wants to delegate to.
 *
 * In such a delegated structure, you might sometimes want to have the
 * delegation stop being valid (e.g. it might be delegating to an
 * object that only sometimes exists). You can null out the delegate
 * pointer using BinarySink_DELEGATE_CLEAR.
 */
#define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_
#define BinarySink_DELEGATE_INIT(obj, othersink) \
    ((obj)->binarysink_ = BinarySink_UPCAST(othersink))
#define BinarySink_DELEGATE_CLEAR(obj) ((obj)->binarysink_ = NULL)

/*
 * The implementing type's write function will want to downcast its
 * 'BinarySink *' parameter back to the more specific type. Also,
 * sometimes you'll want to upcast a pointer to a particular
 * implementing type into an abstract 'BinarySink *' to pass to
 * generic subroutines not defined in this file. These macros do that
 * job.
 *
 * Importantly, BinarySink_UPCAST can also be applied to a BinarySink
 * * itself (and leaves it unchanged). That's achieved by a small
 * piece of C trickery: implementing structures and the BinarySink
 * structure itself both contain a field called binarysink_, but in
 * implementing objects it's a BinarySink[1] whereas in the abstract
 * type it's a 'BinarySink *' pointing back to the same structure,
 * meaning that you can say 'foo->binarysink_' in either case and get
 * a pointer type by different methods.
 */
#define BinarySink_DOWNCAST(object, type)                               \
    TYPECHECK((object) == ((type *)0)->binarysink_,                     \
              ((type *)(((char *)(object)) - offsetof(type, binarysink_))))
#define BinarySink_UPCAST(object)                                       \
    TYPECHECK((object)->binarysink_ == (BinarySink *)0,                 \
              (object)->binarysink_)

/*
 * If you structure-copy an object that's implementing BinarySink,
 * then that tricky self-pointer in its trait subobject will point to
 * the wrong place. You could call BinarySink_INIT again, but this
 * macro is terser and does all that's needed to fix up the copied
 * object.
 */
#define BinarySink_COPIED(obj) \
    ((obj)->binarysink_->binarysink_ = (obj)->binarysink_)

/*
 * The put_* macros are the main client to this system. Any structure
 * which implements the BinarySink 'trait' is valid for use as the
 * first parameter of any of these put_* macros.
 */

/* Basic big-endian integer types. */
#define put_byte(bs, val) \
    BinarySink_put_byte(BinarySink_UPCAST(bs), val)
#define put_uint16(bs, val) \
    BinarySink_put_uint16(BinarySink_UPCAST(bs), val)
#define put_uint32(bs, val) \
    BinarySink_put_uint32(BinarySink_UPCAST(bs), val)
#define put_uint64(bs, val) \
    BinarySink_put_uint64(BinarySink_UPCAST(bs), val)

/* SSH booleans, encoded as a single byte storing either 0 or 1. */
#define put_bool(bs, val) \
    BinarySink_put_bool(BinarySink_UPCAST(bs), val)

/* SSH strings, with a leading uint32 length field. 'stringz' is a
 * convenience function that takes an ordinary C zero-terminated
 * string as input. 'stringsb' takes a strbuf * as input, and
 * finalises it as a side effect (handy for multi-level marshalling in
 * which you use these same functions to format an inner blob of data
 * that then gets wrapped into a string container in an outer one). */
#define put_string(bs, val, len) \
    BinarySink_put_string(BinarySink_UPCAST(bs),val,len)
#define put_stringpl(bs, ptrlen) \
    BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen)
#define put_stringz(bs, val) \
    BinarySink_put_stringz(BinarySink_UPCAST(bs), val)
#define put_stringsb(bs, val) \
    BinarySink_put_stringsb(BinarySink_UPCAST(bs), val)

/* Other string outputs: 'asciz' emits the string data directly into
 * the output including the terminating \0, and 'pstring' emits the
 * string in Pascal style with a leading _one_-byte length field.
 * pstring can fail if the string is too long. */
#define put_asciz(bs, val) \
    BinarySink_put_asciz(BinarySink_UPCAST(bs), val)
#define put_pstring(bs, val) \
    BinarySink_put_pstring(BinarySink_UPCAST(bs), val)

/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */
#define put_mp_ssh1(bs, val) \
    BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val)
#define put_mp_ssh2(bs, val) \
    BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val)

/* Padding with a specified byte. */
#define put_padding(bs, len, padbyte) \
    BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte)

/* Fallback: just emit raw data bytes, using a syntax that matches the
 * rest of these macros. */
#define put_data(bs, val, len) \
    BinarySink_put_data(BinarySink_UPCAST(bs), val, len)
#define put_datapl(bs, pl) \
    BinarySink_put_datapl(BinarySink_UPCAST(bs), pl)
#define put_dataz(bs, val) \
    BinarySink_put_datapl(BinarySink_UPCAST(bs), ptrlen_from_asciz(val))
#define put_datalit(bs, val) \
    BinarySink_put_datapl(BinarySink_UPCAST(bs), PTRLEN_LITERAL(val))

/* Emit printf-formatted data, with no terminator. */
#define put_fmt(bs, ...) \
    BinarySink_put_fmt(BinarySink_UPCAST(bs), __VA_ARGS__)
#define put_fmtv(bs, fmt, ap) \
    BinarySink_put_fmtv(BinarySink_UPCAST(bs), fmt, ap)

/* More complicated function implemented in write_c_string_literal.c */
#define put_c_string_literal(bs, str) \
    BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str)

/* More complicated function implemented in encode_utf8.c */
#define put_utf8_char(bs, c) \
    BinarySink_put_utf8_char(BinarySink_UPCAST(bs), c)

/* More complicated functions still implemented in <platform>/unicode.c */
#define put_mb_to_wc(bs, codepage, mbstr, mblen) \
    BinarySink_put_mb_to_wc(BinarySink_UPCAST(bs), codepage, mbstr, mblen)
#define put_wc_to_mb(bs, codepage, wcstr, wclen, def) \
    BinarySink_put_wc_to_mb(BinarySink_UPCAST(bs), codepage, wcstr, wclen, def)

/*
 * The underlying real C functions that implement most of those
 * macros. Generally you won't want to call these directly, because
 * they have such cumbersome names; you call the wrapper macros above
 * instead.
 *
 * A few functions whose wrapper macros are defined above are actually
 * declared in other headers, so as to guarantee that the
 * declaration(s) of their other parameter type(s) are in scope.
 */
void BinarySink_put_data(BinarySink *, const void *data, size_t len);
void BinarySink_put_datapl(BinarySink *, ptrlen);
void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte);
void BinarySink_put_byte(BinarySink *, unsigned char);
void BinarySink_put_bool(BinarySink *, bool);
void BinarySink_put_uint16(BinarySink *, unsigned long);
void BinarySink_put_uint32(BinarySink *, unsigned long);
void BinarySink_put_uint64(BinarySink *, uint64_t);
void BinarySink_put_string(BinarySink *, const void *data, size_t len);
void BinarySink_put_stringpl(BinarySink *, ptrlen);
void BinarySink_put_stringz(BinarySink *, const char *str);
void BinarySink_put_stringsb(BinarySink *, strbuf *);
void BinarySink_put_asciz(BinarySink *, const char *str);
bool BinarySink_put_pstring(BinarySink *, const char *str);
void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x);
void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x);
void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3);
void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap);
void BinarySink_put_c_string_literal(BinarySink *, ptrlen);
void BinarySink_put_utf8_char(BinarySink *, unsigned);
/* put_mb_to_wc / put_wc_to_mb return false if the codepage is invalid */
bool BinarySink_put_mb_to_wc(
    BinarySink *bs, int codepage, const char *mbstr, int mblen);
bool BinarySink_put_wc_to_mb(
    BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen,
    const char *defchr);

/* ---------------------------------------------------------------------- */

/*
 * A complementary trait structure for _un_-marshalling.
 *
 * This structure contains client-visible data fields rather than
 * methods, because that seemed more useful than leaving it totally
 * opaque. But it's still got the self-pointer system that will allow
 * the set of get_* macros to target one of these itself or any other
 * type that 'derives' from it. So, for example, an SSH packet
 * structure can act as a BinarySource while also having additional
 * fields like the packet type.
 */
typedef enum BinarySourceError {
    BSE_NO_ERROR,
    BSE_OUT_OF_DATA,
    BSE_INVALID
} BinarySourceError;
struct BinarySource {
    /*
     * (data, len) is the data block being decoded. pos is the current
     * position within the block.
     */
    const void *data;
    size_t pos, len;

    /*
     * 'err' indicates whether a decoding error has happened at any
     * point. Once this has been set to something other than
     * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling
     * function. So you can safely do a long sequence of get_foo()
     * operations and then test err just once at the end, rather than
     * having to conditionalise every single get.
     *
     * The unmarshalling functions should always return some value,
     * even if a decoding error occurs. Generally on error they'll
     * return zero (if numeric) or the empty string (if string-based),
     * or some other appropriate default value for more complicated
     * types.
     *
     * If the usual return value is dynamically allocated (e.g. a
     * bignum, or a normal C 'char *' string), then the error value is
     * also dynamic in the same way. So you have to free exactly the
     * same set of things whether or not there was a decoding error,
     * which simplifies exit paths - for example, you could call a big
     * pile of get_foo functions, then put the actual handling of the
     * results under 'if (!get_err(src))', and then free everything
     * outside that if.
     */
    BinarySourceError err;

    /*
     * Self-pointer for the implicit derivation trick, same as
     * BinarySink above.
     */
    BinarySource *binarysource_;
};

/*
 * Implementation macros, similar to BinarySink.
 */
#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1]
static inline void BinarySource_INIT__(BinarySource *src, ptrlen data)
{
    src->data = data.ptr;
    src->len = data.len;
    src->pos = 0;
    src->err = BSE_NO_ERROR;
    src->binarysource_ = src;
}
#define BinarySource_BARE_INIT_PL(obj, pl)                      \
    TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0,      \
              BinarySource_INIT__(obj, pl))
#define BinarySource_BARE_INIT(obj, data_, len_)                \
    BinarySource_BARE_INIT_PL(obj, make_ptrlen(data_, len_))
#define BinarySource_INIT_PL(obj, pl)                                   \
    TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0,          \
              BinarySource_INIT__(BinarySource_UPCAST(obj), pl))
#define BinarySource_INIT(obj, data_, len_)             \
    BinarySource_INIT_PL(obj, make_ptrlen(data_, len_))
#define BinarySource_DOWNCAST(object, type)                             \
    TYPECHECK((object) == ((type *)0)->binarysource_,                     \
              ((type *)(((char *)(object)) - offsetof(type, binarysource_))))
#define BinarySource_UPCAST(object)                                       \
    TYPECHECK((object)->binarysource_ == (BinarySource *)0,                 \
              (object)->binarysource_)
#define BinarySource_COPIED(obj) \
    ((obj)->binarysource_->binarysource_ = (obj)->binarysource_)
#define BinarySource_REWIND_TO(src, pos) \
    BinarySource_REWIND_TO__((src)->binarysource_, pos)
#define BinarySource_REWIND(src) \
    BinarySource_REWIND_TO__((src)->binarysource_, 0)

#define get_data(src, len) \
    BinarySource_get_data(BinarySource_UPCAST(src), len)
#define get_byte(src) \
    BinarySource_get_byte(BinarySource_UPCAST(src))
#define get_bool(src) \
    BinarySource_get_bool(BinarySource_UPCAST(src))
#define get_uint16(src) \
    BinarySource_get_uint16(BinarySource_UPCAST(src))
#define get_uint32(src) \
    BinarySource_get_uint32(BinarySource_UPCAST(src))
#define get_uint64(src) \
    BinarySource_get_uint64(BinarySource_UPCAST(src))
#define get_string(src) \
    BinarySource_get_string(BinarySource_UPCAST(src))
#define get_asciz(src) \
    BinarySource_get_asciz(BinarySource_UPCAST(src))
#define get_chars(src, include) \
    BinarySource_get_chars(BinarySource_UPCAST(src), include)
#define get_nonchars(src, exclude) \
    BinarySource_get_nonchars(BinarySource_UPCAST(src), exclude)
#define get_chomped_line(src) \
    BinarySource_get_chomped_line(BinarySource_UPCAST(src))
#define get_pstring(src) \
    BinarySource_get_pstring(BinarySource_UPCAST(src))
#define get_mp_ssh1(src) \
    BinarySource_get_mp_ssh1(BinarySource_UPCAST(src))
#define get_mp_ssh2(src) \
    BinarySource_get_mp_ssh2(BinarySource_UPCAST(src))
#define get_rsa_ssh1_pub(src, rsa, order) \
    BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order)
#define get_rsa_ssh1_priv(src, rsa) \
    BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa)
#define get_rsa_ssh1_priv_agent(src) \
    BinarySource_get_rsa_ssh1_priv_agent(BinarySource_UPCAST(src))

#define get_err(src) (BinarySource_UPCAST(src)->err)
#define get_avail(src) (BinarySource_UPCAST(src)->len - \
                        BinarySource_UPCAST(src)->pos)
#define get_ptr(src)                                                    \
    ((const void *)(                                                    \
        (const unsigned char *)(BinarySource_UPCAST(src)->data) +       \
        BinarySource_UPCAST(src)->pos))

ptrlen BinarySource_get_data(BinarySource *, size_t);
unsigned char BinarySource_get_byte(BinarySource *);
bool BinarySource_get_bool(BinarySource *);
unsigned BinarySource_get_uint16(BinarySource *);
unsigned long BinarySource_get_uint32(BinarySource *);
uint64_t BinarySource_get_uint64(BinarySource *);
ptrlen BinarySource_get_string(BinarySource *);
const char *BinarySource_get_asciz(BinarySource *);
ptrlen BinarySource_get_chars(BinarySource *, const char *include_set);
ptrlen BinarySource_get_nonchars(BinarySource *, const char *exclude_set);
ptrlen BinarySource_get_chomped_line(BinarySource *);
ptrlen BinarySource_get_pstring(BinarySource *);
mp_int *BinarySource_get_mp_ssh1(BinarySource *src);
mp_int *BinarySource_get_mp_ssh2(BinarySource *src);

void BinarySource_REWIND_TO__(BinarySource *src, size_t pos);

/*
 * A couple of useful standard BinarySink implementations, which live
 * as sensibly here as anywhere else: one that makes a BinarySink
 * whose effect is to write to a stdio stream, and one whose effect is
 * to append to a bufchain.
 */
struct stdio_sink {
    FILE *fp;
    BinarySink_IMPLEMENTATION;
};
struct bufchain_sink {
    bufchain *ch;
    BinarySink_IMPLEMENTATION;
};
struct buffer_sink {
    char *out;
    size_t space;
    bool overflowed;
    BinarySink_IMPLEMENTATION;
};
void stdio_sink_init(stdio_sink *sink, FILE *fp);
void bufchain_sink_init(bufchain_sink *sink, bufchain *ch);
void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len);

#endif /* PUTTY_MARSHAL_H */
