/*	$OpenBSD$	*/
/*
 * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "abuf.h"
#include "defs.h"
#include "dev.h"
#include "file.h"
#include "midi.h"
#include "opt.h"
#include "sock.h"
#include "utils.h"
#include "bsd-compat.h"

#define SOCK_CTLDESC_SIZE	0x800	/* size of s->ctldesc */

void sock_log(struct sock *);
void sock_close(struct sock *);
void sock_slot_fill(void *);
void sock_slot_flush(void *);
void sock_slot_eof(void *);
void sock_slot_onmove(void *);
void sock_slot_onvol(void *);
void sock_midi_imsg(void *, unsigned char *, int);
void sock_midi_omsg(void *, unsigned char *, int);
void sock_midi_fill(void *, int);
void sock_ctl_sync(void *);
struct sock *sock_new(int);
void sock_exit(void *);
int sock_fdwrite(struct sock *, void *, int);
int sock_fdread(struct sock *, void *, int);
int sock_rmsg(struct sock *);
int sock_wmsg(struct sock *);
int sock_rdata(struct sock *);
int sock_wdata(struct sock *);
int sock_setpar(struct sock *);
int sock_auth(struct sock *);
int sock_hello(struct sock *);
int sock_execmsg(struct sock *);
int sock_buildmsg(struct sock *);
int sock_read(struct sock *);
int sock_write(struct sock *);
int sock_pollfd(void *, struct pollfd *);
int sock_revents(void *, struct pollfd *);
void sock_in(void *);
void sock_out(void *);
void sock_hup(void *);

struct fileops sock_fileops = {
	"sock",
	sock_pollfd,
	sock_revents,
	sock_in,
	sock_out,
	sock_hup
};

struct slotops sock_slotops = {
	sock_slot_onmove,
	sock_slot_onvol,
	sock_slot_fill,
	sock_slot_flush,
	sock_slot_eof,
	sock_exit
};

struct midiops sock_midiops = {
	sock_midi_imsg,
	sock_midi_omsg,
	sock_midi_fill,
	sock_exit
};

struct ctlops sock_ctlops = {
	sock_exit,
	sock_ctl_sync
};

struct sock *sock_list = NULL;
unsigned int sock_sesrefs = 0;		/* connections to the session */
uint8_t sock_sescookie[AMSG_COOKIELEN];	/* owner of the session */

/*
 * Old clients used to send dev number and opt name. This routine
 * finds proper opt pointer for the given device.
 */
static struct opt *
legacy_opt(int devnum, char *optname)
{
	struct dev *d;
	struct opt *o;

	d = dev_bynum(devnum);
	if (d == NULL)
		return NULL;
	if (strcmp(optname, "default") == 0) {
		for (o = opt_list; o != NULL; o = o->next) {
			if (strcmp(o->name, d->name) == 0)
				return o;
		}
		return NULL;
	} else {
		o = opt_byname(optname);
		return (o != NULL && o->dev == d) ? o : NULL;
	}
}

/*
 * If control slot is associated to a particular opt, then
 * remove the unused group part of the control name to make mixer
 * look nicer
 */
static char *
ctlgroup(struct sock *f, struct ctl *c)
{
	if (f->ctlslot->opt == NULL)
		return c->group;
	if (strcmp(c->group, f->ctlslot->opt->name) == 0)
		return "";
	if (strcmp(c->group, f->ctlslot->opt->dev->name) == 0)
		return "";
	return c->group;
}

void
sock_log(struct sock *f)
{
#ifdef DEBUG
	static char *rstates[] = { "ridl", "rmsg", "rdat", "rret" };
	static char *wstates[] = { "widl", "wmsg", "wdat" };
#endif
	if (f->slot)
		slot_log(f->slot);
	else if (f->midi)
		midi_log(f->midi);
	else if (f->ctlslot) {
		log_puts("ctlslot");
		log_putu(f->ctlslot - ctlslot_array);
	} else
		log_puts("sock");
#ifdef DEBUG
	if (log_level >= 3) {
		log_puts(",");
		log_puts(rstates[f->rstate]);
		log_puts(",");
		log_puts(wstates[f->wstate]);
	}
#endif
}

void
sock_close(struct sock *f)
{
	struct opt *o;
	struct sock **pf;
	unsigned int tags, i;

	for (pf = &sock_list; *pf != f; pf = &(*pf)->next) {
#ifdef DEBUG
		if (*pf == NULL) {
			log_puts("sock_close: not on list\n");
			panic();
		}
#endif
	}
	*pf = f->next;

#ifdef DEBUG
	if (log_level >= 3) {
		sock_log(f);
		log_puts(": closing\n");
	}
#endif
	if (f->pstate > SOCK_AUTH)
		sock_sesrefs -= f->sesrefs;
	if (f->slot) {
		slot_del(f->slot);
		f->slot = NULL;
	}
	if (f->midi) {
		tags = midi_tags(f->midi);
		for (i = 0; i < DEV_NMAX; i++) {
			if ((tags & (1 << i)) && (o = opt_bynum(i)) != NULL)
				opt_unref(o);
		}
		midi_del(f->midi);
		f->midi = NULL;
	}
	if (f->port) {
		port_unref(f->port);
		f->port = NULL;
	}
	if (f->ctlslot) {
		ctlslot_del(f->ctlslot);
		f->ctlslot = NULL;
		xfree(f->ctldesc);
	}
	file_del(f->file);
	close(f->fd);
	file_slowaccept = 0;
	xfree(f);
}

