/*
 * Packet interface
 * Copyright (C) 1999 Kunihiro Ishiguro
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * GNU Zebra is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <zebra.h>
#include <stddef.h>
#include <pthread.h>

#include "stream.h"
#include "memory.h"
#include "network.h"
#include "prefix.h"
#include "log.h"
#include "frr_pthread.h"
#include "lib_errors.h"

DEFINE_MTYPE_STATIC(LIB, STREAM, "Stream")
DEFINE_MTYPE_STATIC(LIB, STREAM_FIFO, "Stream FIFO")

/* Tests whether a position is valid */
#define GETP_VALID(S, G) ((G) <= (S)->endp)
#define PUT_AT_VALID(S,G) GETP_VALID(S,G)
#define ENDP_VALID(S, E) ((E) <= (S)->size)

/* asserting sanity checks. Following must be true before
 * stream functions are called:
 *
 * Following must always be true of stream elements
 * before and after calls to stream functions:
 *
 * getp <= endp <= size
 *
 * Note that after a stream function is called following may be true:
 * if (getp == endp) then stream is no longer readable
 * if (endp == size) then stream is no longer writeable
 *
 * It is valid to put to anywhere within the size of the stream, but only
 * using stream_put..._at() functions.
 */
#define STREAM_WARN_OFFSETS(S)                                                 \
	do {                                                                   \
		flog_warn(EC_LIB_STREAM,				       \
			  "&(struct stream): %p, size: %lu, getp: %lu, endp: %lu\n", \
			  (void *)(S), (unsigned long)(S)->size,	       \
			  (unsigned long)(S)->getp, (unsigned long)(S)->endp); \
		zlog_backtrace(LOG_WARNING);				       \
	} while (0)

#define STREAM_VERIFY_SANE(S)                                                  \
	do {                                                                   \
		if (!(GETP_VALID(S, (S)->getp) && ENDP_VALID(S, (S)->endp))) { \
			STREAM_WARN_OFFSETS(S);                                \
		}                                                              \
		assert(GETP_VALID(S, (S)->getp));                              \
		assert(ENDP_VALID(S, (S)->endp));                              \
	} while (0)

