[BACK]Return to sock.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sndiod

File: [local] / src / usr.bin / sndiod / sock.c (download)

Revision 1.31, Fri Jul 12 06:30:55 2019 UTC (4 years, 10 months ago) by ratchov
Branch: MAIN
CVS Tags: OPENBSD_6_6_BASE, OPENBSD_6_6
Changes since 1.30: +4 -2 lines

Add affinity between the program and its mixer control.

Currently, if there are two instances of the same program, sndiod will
allocate one volume control to each. If both programs disconnect and
reconnect, the information of which control is assigned to which
program is lost. This makes difficult to run two instances of a player
and crossfade between each other with a MIDI controller.

To address this, the program chooses a 32-bit "id" (for now the
process pid) and sends it to the server. The server records the id in
the client's slot structure.  When the server accepts a new
connection, it uses the id to identify the slot the client used during
the previous connection; if it was not recycled yet, it's assigned to
the program.

/*	$OpenBSD: sock.c,v 1.31 2019/07/12 06:30:55 ratchov Exp $	*/
/*
 * 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 <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"

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);
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 sock *sock_list = NULL;
unsigned int sock_sesrefs = 0;		/* connections to the session */
uint8_t sock_sescookie[AMSG_COOKIELEN];	/* owner of the session */

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
		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 sock **pf;

	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--;
	if (f->slot) {
		slot_del(f->slot);
		f->slot = NULL;
	}
	if (f->midi) {
		midi_del(f->midi);
		f->midi = NULL;
	}
	if (f->port) {
		port_unref(f->port);
		f->port = NULL;
	}
	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;
}

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->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->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);
		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->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->mmc && 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;

	if (sock_sesrefs == 0) {
		/* start a new session */
		memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN);
	} else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) {
		/* another session is active, drop connection */
		return 0;
	}
	sock_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 dev *d;
	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:
		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 < 16) {
			d = dev_bynum(p->devnum);
			if (d == NULL)
				return 0;
			midi_tag(f->midi, p->devnum);
		} else if (p->devnum < 32) {
			midi_tag(f->midi, p->devnum);
		} else if (p->devnum < 48) {
			c = port_bynum(p->devnum - 32);
			if (c == NULL || !port_ref(c))
				return 0;
			f->port = c;
			midi_link(f->midi, c->midi);
		} else
			return 0;
		return 1;
	}
	d = dev_bynum(p->devnum);
	if (d == NULL)
		return 0;
	opt = opt_byname(d, p->opt);
	if (opt == NULL)
		return 0;
	f->slot = slot_new(d, 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 slot *s = f->slot;
	struct amsg *m = &f->rmsg;
	unsigned char *data;
	int size, ctl;

	switch (ntohl(m->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);
		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->dev, s);
		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;

	/*
	 * 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;
	}
#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);
}