void
sock_slot_fill(void *arg)
{
	struct sock *f = arg;
	struct slot *s = f->slot;

	f->fillpending += s->round;
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": fill, rmax -> ");
		log_puti(f->rmax);
		log_puts(", pending -> ");
		log_puti(f->fillpending);
		log_puts("\n");
	}
#endif
}

void
sock_slot_flush(void *arg)
{
	struct sock *f = arg;
	struct slot *s = f->slot;

	f->wmax += s->round * s->sub.bpf;
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": flush, wmax -> ");
		log_puti(f->wmax);
		log_puts("\n");
	}
#endif
}

void
sock_slot_eof(void *arg)
{
	struct sock *f = arg;

#ifdef DEBUG
	if (log_level >= 3) {
		sock_log(f);
		log_puts(": stopped\n");
	}
#endif
	f->stoppending = 1;
}

void
sock_slot_onmove(void *arg)
{
	struct sock *f = (struct sock *)arg;
	struct slot *s = f->slot;

#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": onmove: delta -> ");
		log_puti(s->delta);
		log_puts("\n");
	}
#endif
	if (s->pstate != SOCK_START)
		return;
	f->tickpending++;
}

void
sock_slot_onvol(void *arg)
{
	struct sock *f = (struct sock *)arg;
	struct slot *s = f->slot;

#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": onvol: vol -> ");
		log_puti(s->vol);
		log_puts("\n");
	}
#endif
	if (s->pstate != SOCK_START)
		return;
}

void
sock_midi_imsg(void *arg, unsigned char *msg, int size)
{
	struct sock *f = arg;

	midi_send(f->midi, msg, size);
}

void
sock_midi_omsg(void *arg, unsigned char *msg, int size)
{
	struct sock *f = arg;

	midi_out(f->midi, msg, size);
}

void
sock_midi_fill(void *arg, int count)
{
	struct sock *f = arg;

	f->fillpending += count;
}

void
sock_ctl_sync(void *arg)
{
	struct sock *f = arg;

	if (f->ctlops & SOCK_CTLDESC)
		f->ctlsyncpending = 1;
}

struct sock *
sock_new(int fd)
{
	struct sock *f;

	f = xmalloc(sizeof(struct sock));
	f->pstate = SOCK_AUTH;
	f->slot = NULL;
	f->port = NULL;
	f->midi = NULL;
	f->ctlslot = NULL;
	f->tickpending = 0;
	f->fillpending = 0;
	f->stoppending = 0;
	f->wstate = SOCK_WIDLE;
	f->wtodo = 0xdeadbeef;
	f->rstate = SOCK_RMSG;
	f->rtodo = sizeof(struct amsg);
	f->wmax = f->rmax = 0;
	f->lastvol = -1;
	f->ctlops = 0;
	f->ctlsyncpending = 0;
	f->file = file_new(&sock_fileops, f, "sock", 1);
	f->fd = fd;
	if (f->file == NULL) {
		xfree(f);
		return NULL;
	}
	f->next = sock_list;
	sock_list = f;
	return f;
}

void
sock_exit(void *arg)
{
	struct sock *f = (struct sock *)arg;

#ifdef DEBUG
	if (log_level >= 3) {
		sock_log(f);
		log_puts(": exit\n");
	}
#endif
	sock_close(f);
}

/*
 * write on the socket fd and handle errors
 */
int
sock_fdwrite(struct sock *f, void *data, int count)
{
	int n;

	n = write(f->fd, data, count);
	if (n == -1) {
#ifdef DEBUG
		if (errno == EFAULT) {
			log_puts("sock_fdwrite: fault\n");
			panic();
		}
#endif
		if (errno != EAGAIN) {
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": write filed, errno = ");
				log_puti(errno);
				log_puts("\n");
			}
			sock_close(f);
		} else {
#ifdef DEBUG
			if (log_level >= 4) {
				sock_log(f);
				log_puts(": write blocked\n");
			}
#endif
		}
		return 0;
	}
	if (n == 0) {
		sock_close(f);
		return 0;
	}
	return n;
}

/*
 * read from the socket fd and handle errors
 */
int
sock_fdread(struct sock *f, void *data, int count)
{
	int n;

	n = read(f->fd, data, count);
	if (n == -1) {
#ifdef DEBUG
		if (errno == EFAULT) {
			log_puts("sock_fdread: fault\n");
			panic();
		}
#endif
		if (errno != EAGAIN) {
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": read failed, errno = ");
				log_puti(errno);
				log_puts("\n");
			}
			sock_close(f);
		} else {
#ifdef DEBUG
			if (log_level >= 4) {
				sock_log(f);
				log_puts(": read blocked\n");
			}
#endif
		}
		return 0;
	}
	if (n == 0) {
		sock_close(f);
		return 0;
	}
	return n;
}

/*
 * read the next message into f->rmsg, return 1 on success
 */
int
sock_rmsg(struct sock *f)
{
	int n;
	char *data;

#ifdef DEBUG
	if (f->rtodo == 0) {
		sock_log(f);
		log_puts(": sock_rmsg: nothing to read\n");
		panic();
	}
#endif
	data = (char *)&f->rmsg + sizeof(struct amsg) - f->rtodo;
	n = sock_fdread(f, data, f->rtodo);
	if (n == 0)
		return 0;
	if (n < f->rtodo) {
		f->rtodo -= n;
		return 0;
	}
	f->rtodo = 0;
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": read full message\n");
	}
#endif
	return 1;
}

/*
 * write the message in f->rmsg, return 1 on success
 */