#define STREAM_BOUND_WARN(S, WHAT)                                             \
	do {                                                                   \
		flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds",    \
			  __func__, (WHAT));                                   \
		STREAM_WARN_OFFSETS(S);                                        \
		assert(0);                                                     \
	} while (0)

#define STREAM_BOUND_WARN2(S, WHAT)                                            \
	do {                                                                   \
		flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds",    \
			  __func__, (WHAT));                                   \
		STREAM_WARN_OFFSETS(S);                                        \
	} while (0)

/* XXX: Deprecated macro: do not use */
#define CHECK_SIZE(S, Z)                                                       \
	do {                                                                   \
		if (((S)->endp + (Z)) > (S)->size) {                           \
			flog_warn(                                             \
				EC_LIB_STREAM,                                 \
				"CHECK_SIZE: truncating requested size %lu\n", \
				(unsigned long)(Z));                           \
			STREAM_WARN_OFFSETS(S);                                \
			(Z) = (S)->size - (S)->endp;                           \
		}                                                              \
	} while (0);

/* Make stream buffer. */
struct stream *stream_new(size_t size)
{
	struct stream *s;

	assert(size > 0);

	s = XMALLOC(MTYPE_STREAM, sizeof(struct stream) + size);

	s->getp = s->endp = 0;
	s->next = NULL;
	s->size = size;
	return s;
}

/* Free it now. */
void stream_free(struct stream *s)
{
	if (!s)
		return;

	XFREE(MTYPE_STREAM, s);
}

struct stream *stream_copy(struct stream *dest, const struct stream *src)
{
	STREAM_VERIFY_SANE(src);

	assert(dest != NULL);
	assert(STREAM_SIZE(dest) >= src->endp);

	dest->endp = src->endp;
	dest->getp = src->getp;

	memcpy(dest->data, src->data, src->endp);

	return dest;
}

struct stream *stream_dup(const struct stream *s)
{
	struct stream *snew;

	STREAM_VERIFY_SANE(s);

	if ((snew = stream_new(s->endp)) == NULL)
		return NULL;

	return (stream_copy(snew, s));
}

struct stream *stream_dupcat(const struct stream *s1, const struct stream *s2,
			     size_t offset)
{
	struct stream *new;

	STREAM_VERIFY_SANE(s1);
	STREAM_VERIFY_SANE(s2);

	if ((new = stream_new(s1->endp + s2->endp)) == NULL)
		return NULL;

	memcpy(new->data, s1->data, offset);
	memcpy(new->data + offset, s2->data, s2->endp);
	memcpy(new->data + offset + s2->endp, s1->data + offset,
	       (s1->endp - offset));
	new->endp = s1->endp + s2->endp;
	return new;
}

size_t stream_resize_inplace(struct stream **sptr, size_t newsize)
{
	struct stream *orig = *sptr;

	STREAM_VERIFY_SANE(orig);

	orig = XREALLOC(MTYPE_STREAM, orig, sizeof(struct stream) + newsize);

	orig->size = newsize;

	if (orig->endp > orig->size)
		orig->endp = orig->size;
	if (orig->getp > orig->endp)
		orig->getp = orig->endp;

	STREAM_VERIFY_SANE(orig);

	*sptr = orig;
	return orig->size;
}

size_t stream_get_getp(const struct stream *s)
{
	STREAM_VERIFY_SANE(s);
	return s->getp;
}

size_t stream_get_endp(const struct stream *s)
{
	STREAM_VERIFY_SANE(s);
	return s->endp;
}

size_t stream_get_size(const struct stream *s)
{
	STREAM_VERIFY_SANE(s);
	return s->size;
}

/* Stream structre' stream pointer related functions.  */
void stream_set_getp(struct stream *s, size_t pos)
{
	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, pos)) {
		STREAM_BOUND_WARN(s, "set getp");
		pos = s->endp;
	}

	s->getp = pos;
}

void stream_set_endp(struct stream *s, size_t pos)
{
	STREAM_VERIFY_SANE(s);

	if (!ENDP_VALID(s, pos)) {
		STREAM_BOUND_WARN(s, "set endp");
		return;
	}

	/*
	 * Make sure the current read pointer is not beyond the new endp.
	 */
	if (s->getp > pos) {
		STREAM_BOUND_WARN(s, "set endp");
		return;
	}

	s->endp = pos;
	STREAM_VERIFY_SANE(s);
}

/* Forward pointer. */
void stream_forward_getp(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, s->getp + size)) {
		STREAM_BOUND_WARN(s, "seek getp");
		return;
	}

	s->getp += size;
}

bool stream_forward_getp2(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, s->getp + size))
		return false;

	s->getp += size;

	return true;
}

void stream_rewind_getp(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (size > s->getp || !GETP_VALID(s, s->getp - size)) {
		STREAM_BOUND_WARN(s, "rewind getp");
		return;
	}

	s->getp -= size;
}

bool stream_rewind_getp2(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (size > s->getp || !GETP_VALID(s, s->getp - size))
		return false;

	s->getp -= size;

	return true;
}

void stream_forward_endp(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (!ENDP_VALID(s, s->endp + size)) {
		STREAM_BOUND_WARN(s, "seek endp");
		return;
	}

	s->endp += size;
}

bool stream_forward_endp2(struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (!ENDP_VALID(s, s->endp + size))
		return false;

	s->endp += size;

	return true;
}

/* Copy from stream to destination. */
bool stream_get2(void *dst, struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < size) {
		STREAM_BOUND_WARN2(s, "get");
		return false;
	}

	memcpy(dst, s->data + s->getp, size);
	s->getp += size;

	return true;
}

