/*++
/* NAME
/*	memcache_proto 3
/* SUMMARY
/*	memcache low-level protocol
/* SYNOPSIS
/*	#include <memcache_proto.h>
/*
/*	int	memcache_get(fp, buf, len)
/*	VSTREAM	*fp;
/*	VSTRING	*buf;
/*	ssize_t	len;
/*
/*	int	memcache_printf(fp, format, ...)
/*	VSTREAM	*fp;
/*	const char *format;
/*
/*	int	memcache_vprintf(fp, format, ap)
/*	VSTREAM	*fp;
/*	const char *format;
/*	va_list	ap;
/*
/*	int	memcache_fread(fp, buf, len)
/*	VSTREAM	*fp;
/*	VSTRING	*buf;
/*	ssize_t	len;
/*
/*	int	memcache_fwrite(fp, buf, len)
/*	VSTREAM	*fp;
/*	const char *buf;
/*	ssize_t	len;
/* DESCRIPTION
/*	This module implements the low-level memcache protocol.
/*	All functions return -1 on error and 0 on success.
/* SEE ALSO
/*	smtp_proto(3) SMTP low-level protocol.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

#include <sys_defs.h>

/* Utility library. */

#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <vstring_vstream.h>
#include <compat_va_copy.h>

/* Application-specific. */

#include <memcache_proto.h>

#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)

/* memcache_get - read one line from peer */

int     memcache_get(VSTREAM *stream, VSTRING *vp, ssize_t bound)
{
    int     last_char;
    int     next_char;

    last_char = (bound == 0 ? vstring_get(vp, stream) :
		 vstring_get_bound(vp, stream, bound));

    switch (last_char) {

	/*
	 * Do some repair in the rare case that we stopped reading in the
	 * middle of the CRLF record terminator.
	 */
    case '\r':
	if ((next_char = VSTREAM_GETC(stream)) == '\n') {
	    VSTRING_ADDCH(vp, '\n');
	    /* FALLTRHOUGH */
	} else {
	    if (next_char != VSTREAM_EOF)
		vstream_ungetc(stream, next_char);

	    /*
	     * Input too long, or EOF
	     */
    default:
	    if (msg_verbose)
		msg_info("%s got %s", VSTREAM_PATH(stream),
			 LEN(vp) < bound ? "EOF" : "input too long");
	    return (-1);
	}

	/*
	 * Strip off the record terminator: either CRLF or just bare LF.
	 */
    case '\n':
	vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	if (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
	    vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	VSTRING_TERMINATE(vp);
	if (msg_verbose)
	    msg_info("%s got: %s", VSTREAM_PATH(stream), STR(vp));
	return (0);
    }
}

/* memcache_fwrite - write one blob to peer */

int     memcache_fwrite(VSTREAM *stream, const char *cp, ssize_t todo)
{

    /*
     * Sanity check.
     */
    if (todo < 0)
	msg_panic("memcache_fwrite: negative todo %ld", (long) todo);

    /*
     * Do the I/O.
     */
    if (msg_verbose)
	msg_info("%s write: %.*s", VSTREAM_PATH(stream), (int) todo, cp);
    if (vstream_fwrite(stream, cp, todo) != todo
	|| vstream_fputs("\r\n", stream) == VSTREAM_EOF)
	return (-1);
    else
	return (0);
}

/* memcache_fread - read one blob from peer */

int     memcache_fread(VSTREAM *stream, VSTRING *buf, ssize_t todo)
{

    /*
     * Sanity check.
     */
    if (todo < 0)
	msg_panic("memcache_fread: negative todo %ld", (long) todo);

    /*
     * Do the I/O.
     */
    if (vstream_fread_buf(stream, buf, todo) != todo
	|| VSTREAM_GETC(stream) != '\r'
	|| VSTREAM_GETC(stream) != '\n') {
	if (msg_verbose)
	    msg_info("%s read: error", VSTREAM_PATH(stream));
	return (-1);
    } else {
	VSTRING_TERMINATE(buf);
	if (msg_verbose)
	    msg_info("%s read: %s", VSTREAM_PATH(stream), STR(buf));
	return (0);
    }
}

/* memcache_vprintf - write one line to peer */

int     memcache_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
{

    /*
     * Do the I/O.
     */
    vstream_vfprintf(stream, fmt, ap);
    vstream_fputs("\r\n", stream);
    if (vstream_ferror(stream))
	return (-1);
    else
	return (0);
}

/* memcache_printf - write one line to peer */

int     memcache_printf(VSTREAM *stream, const char *fmt,...)
{
    va_list ap;
    int     ret;

    va_start(ap, fmt);

    if (msg_verbose) {
	VSTRING *buf = vstring_alloc(100);
	va_list ap2;

	VA_COPY(ap2, ap);
	vstring_vsprintf(buf, fmt, ap2);
	va_end(ap2);
	msg_info("%s write: %s", VSTREAM_PATH(stream), STR(buf));
	vstring_free(buf);
    }

    /*
     * Do the I/O.
     */
    ret = memcache_vprintf(stream, fmt, ap);
    va_end(ap);
    return (ret);
}