int
sock_wmsg(struct sock *f)
{
	int n;
	char *data;

#ifdef DEBUG
	if (f->wtodo == 0) {
		sock_log(f);
		log_puts(": sock_wmsg: already written\n");
	}
#endif
	data = (char *)&f->wmsg + sizeof(struct amsg) - f->wtodo;
	n = sock_fdwrite(f, data, f->wtodo);
	if (n == 0)
		return 0;
	if (n < f->wtodo) {
		f->wtodo -= n;
		return 0;
	}
	f->wtodo = 0;
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": wrote full message\n");
	}
#endif
	return 1;
}

/*
 * read data into the slot/midi ring buffer
 */
int
sock_rdata(struct sock *f)
{
	unsigned char midibuf[MIDI_BUFSZ];
	unsigned char *data;
	int n, count;

#ifdef DEBUG
	if (f->rtodo == 0) {
		sock_log(f);
		log_puts(": data block already read\n");
		panic();
	}
#endif
	while (f->rtodo > 0) {
		if (f->slot)
			data = abuf_wgetblk(&f->slot->mix.buf, &count);
		else {
			data = midibuf;
			count = MIDI_BUFSZ;
		}
		if (count > f->rtodo)
			count = f->rtodo;
		n = sock_fdread(f, data, count);
		if (n == 0)
			return 0;
		f->rtodo -= n;
		if (f->slot)
			abuf_wcommit(&f->slot->mix.buf, n);
		else
			midi_in(f->midi, midibuf, n);
	}
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": read complete block\n");
	}
#endif
	if (f->slot)
		slot_write(f->slot);
	return 1;
}

/*
 * write data to the slot/midi ring buffer
 */
int
sock_wdata(struct sock *f)
{
	static unsigned char dummy[AMSG_DATAMAX];
	unsigned char *data = NULL;
	int n, count;

#ifdef DEBUG
	if (f->wtodo == 0) {
		sock_log(f);
		log_puts(": attempted to write zero-sized data block\n");
		panic();
	}
#endif
	if (f->pstate == SOCK_STOP) {
		while (f->wtodo > 0) {
			n = sock_fdwrite(f, dummy, f->wtodo);
			if (n == 0)
				return 0;
			f->wtodo -= n;
		}
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": zero-filled remaining block\n");
		}
#endif
		return 1;
	}
	while (f->wtodo > 0) {
		/*
		 * f->slot and f->midi are set by sock_hello(), so
		 * count is always properly initialized
		 */
		if (f->slot)
			data = abuf_rgetblk(&f->slot->sub.buf, &count);
		else if (f->midi)
			data = abuf_rgetblk(&f->midi->obuf, &count);
		else {
			data = f->ctldesc + (f->wsize - f->wtodo);
			count = f->wtodo;
		}
		if (count > f->wtodo)
			count = f->wtodo;
		n = sock_fdwrite(f, data, count);
		if (n == 0)
			return 0;
		f->wtodo -= n;
		if (f->slot)
			abuf_rdiscard(&f->slot->sub.buf, n);
		else if (f->midi)
			abuf_rdiscard(&f->midi->obuf, n);
	}
	if (f->slot)
		slot_read(f->slot);
	if (f->midi)
		midi_fill(f->midi);
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": wrote complete block\n");
	}
#endif
	return 1;
}

int
sock_setpar(struct sock *f)
{
	struct slot *s = f->slot;
	struct dev *d = s->opt->dev;
	struct amsg_par *p = &f->rmsg.u.par;
	unsigned int min, max;
	uint32_t rate, appbufsz;
	uint16_t pchan, rchan;

	rchan = ntohs(p->rchan);
	pchan = ntohs(p->pchan);
	appbufsz = ntohl(p->appbufsz);
	rate = ntohl(p->rate);

	if (AMSG_ISSET(p->bits)) {
		if (p->bits < BITS_MIN || p->bits > BITS_MAX) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": ");
				log_putu(p->bits);
				log_puts(": bits out of bounds\n");
			}
#endif
			return 0;
		}
		if (AMSG_ISSET(p->bps)) {
			if (p->bps < ((p->bits + 7) / 8) || p->bps > 4) {
#ifdef DEBUG
				if (log_level >= 1) {
					sock_log(f);
					log_puts(": ");
					log_putu(p->bps);
					log_puts(": wrong bytes per sample\n");
				}
#endif
				return 0;
			}
		} else
			p->bps = APARAMS_BPS(p->bits);
		s->par.bits = p->bits;
		s->par.bps = p->bps;
	}
	if (AMSG_ISSET(p->sig))
		s->par.sig = p->sig ? 1 : 0;
	if (AMSG_ISSET(p->le))
		s->par.le = p->le ? 1 : 0;
	if (AMSG_ISSET(p->msb))
		s->par.msb = p->msb ? 1 : 0;
	if (AMSG_ISSET(rchan) && (s->mode & MODE_RECMASK)) {
		if (rchan < 1)
			rchan = 1;
		else if (rchan > NCHAN_MAX)
			rchan = NCHAN_MAX;
		s->sub.nch = rchan;
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": recording channels ");
			log_putu(s->opt->rmin);
			log_puts(":");
			log_putu(s->opt->rmax);
			log_puts(" -> ");
			log_putu(s->opt->rmin);
			log_puts(":");
			log_putu(s->opt->rmin + s->sub.nch - 1);
			log_puts("\n");
		}