void stream_get(void *dst, struct stream *s, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < size) {
		STREAM_BOUND_WARN(s, "get");
		return;
	}

	memcpy(dst, s->data + s->getp, size);
	s->getp += size;
}

/* Get next character from the stream. */
bool stream_getc2(struct stream *s, uint8_t *byte)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint8_t)) {
		STREAM_BOUND_WARN2(s, "get char");
		return false;
	}
	*byte = s->data[s->getp++];

	return true;
}

uint8_t stream_getc(struct stream *s)
{
	uint8_t c;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint8_t)) {
		STREAM_BOUND_WARN(s, "get char");
		return 0;
	}
	c = s->data[s->getp++];

	return c;
}

/* Get next character from the stream. */
uint8_t stream_getc_from(struct stream *s, size_t from)
{
	uint8_t c;

	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + sizeof(uint8_t))) {
		STREAM_BOUND_WARN(s, "get char");
		return 0;
	}

	c = s->data[from];

	return c;
}

bool stream_getw2(struct stream *s, uint16_t *word)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint16_t)) {
		STREAM_BOUND_WARN2(s, "get ");
		return false;
	}

	*word = s->data[s->getp++] << 8;
	*word |= s->data[s->getp++];

	return true;
}

/* Get next word from the stream. */
uint16_t stream_getw(struct stream *s)
{
	uint16_t w;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint16_t)) {
		STREAM_BOUND_WARN(s, "get ");
		return 0;
	}

	w = s->data[s->getp++] << 8;
	w |= s->data[s->getp++];

	return w;
}

/* Get next word from the stream. */
uint16_t stream_getw_from(struct stream *s, size_t from)
{
	uint16_t w;

	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + sizeof(uint16_t))) {
		STREAM_BOUND_WARN(s, "get ");
		return 0;
	}

	w = s->data[from++] << 8;
	w |= s->data[from];

	return w;
}

/* Get next 3-byte from the stream. */
uint32_t stream_get3_from(struct stream *s, size_t from)
{
	uint32_t l;

	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + 3)) {
		STREAM_BOUND_WARN(s, "get 3byte");
		return 0;
	}

	l = s->data[from++] << 16;
	l |= s->data[from++] << 8;
	l |= s->data[from];

	return l;
}

uint32_t stream_get3(struct stream *s)
{
	uint32_t l;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < 3) {
		STREAM_BOUND_WARN(s, "get 3byte");
		return 0;
	}

	l = s->data[s->getp++] << 16;
	l |= s->data[s->getp++] << 8;
	l |= s->data[s->getp++];

	return l;
}

/* Get next long word from the stream. */
uint32_t stream_getl_from(struct stream *s, size_t from)
{
	uint32_t l;

	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + sizeof(uint32_t))) {
		STREAM_BOUND_WARN(s, "get long");
		return 0;
	}

	l = (unsigned)(s->data[from++]) << 24;
	l |= s->data[from++] << 16;
	l |= s->data[from++] << 8;
	l |= s->data[from];

	return l;
}

/* Copy from stream at specific location to destination. */
void stream_get_from(void *dst, struct stream *s, size_t from, size_t size)
{
	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + size)) {
		STREAM_BOUND_WARN(s, "get from");
		return;
	}

	memcpy(dst, s->data + from, size);
}

bool stream_getl2(struct stream *s, uint32_t *l)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN2(s, "get long");
		return false;
	}

	*l = (unsigned int)(s->data[s->getp++]) << 24;
	*l |= s->data[s->getp++] << 16;
	*l |= s->data[s->getp++] << 8;
	*l |= s->data[s->getp++];

	return true;
}

uint32_t stream_getl(struct stream *s)
{
	uint32_t l;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN(s, "get long");
		return 0;
	}

	l = (unsigned)(s->data[s->getp++]) << 24;
	l |= s->data[s->getp++] << 16;
	l |= s->data[s->getp++] << 8;
	l |= s->data[s->getp++];

	return l;
}

