/*
20140126
20241215 - reformated using clang-format
Jan Mojzis
Public domain.
*/

#include <poll.h>
#include <unistd.h>
#include "byte.h"
#include "e.h"
#include "bug.h"
#include "crypto_uint32.h"
#include "purge.h"
#include "ssh.h"
#include "sshcrypto.h"
#include "numtostr.h"
#include "packet.h"

static int packet_get_plain_(struct buf *b) {

    crypto_uint32 packet_length;
    long long len;
    struct buf *recvbuf = &packet.recvbuf;
    unsigned char *pp;
    long long l;

    pp = recvbuf->buf + PACKET_ZEROBYTES;
    l = recvbuf->len - PACKET_ZEROBYTES;

    /* we need at least 4 bytes */
    if (l < 4) return 1;

    /* parse length */
    packet_length = crypto_uint32_load_bigendian(pp);
    if (packet_length > PACKET_LIMIT) {
        char buf1[NUMTOSTR_LEN];
        char buf2[NUMTOSTR_LEN];
        errno = EPROTO;
        log_f4("packet length ", numtostr(buf1, packet_length),
               " > PACKET_LIMIT ", numtostr(buf2, PACKET_LIMIT));
        global_die(111);
    }
    if (packet_length + 4 > l) return 1;

    /* we have full packet */
    len = packet_length;
    len -= recvbuf->buf[PACKET_ZEROBYTES + 4] + 1;
    if (len <= 0) bug_proto();
    buf_put(b, recvbuf->buf + PACKET_ZEROBYTES + 5, len);

    byte_copy(pp, l - packet_length + 4, pp + packet_length + 4);
    purge(pp + l - packet_length + 4, packet_length + 4);
    recvbuf->len -= packet_length + 4;

    packet.receivepacketid++;
    return 1;
}

static int packet_get_(struct buf *b) {

    crypto_uint32 lastreceivepacketid = packet.receivepacketid;
    int ret;

    if (packet.flagkeys) { ret = sshcrypto_packet_get(b); }
    else { ret = packet_get_plain_(b); }

    /* overflow check */
    if (lastreceivepacketid > packet.receivepacketid) {
        log_f1("receivepacketid overflow");
        global_die(111);
    }

    return ret;
}

int packet_get(struct buf *b, crypto_uint8 x) {

    buf_purge(b);
    if (!packet_get_(b)) return 0;
    if (b->len <= 0) return 1;
    if (!packet.flagauthorized)
        if (packet.receivepacketid > PACKET_UNAUTHENTICATED_MESSAGES) {
            errno = EPROTO;
            log_f1("too many unauthenticated messages");
            global_die(111);
        }

    switch (b->buf[0]) {
        case SSH_MSG_DISCONNECT:
            errno = 0;
            return 0;
        case SSH_MSG_IGNORE:
        case SSH_MSG_DEBUG:
            if (!packet.flagkeys) {
                log_f1("SSH_MSG_IGNORE/SSH_MSG_DEBUG packet rejected in "
                       "plain-text mode");
                global_die(111);
            }
            buf_purge(b);
            break;
        case SSH_MSG_NEWKEYS:
            /* strict kex - reset receivepacketid */
            if (sshcrypto_kex_flags & sshcrypto_FLAGSTRICTKEX) {
                packet.receivepacketid = 0;
            }
            break;
        default:
            if (x && x != b->buf[0]) {
                char buf1[NUMTOSTR_LEN];
                char buf2[NUMTOSTR_LEN];
                errno = EPROTO;
                log_f4("expected packet type ", numtostr(buf1, x), ", got ",
                       numtostr(buf2, b->buf[0]));
                global_die(111);
            }
            break;
    }
    return 1;
}

int packet_getall(struct buf *b, crypto_uint8 ch) {

    struct pollfd x;
    long long before;

    buf_purge(b);

    for (;;) {
        before = packet.recvbuf.len;
        if (!packet_get(b, ch)) return 0;
        if (b->len > 0) break;
        if (before != packet.recvbuf.len) continue;
        x.fd = 0;
        x.events = POLLIN | POLLERR;
        poll(&x, 1, -1);
        if (!packet_recv()) return 0;
    }
    return 1;
}