#endif
	}
	if (AMSG_ISSET(pchan) && (s->mode & MODE_PLAY)) {
		if (pchan < 1)
			pchan = 1;
		else if (pchan > NCHAN_MAX)
			pchan = NCHAN_MAX;
		s->mix.nch = pchan;
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": playback channels ");
			log_putu(s->opt->pmin);
			log_puts(":");
			log_putu(s->opt->pmin + s->mix.nch - 1);
			log_puts(" -> ");
			log_putu(s->opt->pmin);
			log_puts(":");
			log_putu(s->opt->pmax);
			log_puts("\n");
		}
#endif
	}
	if (AMSG_ISSET(rate)) {
		if (rate < RATE_MIN)
			rate = RATE_MIN;
		else if (rate > RATE_MAX)
			rate = RATE_MAX;
		s->round = dev_roundof(d, rate);
		s->rate = rate;
		if (!AMSG_ISSET(appbufsz)) {
			appbufsz = d->bufsz / d->round * s->round;
#ifdef DEBUG
			if (log_level >= 3) {
				sock_log(f);
				log_puts(": ");
				log_putu(appbufsz);
				log_puts(" frame buffer\n");
			}
#endif
		}
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": ");
			log_putu(rate);
			log_puts("Hz sample rate, ");
			log_putu(s->round);
			log_puts(" frame blocks\n");
		}
#endif
	}
	if (AMSG_ISSET(p->xrun)) {
		if (p->xrun != XRUN_IGNORE &&
		    p->xrun != XRUN_SYNC &&
		    p->xrun != XRUN_ERROR) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": ");
				log_putx(p->xrun);
				log_puts(": bad xrun policy\n");
			}
#endif
			return 0;
		}
		s->xrun = p->xrun;
		if (s->opt->mtc != NULL && s->xrun == XRUN_IGNORE)
			s->xrun = XRUN_SYNC;
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": 0x");
			log_putx(s->xrun);
			log_puts(" xrun policy\n");
		}
#endif
	}
	if (AMSG_ISSET(appbufsz)) {
		rate = s->rate;
		min = 1;
		max = 1 + rate / d->round;
		min *= s->round;
		max *= s->round;
		appbufsz += s->round / 2;
		appbufsz -= appbufsz % s->round;
		if (appbufsz < min)
			appbufsz = min;
		if (appbufsz > max)
			appbufsz = max;
		s->appbufsz = appbufsz;
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": ");
			log_putu(s->appbufsz);
			log_puts(" frame buffer\n");
		}
#endif
	}
	return 1;
}

int
sock_auth(struct sock *f)
{
	struct amsg_auth *p = &f->rmsg.u.auth;
	uid_t euid;
	gid_t egid;

	/*
	 * root bypasses any authentication checks and has no session
	 */
	if (getpeereid(f->fd, &euid, &egid) == 0 && euid == 0) {
		f->pstate = SOCK_HELLO;
		f->sesrefs = 0;
		return 1;
	}

	if (sock_sesrefs == 0) {
		/* start a new session */
		memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN);
		f->sesrefs = 1;
	} else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) {
		/* another session is active, drop connection */
		return 0;
	}
	sock_sesrefs += f->sesrefs;
	f->pstate = SOCK_HELLO;
	return 1;
}

int
sock_hello(struct sock *f)
{
	struct amsg_hello *p = &f->rmsg.u.hello;
	struct port *c;
	struct opt *opt;
	unsigned int mode;
	unsigned int id;

	mode = ntohs(p->mode);
	id = ntohl(p->id);
#ifdef DEBUG
	if (log_level >= 3) {
		sock_log(f);
		log_puts(": hello from <");
		log_puts(p->who);
		log_puts(">, mode = ");
		log_putx(mode);
		log_puts(", ver ");
		log_putu(p->version);
		log_puts("\n");
	}
#endif
	if (p->version != AMSG_VERSION) {
		if (log_level >= 1) {
			sock_log(f);
			log_puts(": ");
			log_putu(p->version);
			log_puts(": unsupported protocol version\n");
		}
		return 0;
	}
	switch (mode) {
	case MODE_MIDIIN:
	case MODE_MIDIOUT:
	case MODE_MIDIOUT | MODE_MIDIIN:
	case MODE_REC:
	case MODE_PLAY:
	case MODE_PLAY | MODE_REC:
	case MODE_CTLREAD:
	case MODE_CTLWRITE:
	case MODE_CTLREAD | MODE_CTLWRITE:
		break;
	default:
#ifdef DEBUG
		if (log_level >= 1) {
			sock_log(f);
			log_puts(": ");
			log_putx(mode);
			log_puts(": unsupported mode\n");
		}
#endif
		return 0;
	}
	f->pstate = SOCK_INIT;
	f->port = NULL;
	if (mode & MODE_MIDIMASK) {
		f->slot = NULL;
		f->midi = midi_new(&sock_midiops, f, mode);
		if (f->midi == NULL)
			return 0;
		/* XXX: add 'devtype' to libsndio */
		if (p->devnum == AMSG_NODEV) {
			opt = opt_byname(p->opt);
			if (opt == NULL)
				return 0;
			if (!opt_ref(opt))
				return 0;
			midi_tag(f->midi, opt->num);
		} else if (p->devnum < 16) {
			opt = legacy_opt(p->devnum, p->opt);
			if (opt == NULL)
				return 0;
			if (!opt_ref(opt))
				return 0;
			midi_tag(f->midi, opt->num);
		} else if (p->devnum < 32) {
			midi_tag(f->midi, p->devnum);
		} else if (p->devnum < 48) {
			c = port_alt_ref(p->devnum - 32);
			if (c == NULL)
				return 0;
			f->port = c;
			midi_link(f->midi, c->midi);
		} else
			return 0;
		return 1;
	}
	if (mode & MODE_CTLMASK) {
		if (p->devnum == AMSG_NODEV) {
			opt = opt_byname(p->opt);
			if (opt == NULL)
				return 0;
		} else {
			opt = legacy_opt(p->devnum, p->opt);
			if (opt == NULL)
				return 0;
		}
		f->ctlslot = ctlslot_new(opt, &sock_ctlops, f);
		if (f->ctlslot == NULL) {
			if (log_level >= 2) {
				sock_log(f);
				log_puts(": couldn't get slot\n");
			}
			return 0;
		}
		f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE);
		f->ctlops = 0;
		f->ctlsyncpending = 0;
		return 1;
	}
	opt = (p->devnum == AMSG_NODEV) ?
	    opt_byname(p->opt) : legacy_opt(p->devnum, p->opt);
	if (opt == NULL)
		return 0;
	f->slot = slot_new(opt, id, p->who, &sock_slotops, f, mode);
	if (f->slot == NULL)
		return 0;
	f->midi = NULL;
	return 1;
}