/* Get next quad word from the stream. */
uint64_t stream_getq_from(struct stream *s, size_t from)
{
	uint64_t q;

	STREAM_VERIFY_SANE(s);

	if (!GETP_VALID(s, from + sizeof(uint64_t))) {
		STREAM_BOUND_WARN(s, "get quad");
		return 0;
	}

	q = ((uint64_t)s->data[from++]) << 56;
	q |= ((uint64_t)s->data[from++]) << 48;
	q |= ((uint64_t)s->data[from++]) << 40;
	q |= ((uint64_t)s->data[from++]) << 32;
	q |= ((uint64_t)s->data[from++]) << 24;
	q |= ((uint64_t)s->data[from++]) << 16;
	q |= ((uint64_t)s->data[from++]) << 8;
	q |= ((uint64_t)s->data[from++]);

	return q;
}

uint64_t stream_getq(struct stream *s)
{
	uint64_t q;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint64_t)) {
		STREAM_BOUND_WARN(s, "get quad");
		return 0;
	}

	q = ((uint64_t)s->data[s->getp++]) << 56;
	q |= ((uint64_t)s->data[s->getp++]) << 48;
	q |= ((uint64_t)s->data[s->getp++]) << 40;
	q |= ((uint64_t)s->data[s->getp++]) << 32;
	q |= ((uint64_t)s->data[s->getp++]) << 24;
	q |= ((uint64_t)s->data[s->getp++]) << 16;
	q |= ((uint64_t)s->data[s->getp++]) << 8;
	q |= ((uint64_t)s->data[s->getp++]);

	return q;
}

bool stream_getq2(struct stream *s, uint64_t *q)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint64_t)) {
		STREAM_BOUND_WARN2(s, "get uint64");
		return false;
	}

	*q = ((uint64_t)s->data[s->getp++]) << 56;
	*q |= ((uint64_t)s->data[s->getp++]) << 48;
	*q |= ((uint64_t)s->data[s->getp++]) << 40;
	*q |= ((uint64_t)s->data[s->getp++]) << 32;
	*q |= ((uint64_t)s->data[s->getp++]) << 24;
	*q |= ((uint64_t)s->data[s->getp++]) << 16;
	*q |= ((uint64_t)s->data[s->getp++]) << 8;
	*q |= ((uint64_t)s->data[s->getp++]);

	return true;
}

/* Get next long word from the stream. */
uint32_t stream_get_ipv4(struct stream *s)
{
	uint32_t l;

	STREAM_VERIFY_SANE(s);

	if (STREAM_READABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN(s, "get ipv4");
		return 0;
	}

	memcpy(&l, s->data + s->getp, sizeof(uint32_t));
	s->getp += sizeof(uint32_t);

	return l;
}

bool stream_get_ipaddr(struct stream *s, struct ipaddr *ip)
{
	uint16_t ipa_len;

	STREAM_VERIFY_SANE(s);

	/* Get address type. */
	if (STREAM_READABLE(s) < sizeof(uint16_t)) {
		STREAM_BOUND_WARN2(s, "get ipaddr");
		return false;
	}
	ip->ipa_type = stream_getw(s);

	/* Get address value. */
	switch (ip->ipa_type) {
	case IPADDR_V4:
		ipa_len = IPV4_MAX_BYTELEN;
		break;
	case IPADDR_V6:
		ipa_len = IPV6_MAX_BYTELEN;
		break;
	default:
		flog_err(EC_LIB_DEVELOPMENT,
			 "%s: unknown ip address-family: %u", __func__,
			 ip->ipa_type);
		return false;
	}
	if (STREAM_READABLE(s) < ipa_len) {
		STREAM_BOUND_WARN2(s, "get ipaddr");
		return false;
	}
	memcpy(&ip->ip, s->data + s->getp, ipa_len);
	s->getp += ipa_len;

	return true;
}

