/*
20140108
20241208 - reformated using clang-format
Jan Mojzis
Public domain.

The 'buf' library holds a string in allocated space.
The string length is limited by allocated memory.
All 'buf_put*' functions checks the limit and if
the string exceeds the limit, 'buf_put*' function
immediately exits with 111 status code.
*/

#include "byte.h"
#include "str.h"
#include "purge.h"
#include "randombytes.h"
#include "crypto_uint32.h"
#include "bug.h"
#include "buf.h"

/*
Initialize 'b' structure.
*/
void buf_init_(const char *fn, unsigned long long line, struct buf *b,
               unsigned char *buf, long long alloc) {

    if (!b || !buf || alloc <= 0 || alloc > 1073741824) bug_inval_(fn, line);

    b->buf = buf;
    b->len = 0;
    b->alloc = alloc;
    purge(b->buf, b->alloc);
}

/*
Remove content of 'b'.
*/
void buf_purge_(const char *fn, unsigned long long line, struct buf *b) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf)
        bug_inval_(fn, line);

    purge(b->buf, b->len);
    b->len = 0;
}

/*
Retun if 'b' has available space for string of length 'len'.
*/
int buf_ready_(const char *fn, unsigned long long line, struct buf *b,
               long long len) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf || len < 0 || len > 1073741824)
        bug_inval_(fn, line);
    return (b->len + len < b->alloc);
}

/*
Put string 'x' of length 'len'.
*/
int buf_put_(const char *fn, unsigned long long line, struct buf *b,
             const unsigned char *x, long long len) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf || !x || len < 0 || len > 1073741824)
        bug_inval_(fn, line);
    if (b->len + len >= b->alloc) bug_nomem_(fn, line);

    byte_copy(b->buf + b->len, len, x);
    b->len += len;
    return 1;
}

/*
Put 0-terminated string 'x'.
*/
int buf_puts_(const char *fn, unsigned long long line, struct buf *b,
              const char *x) {
    return buf_put_(fn, line, b, (const unsigned char *) x, str_len(x));
}

/*
Put zero bytes of length 'len'.
*/
int buf_putzerobytes_(const char *fn, unsigned long long line, struct buf *b,
                      long long len) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf || len < 0 || len > 1073741824)
        bug_inval_(fn, line);
    if (b->len + len >= b->alloc) bug_nomem_(fn, line);

    byte_zero(b->buf + b->len, len);
    b->len += len;
    return 1;
}

/*
Put random bytes of length 'len'.
*/
int buf_putrandombytes_(const char *fn, unsigned long long line, struct buf *b,
                        long long len) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf || len < 0 || len > 1073741824)
        bug_inval_(fn, line);
    if (b->len + len >= b->alloc) bug_nomem_(fn, line);

    randombytes(b->buf + b->len, len);
    b->len += len;
    return 1;
}

/*
Put padding of length 'len'.
*/
int buf_putpadding_(const char *fn, unsigned long long line, struct buf *b,
                    long long len) {

    if (!b || b->len < 0 || b->len > 1073741824 || b->alloc <= 0 ||
        b->alloc > 1073741824 || !b->buf || len < 0 || len > 1073741824)
        bug_inval_(fn, line);
    if (b->len + len >= b->alloc) bug_nomem_(fn, line);

    purge(b->buf + b->len, len);
    b->len += len;
    return 1;
}

/*
Put 32-bit unsigned integer in big-endian format.
*/
int buf_putnum32_(const char *fn, unsigned long long line, struct buf *b,
                  crypto_uint32 x) {

    unsigned char s[4];

    crypto_uint32_store_bigendian(s, x);
    return buf_put_(fn, line, b, s, sizeof s);
}

/*
Put 8-bit unsigned integer.
*/
int buf_putnum8_(const char *fn, unsigned long long line, struct buf *b,
                 crypto_uint8 u) {

    return buf_put_(fn, line, b, &u, 1);
}

/*
Put string 'x' of length 'len' and format it as a SSH-string.
*/
int buf_putstringlen_(const char *fn, unsigned long long line, struct buf *b,
                      const unsigned char *x, long long len) {

    if (!buf_putnum32_(fn, line, b, len)) return 0;
    return buf_put_(fn, line, b, x, len);
}

/*
Put 0-terminated string 'x' and format it as a SSH-string.
*/
int buf_putstring_(const char *fn, unsigned long long line, struct buf *b,
                   const char *x) {

    return buf_putstringlen_(fn, line, b, (const unsigned char *) x,
                             str_len(x));
}

/*
Put SSH shared secret (bignum formated into wire format)
*/
int buf_putsharedsecret_(const char *fn, unsigned long long line, struct buf *b,
                         const unsigned char *x, long long len) {

    long long pos;

    if (len < 0 || len > 1073741824 || !b || !x) bug_inval_(fn, line);

    for (pos = 0; pos < len; ++pos)
        if (x[pos]) break;

    if (x[pos] & 0x80) {
        buf_putnum32_(fn, line, b, len - pos + 1);
        buf_putnum8_(fn, line, b, 0);
    }
    else { buf_putnum32_(fn, line, b, len - pos + 0); }
    return buf_put_(fn, line, b, x + pos, len - pos);
}

/*
Put string 'x' of length 'len' and encode it into base64.
*/
static const unsigned char b64chars[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int buf_putbase64_(const char *fn, unsigned long long line, struct buf *b,
                   const unsigned char *x, long long len) {

    long long i;
    unsigned long long bits = 0, v = 0;
    unsigned char ch;

    if (len < 0 || len > 1073741824 || !b || !x) bug_inval_(fn, line);

    for (i = 0; i < len; ++i) {
        v <<= 8;
        v += x[i];
        bits += 8;
        while (bits > 6) {
            ch = b64chars[((v >> (bits - 6)) & 63)];
            buf_putnum8_(fn, line, b, ch);
            bits -= 6;
        }
    }

    if (bits) {
        v <<= (6 - bits);
        ch = b64chars[v & 63];
        buf_putnum8_(fn, line, b, ch);
    }
    while (b->len & 3) { buf_putnum8_(fn, line, b, '='); }
    return 1;
}
