#ifndef PUTTY_MARSHAL_H
#define PUTTY_MARSHAL_H

#include "defs.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);
    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_->binarysink_ = (obj)->binarysink_)

/*
 * 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. uint64 is the structure type
 * defined in int64.h, not the C99 built-in type. */
#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_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)

/* 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)

/*
 * 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_byte(BinarySink *, unsigned char);
void BinarySink_put_bool(BinarySink *, int);
void BinarySink_put_uint16(BinarySink *, unsigned long);
void BinarySink_put_uint32(BinarySink *, unsigned long);
void BinarySink_put_string(BinarySink *, const void *data, size_t len);
void BinarySink_put_stringz(BinarySink *, const char *str);
struct strbuf;
void BinarySink_put_stringsb(BinarySink *, struct strbuf *);
void BinarySink_put_asciz(BinarySink *, const char *str);
int BinarySink_put_pstring(BinarySink *, const char *str);

#endif /* PUTTY_MARSHAL_H */