float stream_getf(struct stream *s)
{
	union {
		float r;
		uint32_t d;
	} u;
	u.d = stream_getl(s);
	return u.r;
}

double stream_getd(struct stream *s)
{
	union {
		double r;
		uint64_t d;
	} u;
	u.d = stream_getq(s);
	return u.r;
}

/* Copy to source to stream.
 *
 * XXX: This uses CHECK_SIZE and hence has funny semantics -> Size will wrap
 * around. This should be fixed once the stream updates are working.
 *
 * stream_write() is saner
 */
void stream_put(struct stream *s, const void *src, size_t size)
{

	/* XXX: CHECK_SIZE has strange semantics. It should be deprecated */
	CHECK_SIZE(s, size);

	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		return;
	}

	if (src)
		memcpy(s->data + s->endp, src, size);
	else
		memset(s->data + s->endp, 0, size);

	s->endp += size;
}

/* Put character to the stream. */
int stream_putc(struct stream *s, uint8_t c)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint8_t)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[s->endp++] = c;
	return sizeof(uint8_t);
}

/* Put word to the stream. */
int stream_putw(struct stream *s, uint16_t w)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint16_t)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[s->endp++] = (uint8_t)(w >> 8);
	s->data[s->endp++] = (uint8_t)w;

	return 2;
}

/* Put long word to the stream. */
int stream_put3(struct stream *s, uint32_t l)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < 3) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[s->endp++] = (uint8_t)(l >> 16);
	s->data[s->endp++] = (uint8_t)(l >> 8);
	s->data[s->endp++] = (uint8_t)l;

	return 3;
}

/* Put long word to the stream. */
int stream_putl(struct stream *s, uint32_t l)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[s->endp++] = (uint8_t)(l >> 24);
	s->data[s->endp++] = (uint8_t)(l >> 16);
	s->data[s->endp++] = (uint8_t)(l >> 8);
	s->data[s->endp++] = (uint8_t)l;

	return 4;
}

/* Put quad word to the stream. */
int stream_putq(struct stream *s, uint64_t q)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint64_t)) {
		STREAM_BOUND_WARN(s, "put quad");
		return 0;
	}

	s->data[s->endp++] = (uint8_t)(q >> 56);
	s->data[s->endp++] = (uint8_t)(q >> 48);
	s->data[s->endp++] = (uint8_t)(q >> 40);
	s->data[s->endp++] = (uint8_t)(q >> 32);
	s->data[s->endp++] = (uint8_t)(q >> 24);
	s->data[s->endp++] = (uint8_t)(q >> 16);
	s->data[s->endp++] = (uint8_t)(q >> 8);
	s->data[s->endp++] = (uint8_t)q;

	return 8;
}

int stream_putf(struct stream *s, float f)
{
	union {
		float i;
		uint32_t o;
	} u;
	u.i = f;
	return stream_putl(s, u.o);
}

int stream_putd(struct stream *s, double d)
{
	union {
		double i;
		uint64_t o;
	} u;
	u.i = d;
	return stream_putq(s, u.o);
}

int stream_putc_at(struct stream *s, size_t putp, uint8_t c)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + sizeof(uint8_t))) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[putp] = c;

	return 1;
}

int stream_putw_at(struct stream *s, size_t putp, uint16_t w)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + sizeof(uint16_t))) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	s->data[putp] = (uint8_t)(w >> 8);
	s->data[putp + 1] = (uint8_t)w;

	return 2;
}

int stream_put3_at(struct stream *s, size_t putp, uint32_t l)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + 3)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}
	s->data[putp] = (uint8_t)(l >> 16);
	s->data[putp + 1] = (uint8_t)(l >> 8);
	s->data[putp + 2] = (uint8_t)l;

	return 3;
}

int stream_putl_at(struct stream *s, size_t putp, uint32_t l)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + sizeof(uint32_t))) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}
	s->data[putp] = (uint8_t)(l >> 24);
	s->data[putp + 1] = (uint8_t)(l >> 16);
	s->data[putp + 2] = (uint8_t)(l >> 8);
	s->data[putp + 3] = (uint8_t)l;

	return 4;
}