/*
 * execute the message in f->rmsg, return 1 on success
 */
int
sock_execmsg(struct sock *f)
{
	struct ctl *c;
	struct slot *s = f->slot;
	struct amsg *m = &f->rmsg;
	unsigned char *data;
	unsigned int size, ctl;
	int cmd;

	cmd = ntohl(m->cmd);
	switch (cmd) {
	case AMSG_DATA:
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": DATA message\n");
		}
#endif
		if (s != NULL && f->pstate != SOCK_START) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": DATA, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if ((f->slot && !(f->slot->mode & MODE_PLAY)) ||
		    (f->midi && !(f->midi->mode & MODE_MIDIOUT))) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": DATA, input-only mode\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		size = ntohl(m->u.data.size);
		if (size == 0) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": zero size payload\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (s != NULL && size % s->mix.bpf != 0) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": not aligned to frame\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (s != NULL && size > f->ralign) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": size = ");
				log_puti(size);
				log_puts(": ralign = ");
				log_puti(f->ralign);
				log_puts(": not aligned to block\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->rstate = SOCK_RDATA;
		f->rsize = f->rtodo = size;
		if (s != NULL) {
			f->ralign -= size;
			if (f->ralign == 0)
				f->ralign = s->round * s->mix.bpf;
		}
		if (f->rtodo > f->rmax) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": unexpected data, size = ");
				log_putu(size);
				log_puts(", rmax = ");
				log_putu(f->rmax);
				log_puts("\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->rmax -= f->rtodo;
		if (f->rtodo == 0) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": zero-length data chunk\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		break;
	case AMSG_START:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": START message\n");
		}
#endif
		if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": START, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->tickpending = 0;
		f->stoppending = 0;
		slot_start(s);
		if (s->mode & MODE_PLAY) {
			f->fillpending = s->appbufsz;
			f->ralign = s->round * s->mix.bpf;
			f->rmax = 0;
		}
		if (s->mode & MODE_RECMASK) {
			f->walign = s->round * s->sub.bpf;
			f->wmax = 0;
		}
		f->pstate = SOCK_START;
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
		if (log_level >= 2) {
			slot_log(f->slot);
			log_puts(": ");
			log_putu(s->rate);
			log_puts("Hz, ");
			aparams_log(&s->par);
			if (s->mode & MODE_PLAY) {
				log_puts(", play ");
				log_puti(s->opt->pmin);
				log_puts(":");
				log_puti(s->opt->pmin + s->mix.nch - 1);
			}
			if (s->mode & MODE_RECMASK) {
				log_puts(", rec ");
				log_puti(s->opt->rmin);
				log_puts(":");
				log_puti(s->opt->rmin + s->sub.nch - 1);
			}
			log_puts(", ");
			log_putu(s->appbufsz / s->round);
			log_puts(" blocks of ");
			log_putu(s->round);
			log_puts(" frames\n");
		}
		break;
	case AMSG_STOP:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": STOP message\n");
		}
#endif
		if (f->pstate != SOCK_START) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": STOP, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->rmax = 0;
		if (!(s->mode & MODE_PLAY))
			f->stoppending = 1;
		f->pstate = SOCK_STOP;
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
		if (s->mode & MODE_PLAY) {
			if (f->ralign < s->round * s->mix.bpf) {
				data = abuf_wgetblk(&s->mix.buf, &size);
#ifdef DEBUG
				if (size < f->ralign) {
					sock_log(f);
					log_puts(": unaligned stop, size = ");
					log_putu(size);
					log_puts(", ralign = ");
					log_putu(f->ralign);
					log_puts("\n");
					panic();
				}
#endif
				memset(data, 0, f->ralign);
				abuf_wcommit(&s->mix.buf, f->ralign);
				f->ralign = s->round * s->mix.bpf;
			}
		}
		slot_stop(s, AMSG_ISSET(m->u.stop.drain) ? m->u.stop.drain : 1);
		break;
	case AMSG_SETPAR:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": SETPAR message\n");
		}
#endif
		if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": SETPAR, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (!sock_setpar(f)) {
			sock_close(f);
			return 0;
		}
		f->rtodo = sizeof(struct amsg);
		f->rstate = SOCK_RMSG;
		break;
	case AMSG_GETPAR:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": GETPAR message\n");
		}