int stream_putq_at(struct stream *s, size_t putp, uint64_t q)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + sizeof(uint64_t))) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}
	s->data[putp] = (uint8_t)(q >> 56);
	s->data[putp + 1] = (uint8_t)(q >> 48);
	s->data[putp + 2] = (uint8_t)(q >> 40);
	s->data[putp + 3] = (uint8_t)(q >> 32);
	s->data[putp + 4] = (uint8_t)(q >> 24);
	s->data[putp + 5] = (uint8_t)(q >> 16);
	s->data[putp + 6] = (uint8_t)(q >> 8);
	s->data[putp + 7] = (uint8_t)q;

	return 8;
}

/* Put long word to the stream. */
int stream_put_ipv4(struct stream *s, uint32_t l)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}
	memcpy(s->data + s->endp, &l, sizeof(uint32_t));
	s->endp += sizeof(uint32_t);

	return sizeof(uint32_t);
}

/* Put long word to the stream. */
int stream_put_in_addr(struct stream *s, const struct in_addr *addr)
{
	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	memcpy(s->data + s->endp, addr, sizeof(uint32_t));
	s->endp += sizeof(uint32_t);

	return sizeof(uint32_t);
}

bool stream_put_ipaddr(struct stream *s, struct ipaddr *ip)
{
	stream_putw(s, ip->ipa_type);

	switch (ip->ipa_type) {
	case IPADDR_V4:
		stream_put_in_addr(s, &ip->ipaddr_v4);
		break;
	case IPADDR_V6:
		stream_write(s, (uint8_t *)&ip->ipaddr_v6, 16);
		break;
	default:
		flog_err(EC_LIB_DEVELOPMENT,
			 "%s: unknown ip address-family: %u", __func__,
			 ip->ipa_type);
		return false;
	}

	return true;
}

/* Put in_addr at location in the stream. */
int stream_put_in_addr_at(struct stream *s, size_t putp,
			  const struct in_addr *addr)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + 4)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	memcpy(&s->data[putp], addr, 4);
	return 4;
}

/* Put in6_addr at location in the stream. */
int stream_put_in6_addr_at(struct stream *s, size_t putp,
			   const struct in6_addr *addr)
{
	STREAM_VERIFY_SANE(s);

	if (!PUT_AT_VALID(s, putp + 16)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	memcpy(&s->data[putp], addr, 16);
	return 16;
}

/* Put prefix by nlri type format. */
int stream_put_prefix_addpath(struct stream *s, const struct prefix *p,
			      int addpath_encode, uint32_t addpath_tx_id)
{
	size_t psize;
	size_t psize_with_addpath;

	STREAM_VERIFY_SANE(s);

	psize = PSIZE(p->prefixlen);

	if (addpath_encode)
		psize_with_addpath = psize + 4;
	else
		psize_with_addpath = psize;

	if (STREAM_WRITEABLE(s) < (psize_with_addpath + sizeof(uint8_t))) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	if (addpath_encode) {
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24);
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16);
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8);
		s->data[s->endp++] = (uint8_t)addpath_tx_id;
	}

	s->data[s->endp++] = p->prefixlen;
	memcpy(s->data + s->endp, &p->u.prefix, psize);
	s->endp += psize;

	return psize;
}

int stream_put_prefix(struct stream *s, const struct prefix *p)
{
	return stream_put_prefix_addpath(s, p, 0, 0);
}