#endif
		if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": GETPAR, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		AMSG_INIT(m);
		m->cmd = htonl(AMSG_GETPAR);
		m->u.par.legacy_mode = s->mode;
		m->u.par.xrun = s->xrun;
		m->u.par.bits = s->par.bits;
		m->u.par.bps = s->par.bps;
		m->u.par.sig = s->par.sig;
		m->u.par.le = s->par.le;
		m->u.par.msb = s->par.msb;
		if (s->mode & MODE_PLAY)
			m->u.par.pchan = htons(s->mix.nch);
		if (s->mode & MODE_RECMASK)
			m->u.par.rchan = htons(s->sub.nch);
		m->u.par.rate = htonl(s->rate);
		m->u.par.appbufsz = htonl(s->appbufsz);
		m->u.par.bufsz = htonl(SLOT_BUFSZ(s));
		m->u.par.round = htonl(s->round);
		f->rstate = SOCK_RRET;
		f->rtodo = sizeof(struct amsg);
		break;
	case AMSG_SETVOL:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": SETVOL message\n");
		}
#endif
		if (f->pstate < SOCK_INIT || s == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": SETVOL, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		ctl = ntohl(m->u.vol.ctl);
		if (ctl > MIDI_MAXCTL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": SETVOL, volume out of range\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->rtodo = sizeof(struct amsg);
		f->rstate = SOCK_RMSG;
		f->lastvol = ctl; /* dont trigger feedback message */
		slot_setvol(s, ctl);
		dev_midi_vol(s->opt->dev, s);
		ctl_onval(CTL_SLOT_LEVEL, s, NULL, ctl);
		break;
	case AMSG_CTLSUB_OLD:
	case AMSG_CTLSUB:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": CTLSUB message, desc = ");
			log_putx(m->u.ctlsub.desc);
			log_puts(", val = ");
			log_putx(m->u.ctlsub.val);
			log_puts("\n");
		}
#endif
		if (f->pstate != SOCK_INIT || f->ctlslot == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": CTLSUB, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (m->u.ctlsub.desc) {
			if (!(f->ctlops & SOCK_CTLDESC)) {
				ctl = f->ctlslot->self;
				c = ctl_list;
				while (c != NULL) {
					if (ctlslot_visible(f->ctlslot, c))
						c->desc_mask |= ctl;
					c = c->next;
				}
				f->ctlops |= SOCK_CTLDESC;
				f->ctlsyncpending = 1;
				f->ctl_desc_size = (cmd == AMSG_CTLSUB) ?
				    sizeof(struct amsg_ctl_desc) :
				    AMSG_OLD_DESC_SIZE;
			}
		} else
			f->ctlops &= ~SOCK_CTLDESC;
		if (m->u.ctlsub.val) {
			f->ctlops |= SOCK_CTLVAL;
		} else
			f->ctlops &= ~SOCK_CTLVAL;
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
		break;
	case AMSG_CTLSET:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": CTLSET message\n");
		}
#endif
		if (f->pstate < SOCK_INIT || f->ctlslot == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": CTLSET, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}

		c = ctlslot_lookup(f->ctlslot, ntohs(m->u.ctlset.addr));
		if (c == NULL) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": CTLSET, wrong addr\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (!ctl_setval(c, ntohs(m->u.ctlset.val))) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": CTLSET, bad value\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		f->rtodo = sizeof(struct amsg);
		f->rstate = SOCK_RMSG;
		break;
	case AMSG_AUTH:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": AUTH message\n");
		}
#endif
		if (f->pstate != SOCK_AUTH) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": AUTH, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (!sock_auth(f)) {
			sock_close(f);
			return 0;
		}
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
		break;
	case AMSG_HELLO:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": HELLO message\n");
		}
#endif
		if (f->pstate != SOCK_HELLO) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": HELLO, wrong state\n");
			}
#endif
			sock_close(f);
			return 0;
		}
		if (!sock_hello(f)) {
			sock_close(f);
			return 0;
		}
		AMSG_INIT(m);
		m->cmd = htonl(AMSG_ACK);
		f->rstate = SOCK_RRET;
		f->rtodo = sizeof(struct amsg);
		break;
	case AMSG_BYE:
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": BYE message\n");
		}
#endif
		if (s != NULL && f->pstate != SOCK_INIT) {
#ifdef DEBUG
			if (log_level >= 1) {
				sock_log(f);
				log_puts(": BYE, wrong state\n");
			}
#endif
		}
		sock_close(f);
		return 0;
	default:
#ifdef DEBUG
		if (log_level >= 1) {
			sock_log(f);
			log_puts(": unknown command in message\n");
		}
#endif
		sock_close(f);
		return 0;
	}
	return 1;
}

/*
 * build a message in f->wmsg, return 1 on success and 0 if
 * there's nothing to do. Assume f->wstate is SOCK_WIDLE
 */
int
sock_buildmsg(struct sock *f)
{
	unsigned int size, type, mask;
	struct amsg_ctl_desc *desc;
	struct ctl *c, **pc;

	/*
	 * If pos changed (or initial tick), build a MOVE message.
	 */
	if (f->tickpending) {
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": building MOVE message, delta = ");
			log_puti(f->slot->delta);
			log_puts("\n");
		}
#endif
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_MOVE);
		f->wmsg.u.ts.delta = htonl(f->slot->delta);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		f->tickpending = 0;
		/*
		 * XXX: use tickpending as accumulator rather than
		 * slot->delta
		 */
		f->slot->delta = 0;
		return 1;
	}

	if (f->fillpending > 0) {
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_FLOWCTL);
		f->wmsg.u.ts.delta = htonl(f->fillpending);
		size = f->fillpending;
		if (f->slot)
			size *= f->slot->mix.bpf;
		f->rmax += size;
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": building FLOWCTL message, count = ");
			log_puti(f->fillpending);
			log_puts(", rmax -> ");
			log_puti(f->rmax);
			log_puts("\n");
		}
#endif
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		f->fillpending = 0;
		return 1;
	}

	/*
	 * if volume changed build a SETVOL message
	 */
	if (f->pstate >= SOCK_START && f->slot->vol != f->lastvol) {
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": building SETVOL message, vol = ");
			log_puti(f->slot->vol);
			log_puts("\n");
		}
#endif
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_SETVOL);
		f->wmsg.u.vol.ctl = htonl(f->slot->vol);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		f->lastvol = f->slot->vol;
		return 1;
	}

	if (f->midi != NULL && f->midi->obuf.used > 0) {
		size = f->midi->obuf.used;
		if (size > AMSG_DATAMAX)
			size = AMSG_DATAMAX;
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_DATA);
		f->wmsg.u.data.size = htonl(size);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		return 1;
	}

	/*
	 * If data available, build a DATA message.
	 */
	if (f->slot != NULL && f->wmax > 0 && f->slot->sub.buf.used > 0) {
		size = f->slot->sub.buf.used;
		if (size > AMSG_DATAMAX)
			size = AMSG_DATAMAX;
		if (size > f->walign)
			size = f->walign;
		if (size > f->wmax)
			size = f->wmax;
		size -= size % f->slot->sub.bpf;
#ifdef DEBUG
		if (size == 0) {
			sock_log(f);
			log_puts(": sock_buildmsg size == 0\n");
			panic();
		}
#endif
		f->walign -= size;
		f->wmax -= size;
		if (f->walign == 0)
			f->walign = f->slot->round * f->slot->sub.bpf;
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": building audio DATA message, size = ");
			log_puti(size);
			log_puts("\n");
		}
#endif
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_DATA);
		f->wmsg.u.data.size = htonl(size);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		return 1;
	}

	if (f->stoppending) {
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": building STOP message\n");
		}
#endif
		f->stoppending = 0;
		f->pstate = SOCK_INIT;
		AMSG_INIT(&f->wmsg);
		f->wmsg.cmd = htonl(AMSG_STOP);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
		return 1;
	}

	/*
	 * XXX: add a flag indicating if there are changes
	 * in controls not seen by this client, rather
	 * than walking through the full list of control
	 * searching for the {desc,val}_mask bits
	 */
	if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
		mask = f->ctlslot->self;
		size = 0;
		pc = &ctl_list;
		while ((c = *pc) != NULL) {
			if ((c->desc_mask & mask) == 0 ||
			    (c->refs_mask & mask) == 0) {
				pc = &c->next;
				continue;
			}
			if (size + f->ctl_desc_size > SOCK_CTLDESC_SIZE)
				break;
			desc = (struct amsg_ctl_desc *)(f->ctldesc + size);
			c->desc_mask &= ~mask;
			c->val_mask &= ~mask;
			type = ctlslot_visible(f->ctlslot, c) ?
			    c->type : CTL_NONE;
			strlcpy(desc->group, ctlgroup(f, c), AMSG_CTL_NAMEMAX);
			strlcpy(desc->node0.name, c->node0.name,
			    AMSG_CTL_NAMEMAX);
			desc->node0.unit = ntohs(c->node0.unit);
			strlcpy(desc->node1.name, c->node1.name,
			    AMSG_CTL_NAMEMAX);
			desc->node1.unit = ntohs(c->node1.unit);
			desc->type = type;
			strlcpy(desc->func, c->func, AMSG_CTL_NAMEMAX);
			desc->addr = htons(c->addr);
			desc->maxval = htons(c->maxval);
			desc->curval = htons(c->curval);

			/* old clients don't have the 'display' member */
			if (f->ctl_desc_size >= offsetof(struct amsg_ctl_desc,
				display) + AMSG_CTL_DISPLAYMAX) {
				strlcpy(desc->display, c->display, AMSG_CTL_DISPLAYMAX);
			}

			size += f->ctl_desc_size;

			/* if this is a deleted entry unref it */
			if (type == CTL_NONE) {
				c->refs_mask &= ~mask;
				if (c->refs_mask == 0) {
					*pc = c->next;
					xfree(c);
					continue;
				}
			}

			pc = &c->next;
		}
		if (size > 0) {
			AMSG_INIT(&f->wmsg);
			f->wmsg.cmd = htonl(AMSG_DATA);
			f->wmsg.u.data.size = htonl(size);
			f->wtodo = sizeof(struct amsg);
			f->wstate = SOCK_WMSG;
#ifdef DEBUG
			if (log_level >= 3) {
				sock_log(f);
				log_puts(": building control DATA message\n");
			}
#endif
			return 1;
		}
	}
	if (f->ctlslot && (f->ctlops & SOCK_CTLVAL)) {
		mask = f->ctlslot->self;
		for (c = ctl_list; c != NULL; c = c->next) {
			if (!ctlslot_visible(f->ctlslot, c))
				continue;
			if ((c->val_mask & mask) == 0)
				continue;
			c->val_mask &= ~mask;
			AMSG_INIT(&f->wmsg);
			f->wmsg.cmd = htonl(AMSG_CTLSET);
			f->wmsg.u.ctlset.addr = htons(c->addr);
			f->wmsg.u.ctlset.val = htons(c->curval);
			f->wtodo = sizeof(struct amsg);
			f->wstate = SOCK_WMSG;
#ifdef DEBUG
			if (log_level >= 3) {
				sock_log(f);
				log_puts(": building CTLSET message\n");
			}
#endif
			return 1;
		}
	}
	if (f->ctlslot && f->ctlsyncpending) {
		f->ctlsyncpending = 0;
		f->wmsg.cmd = htonl(AMSG_CTLSYNC);
		f->wtodo = sizeof(struct amsg);
		f->wstate = SOCK_WMSG;
#ifdef DEBUG
		if (log_level >= 3) {
			sock_log(f);
			log_puts(": building CTLSYNC message\n");
		}
#endif
		return 1;
	}
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": no messages to build anymore, idling...\n");
	}