/* Put NLRI with label */
int stream_put_labeled_prefix(struct stream *s, const struct prefix *p,
			      mpls_label_t *label, int addpath_encode,
			      uint32_t addpath_tx_id)
{
	size_t psize;
	size_t psize_with_addpath;
	uint8_t *label_pnt = (uint8_t *)label;

	STREAM_VERIFY_SANE(s);

	psize = PSIZE(p->prefixlen);
	psize_with_addpath = psize + (addpath_encode ? 4 : 0);

	if (STREAM_WRITEABLE(s) < (psize_with_addpath + 3)) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	if (addpath_encode) {
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24);
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16);
		s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8);
		s->data[s->endp++] = (uint8_t)addpath_tx_id;
	}

	stream_putc(s, (p->prefixlen + 24));
	stream_putc(s, label_pnt[0]);
	stream_putc(s, label_pnt[1]);
	stream_putc(s, label_pnt[2]);
	memcpy(s->data + s->endp, &p->u.prefix, psize);
	s->endp += psize;

	return (psize + 3);
}

/* Read size from fd. */
int stream_read(struct stream *s, int fd, size_t size)
{
	int nbytes;

	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	nbytes = readn(fd, s->data + s->endp, size);

	if (nbytes > 0)
		s->endp += nbytes;

	return nbytes;
}

ssize_t stream_read_try(struct stream *s, int fd, size_t size)
{
	ssize_t nbytes;

	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		/* Fatal (not transient) error, since retrying will not help
		   (stream is too small to contain the desired data). */
		return -1;
	}

	if ((nbytes = read(fd, s->data + s->endp, size)) >= 0) {
		s->endp += nbytes;
		return nbytes;
	}
	/* Error: was it transient (return -2) or fatal (return -1)? */
	if (ERRNO_IO_RETRY(errno))
		return -2;
	flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd,
		 safe_strerror(errno));
	return -1;
}

/* Read up to size bytes into the stream from the fd, using recvmsgfrom
 * whose arguments match the remaining arguments to this function
 */
ssize_t stream_recvfrom(struct stream *s, int fd, size_t size, int flags,
			struct sockaddr *from, socklen_t *fromlen)
{
	ssize_t nbytes;

	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		/* Fatal (not transient) error, since retrying will not help
		   (stream is too small to contain the desired data). */
		return -1;
	}

	if ((nbytes = recvfrom(fd, s->data + s->endp, size, flags, from,
			       fromlen))
	    >= 0) {
		s->endp += nbytes;
		return nbytes;
	}
	/* Error: was it transient (return -2) or fatal (return -1)? */
	if (ERRNO_IO_RETRY(errno))
		return -2;
	flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd,
		 safe_strerror(errno));
	return -1;
}

/* Read up to smaller of size or SIZE_REMAIN() bytes to the stream, starting
 * from endp.
 * First iovec will be used to receive the data.
 * Stream need not be empty.
 */
ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *msgh, int flags,
		       size_t size)
{
	int nbytes;
	struct iovec *iov;

	STREAM_VERIFY_SANE(s);
	assert(msgh->msg_iovlen > 0);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		/* This is a logic error in the calling code: the stream is too
		   small
		   to hold the desired data! */
		return -1;
	}

	iov = &(msgh->msg_iov[0]);
	iov->iov_base = (s->data + s->endp);
	iov->iov_len = size;

	nbytes = recvmsg(fd, msgh, flags);

	if (nbytes > 0)
		s->endp += nbytes;

	return nbytes;
}

/* Write data to buffer. */
size_t stream_write(struct stream *s, const void *ptr, size_t size)
{

	CHECK_SIZE(s, size);

	STREAM_VERIFY_SANE(s);

	if (STREAM_WRITEABLE(s) < size) {
		STREAM_BOUND_WARN(s, "put");
		return 0;
	}

	memcpy(s->data + s->endp, ptr, size);
	s->endp += size;

	return size;
}

/* Return current read pointer.
 * DEPRECATED!
 * Use stream_get_pnt_to if you must, but decoding streams properly
 * is preferred
 */
uint8_t *stream_pnt(struct stream *s)
{
	STREAM_VERIFY_SANE(s);
	return s->data + s->getp;
}