#endif
	f->wstate = SOCK_WIDLE;
	return 0;
}

/*
 * iteration of the socket reader loop, return 1 on success
 */
int
sock_read(struct sock *f)
{
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": reading ");
		log_putu(f->rtodo);
		log_puts(" todo\n");
	}
#endif
	switch (f->rstate) {
	case SOCK_RIDLE:
		return 0;
	case SOCK_RMSG:
		if (!sock_rmsg(f))
			return 0;
		if (!sock_execmsg(f))
			return 0;
		break;
	case SOCK_RDATA:
		if (!sock_rdata(f))
			return 0;
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
		break;
	case SOCK_RRET:
		if (f->wstate != SOCK_WIDLE) {
#ifdef DEBUG
			if (log_level >= 4) {
				sock_log(f);
				log_puts(": can't reply, write-end blocked\n");
			}
#endif
			return 0;
		}
		f->wmsg = f->rmsg;
		f->wstate = SOCK_WMSG;
		f->wtodo = sizeof(struct amsg);
		f->rstate = SOCK_RMSG;
		f->rtodo = sizeof(struct amsg);
#ifdef DEBUG
		if (log_level >= 4) {
			sock_log(f);
			log_puts(": copied RRET message\n");
		}
#endif
	}
	return 1;
}

/*
 * iteration of the socket writer loop, return 1 on success
 */
int
sock_write(struct sock *f)
{
#ifdef DEBUG
	if (log_level >= 4) {
		sock_log(f);
		log_puts(": writing");
		if (f->wstate != SOCK_WIDLE) {
			log_puts(" todo = ");
			log_putu(f->wtodo);
		}
		log_puts("\n");
	}
#endif
	switch (f->wstate) {
	case SOCK_WMSG:
		if (!sock_wmsg(f))
			return 0;
		/*
		 * f->wmsg is either build by sock_buildmsg() or
		 * copied from f->rmsg (in the SOCK_RRET state), so
		 * it's safe.
		 */
		if (ntohl(f->wmsg.cmd) != AMSG_DATA) {
			f->wstate = SOCK_WIDLE;
			f->wtodo = 0xdeadbeef;
			break;
		}
		f->wstate = SOCK_WDATA;
		f->wsize = f->wtodo = ntohl(f->wmsg.u.data.size);
		/* PASSTHROUGH */
	case SOCK_WDATA:
		if (!sock_wdata(f))
			return 0;
		if (f->wtodo > 0)
			break;
		f->wstate = SOCK_WIDLE;
		f->wtodo = 0xdeadbeef;
		if (f->pstate == SOCK_STOP) {
			f->pstate = SOCK_INIT;
			f->wmax = 0;
#ifdef DEBUG
			if (log_level >= 4) {
				sock_log(f);
				log_puts(": drained, moved to INIT state\n");
			}
#endif
		}
		/* PASSTHROUGH */
	case SOCK_WIDLE:
		if (f->rstate == SOCK_RRET) {
			f->wmsg = f->rmsg;
			f->wstate = SOCK_WMSG;
			f->wtodo = sizeof(struct amsg);
			f->rstate = SOCK_RMSG;
			f->rtodo = sizeof(struct amsg);
#ifdef DEBUG
			if (log_level >= 4) {
				sock_log(f);
				log_puts(": copied RRET message\n");
			}
#endif
		} else {
			if (!sock_buildmsg(f))
				return 0;
		}
		break;
#ifdef DEBUG
	default:
		sock_log(f);
		log_puts(": bad writing end state\n");
		panic();
#endif
	}
	return 1;
}

int
sock_pollfd(void *arg, struct pollfd *pfd)
{
	struct sock *f = arg;
	int events = 0;

	/*
	 * feedback counters, clock ticks and alike may have changed,
	 * prepare a message to trigger writes
	 *
	 * XXX: doing this at the beginning of the cycle is not optimal,
	 * because state is changed at the end of the read cycle, and
	 * thus counters, ret message and alike are generated then.
	 */
	if (f->wstate == SOCK_WIDLE && f->rstate != SOCK_RRET)
		sock_buildmsg(f);

	if (f->rstate == SOCK_RMSG ||
	    f->rstate == SOCK_RDATA)
		events |= POLLIN;
	if (f->rstate == SOCK_RRET ||
	    f->wstate == SOCK_WMSG ||
	    f->wstate == SOCK_WDATA)
		events |= POLLOUT;
	pfd->fd = f->fd;
	pfd->events = events;
	return 1;
}

int
sock_revents(void *arg, struct pollfd *pfd)
{
	return pfd->revents;
}

void
sock_in(void *arg)
{
	struct sock *f = arg;

	while (sock_read(f))
		;
}

void
sock_out(void *arg)
{
	struct sock *f = arg;

	while (sock_write(f))
		;
}

void
sock_hup(void *arg)
{
	struct sock *f = arg;

	sock_close(f);
}