/* Check does this stream empty? */
int stream_empty(struct stream *s)
{
	STREAM_VERIFY_SANE(s);

	return (s->endp == 0);
}

/* Reset stream. */
void stream_reset(struct stream *s)
{
	STREAM_VERIFY_SANE(s);

	s->getp = s->endp = 0;
}

/* Write stream contens to the file discriptor. */
int stream_flush(struct stream *s, int fd)
{
	int nbytes;

	STREAM_VERIFY_SANE(s);

	nbytes = write(fd, s->data + s->getp, s->endp - s->getp);

	return nbytes;
}

void stream_hexdump(const struct stream *s)
{
	zlog_hexdump(s->data, s->endp);
}

/* Stream first in first out queue. */

struct stream_fifo *stream_fifo_new(void)
{
	struct stream_fifo *new;

	new = XMALLOC(MTYPE_STREAM_FIFO, sizeof(struct stream_fifo));
	stream_fifo_init(new);
	return new;
}

void stream_fifo_init(struct stream_fifo *fifo)
{
	memset(fifo, 0, sizeof(struct stream_fifo));
	pthread_mutex_init(&fifo->mtx, NULL);
}

/* Add new stream to fifo. */
void stream_fifo_push(struct stream_fifo *fifo, struct stream *s)
{
#if defined DEV_BUILD
	size_t max, curmax;
#endif

	if (fifo->tail)
		fifo->tail->next = s;
	else
		fifo->head = s;

	fifo->tail = s;
	fifo->tail->next = NULL;
#if !defined DEV_BUILD
	atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release);
#else
	max = atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release);
	curmax = atomic_load_explicit(&fifo->max_count, memory_order_relaxed);
	if (max > curmax)
		atomic_store_explicit(&fifo->max_count, max,
				      memory_order_relaxed);
#endif
}

void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s)
{
	frr_with_mutex(&fifo->mtx) {
		stream_fifo_push(fifo, s);
	}
}

/* Delete first stream from fifo. */
struct stream *stream_fifo_pop(struct stream_fifo *fifo)
{
	struct stream *s;

	s = fifo->head;

	if (s) {
		fifo->head = s->next;

		if (fifo->head == NULL)
			fifo->tail = NULL;

		atomic_fetch_sub_explicit(&fifo->count, 1,
					  memory_order_release);

		/* ensure stream is scrubbed of references to this fifo */
		s->next = NULL;
	}

	return s;
}

struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo)
{
	struct stream *ret;

	frr_with_mutex(&fifo->mtx) {
		ret = stream_fifo_pop(fifo);
	}

	return ret;
}

struct stream *stream_fifo_head(struct stream_fifo *fifo)
{
	return fifo->head;
}

struct stream *stream_fifo_head_safe(struct stream_fifo *fifo)
{
	struct stream *ret;

	frr_with_mutex(&fifo->mtx) {
		ret = stream_fifo_head(fifo);
	}

	return ret;
}

void stream_fifo_clean(struct stream_fifo *fifo)
{
	struct stream *s;
	struct stream *next;

	for (s = fifo->head; s; s = next) {
		next = s->next;
		stream_free(s);
	}
	fifo->head = fifo->tail = NULL;
	atomic_store_explicit(&fifo->count, 0, memory_order_release);
}

void stream_fifo_clean_safe(struct stream_fifo *fifo)
{
	frr_with_mutex(&fifo->mtx) {
		stream_fifo_clean(fifo);
	}
}

size_t stream_fifo_count_safe(struct stream_fifo *fifo)
{
	return atomic_load_explicit(&fifo->count, memory_order_acquire);
}

void stream_fifo_deinit(struct stream_fifo *fifo)
{
	stream_fifo_clean(fifo);
	pthread_mutex_destroy(&fifo->mtx);
}

void stream_fifo_free(struct stream_fifo *fifo)
{
	stream_fifo_deinit(fifo);
	XFREE(MTYPE_STREAM_FIFO, fifo);
}
