Improve monitor client interface and vomp for asterisk channel driver

This commit is contained in:
Jeremy Lakeman 2012-08-08 10:56:05 +09:30
parent 736a8f43c0
commit f059546eb0
8 changed files with 421 additions and 201 deletions

@ -346,13 +346,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define VOMP_CODEC_CODECSISUPPORT 0xfe #define VOMP_CODEC_CODECSISUPPORT 0xfe
#define VOMP_CODEC_CHANGEYOURCODECTO 0xff #define VOMP_CODEC_CHANGEYOURCODECTO 0xff
#define VOMP_STATE_NOCALL 1
#define VOMP_STATE_CALLPREP 2
#define VOMP_STATE_RINGINGOUT 3
#define VOMP_STATE_RINGINGIN 4
#define VOMP_STATE_INCALL 5
#define VOMP_STATE_CALLENDED 6
/* in milliseconds of inactivity */ /* in milliseconds of inactivity */
#define VOMP_CALL_TIMEOUT 120000 #define VOMP_CALL_TIMEOUT 120000
#define VOMP_CALL_STATUS_INTERVAL 1000 #define VOMP_CALL_STATUS_INTERVAL 1000

22
log.c

@ -175,8 +175,7 @@ static int _log_prepare(int level, const char *file, unsigned int line, const ch
return 1; return 1;
} }
static void _log_finish(int level) static void _log_internal(int level, struct strbuf *buf){
{
#ifdef ANDROID #ifdef ANDROID
int alevel = ANDROID_LOG_UNKNOWN; int alevel = ANDROID_LOG_UNKNOWN;
switch (level) { switch (level) {
@ -186,17 +185,28 @@ static void _log_finish(int level)
case LOG_LEVEL_WARN: alevel = ANDROID_LOG_WARN; break; case LOG_LEVEL_WARN: alevel = ANDROID_LOG_WARN; break;
case LOG_LEVEL_DEBUG: alevel = ANDROID_LOG_DEBUG; break; case LOG_LEVEL_DEBUG: alevel = ANDROID_LOG_DEBUG; break;
} }
__android_log_print(alevel, "servald", "%s", strbuf_str(&logbuf)); __android_log_print(alevel, "servald", "%s", strbuf_str(buf));
strbuf_reset(&logbuf); strbuf_reset(buf);
#else #else
FILE *logf = open_logging(); FILE *logf = open_logging();
if (logf) { if (logf) {
fprintf(logf, "%s\n%s", strbuf_str(&logbuf), strbuf_overrun(&logbuf) ? "LOG OVERRUN\n" : ""); fprintf(logf, "%s\n%s", strbuf_str(buf), strbuf_overrun(buf) ? "LOG OVERRUN\n" : "");
strbuf_reset(&logbuf); strbuf_reset(buf);
} }
#endif #endif
} }
void (*_log_implementation)(int level, struct strbuf *buf)=_log_internal;
static void _log_finish(int level){
if(_log_implementation)
_log_implementation(level, &logbuf);
}
void set_log_implementation(void (*log_function)(int level, struct strbuf *buf)){
_log_implementation=log_function;
}
void logArgv(int level, const char *file, unsigned int line, const char *function, const char *label, int argc, const char *const *argv) void logArgv(int level, const char *file, unsigned int line, const char *function, const char *label, int argc, const char *const *argv)
{ {
if (_log_prepare(level, file, line, function)) { if (_log_prepare(level, file, line, function)) {

3
log.h

@ -61,6 +61,8 @@ extern unsigned int debug;
#define LOG_LEVEL_ERROR (3) #define LOG_LEVEL_ERROR (3)
#define LOG_LEVEL_FATAL (4) #define LOG_LEVEL_FATAL (4)
struct strbuf;
void set_logging(FILE *f); void set_logging(FILE *f);
FILE *open_logging(); FILE *open_logging();
void close_logging(); void close_logging();
@ -74,6 +76,7 @@ char *toprint(char *dstStr, ssize_t dstBufSiz, const char *srcBuf, size_t srcByt
size_t toprint_strlen(const char *srcBuf, size_t srcBytes); size_t toprint_strlen(const char *srcBuf, size_t srcBytes);
ssize_t get_self_executable_path(char *buf, size_t len); ssize_t get_self_executable_path(char *buf, size_t len);
int log_backtrace(const char *file, unsigned int line, const char *function); int log_backtrace(const char *file, unsigned int line, const char *function);
void set_log_implementation(void (*log_function)(int level, struct strbuf *buf));
#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) == -1 ? toprint_strlen((buf),(len)) + 1 : (dstlen)), (dstlen), (buf), (len)) #define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) == -1 ? toprint_strlen((buf),(len)) + 1 : (dstlen)), (dstlen), (buf), (len))

@ -16,99 +16,261 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include "monitor-client.h" #include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#ifdef WIN32
#include "win32/win32.h"
#endif
#include <unistd.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <sys/un.h>
#include <fcntl.h>
#include "constants.h"
#include "conf.h"
#include "log.h"
#include "str.h"
#include "monitor-client.h"
#include <ctype.h>
#define STATE_INIT 0
#define STATE_DATA 1
#define STATE_READY 2
#define MONITOR_CLIENT_BUFFER_SIZE 8192
#define MAX_ARGS 32
struct monitor_state {
char *cmd;
int argc;
char *argv[MAX_ARGS];
unsigned char *data;
int dataBytes;
int cmdBytes;
int state;
unsigned char buffer[MONITOR_CLIENT_BUFFER_SIZE];
int bufferBytes;
};
// FIX ME, COPY-PASTA from monitor.c
int monitor_socket_name(struct sockaddr_un *name){
int len;
#ifdef linux
/* Use abstract namespace as Android has no writable FS which supports sockets.
Abstract namespace is just plain better, anyway, as no dead files end up
hanging around. */
name.sun_path[0]=0;
/* XXX: 104 comes from OSX sys/un.h - no #define (note Linux has UNIX_PATH_MAX and it's 108(!)) */
snprintf(&name->sun_path[1],104-2,
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
/* Doesn't include trailing nul */
len = 1+strlen(&name->sun_path[1]) + sizeof(name->sun_family);
#else
snprintf(name->sun_path,104-1,"%s/%s",
serval_instancepath(),
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
/* Includes trailing nul */
len = 1+strlen(name->sun_path) + sizeof(name->sun_family);
#endif
return len;
}
/* Open monitor interface abstract domain named socket */ /* Open monitor interface abstract domain named socket */
int monitor_client_open() int monitor_client_open(struct monitor_state **res)
{ {
int fd; int fd;
struct sockaddr_un addr; struct sockaddr_un addr;
if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket"); perror("socket");
exit(-1); return -1;
} }
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
/* XXX - On non-linux systems, we need to use a regular named socket */ int len = monitor_socket_name(&addr);
addr.sun_path[0]=0;
snprintf(&addr.sun_path[1],100, INFOF("Attempting to connect to %s", &addr.sun_path[1]);
"%s", confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
int len = 1+strlen(&addr.sun_path[1]) + sizeof(addr.sun_family);
char *p=(char *)&addr;
printf("last char='%c' %02x\n",p[len-1],p[len-1]);
if (connect(fd, (struct sockaddr*)&addr, len) == -1) { if (connect(fd, (struct sockaddr*)&addr, len) == -1) {
perror("connect"); perror("connect");
exit(-1); return -1;
} }
*res = (struct monitor_state*)malloc(sizeof(struct monitor_state));
return fd; return fd;
} }
int monitor_client_writeline(int fd,char *msg) int monitor_client_close(int fd, struct monitor_state *res){
{ free(res);
return write(fd,msg,strlen(msg)); close(fd);
return 0;
} }
int monitor_client_writeline_and_data(int fd,char *msg,unsigned char *data,int bytes) int monitor_client_writeline(int fd,char *fmt, ...)
{ {
int maxlen=strlen(msg)+20+bytes; char msg[512];
int n;
va_list ap;
if (fd<0)
return -1;
va_start(ap, fmt);
n=vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
return write(fd,msg,n);
}
int monitor_client_writeline_and_data(int fd,unsigned char *data,int bytes,char *fmt,...)
{
int maxlen=512+bytes;
char out[maxlen]; char out[maxlen];
snprintf(out,maxlen,"*%d:%s\n",bytes,msg); va_list ap;
int len=strlen(msg); int n;
bcopy(&data[0],&msg[len],bytes);
len+=bytes; if (fd<0)
return write(fd,msg,len); return -1;
n=snprintf(out,maxlen-bytes,"*%d:",bytes);
va_start(ap, fmt);
n+=vsnprintf(out+n, maxlen-bytes-n, fmt, ap);
va_end(ap);
bcopy(data,out+n,bytes);
n+=bytes;
return write(fd,out,n);
} }
static unsigned char buffer[MONITOR_CLIENT_BUFFER_SIZE]; int monitor_client_read(int fd, struct monitor_state *res, struct monitor_command_handler *handlers, int handler_count)
static int buffer_bytes=0;
int monitor_client_readline(int fd, monitor_result **res)
{ {
monitor_result *r=*res;
/* Read any available bytes */ /* Read any available bytes */
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, NULL) | O_NONBLOCK); int oldOffset = res->bufferBytes;
int bytesRead=read(fd,&buffer[buffer_bytes],
MONITOR_CLIENT_BUFFER_SIZE-buffer_bytes); if (res->bufferBytes==0)
if (bytesRead>0) buffer_bytes+=bytesRead; res->cmd = (char *)res->buffer;
/* Now see if we have a full line of results to return */ int bytesRead=read(fd, res->buffer + oldOffset, MONITOR_CLIENT_BUFFER_SIZE - oldOffset);
int i; if (bytesRead<1){
for(i=0;i<buffer_bytes;i++) switch(errno) {
if (buffer[i]=='\n') { case EINTR:
/* Found an end of line marker. case ENOTRECOVERABLE:
Now check if there is a data section to extract. */ /* transient errors */
int dataBytes=0; WHY_perror("read");
int lineStart=0; case EAGAIN:
if (sscanf("*%d:%n",(char *)buffer,&dataBytes,&lineStart)==1) return 0;
{
if ((dataBytes+i)>buffer_bytes)
{
/* We don't yet have enough bytes to return */
return -1;
}
/* Copy data section */
r->dataBytes=dataBytes;
bcopy(&buffer[i],&r->data[0],dataBytes);
/* copy line from after the *len: part, and without the
new line. Then null-terminate it */
bcopy(&buffer[lineStart],&r->line[0],i-lineStart);
r->line[i-lineStart]=0;
/* remember to discard the data section from the buffer */
i+=dataBytes;
} else {
/* no data section */
r->dataBytes=0;
}
/* shuffle buffer down */
bcopy(&buffer[i],&buffer[0],buffer_bytes-i);
buffer_bytes-=i;
return 0;
} }
/* no end of line, so need to read more */ WHY_perror("read");
return -1; return -1;
}
res->bufferBytes+=bytesRead;
again:
// wait until we have the whole command line
if (res->state == STATE_INIT){
int i;
for(i=oldOffset;i<res->bufferBytes;i++){
if (res->buffer[i]=='\n'){
// skip any leading \n's
if ((char*)(res->buffer+i) == res->cmd){
res->cmd++;
continue;
}
res->buffer[i]=0;
res->dataBytes = 0;
res->cmdBytes = i + 1;
if (*res->cmd=='*'){
res->cmd++;
for (; isdigit(*res->cmd); ++res->cmd)
res->dataBytes = res->dataBytes * 10 + *res->cmd - '0';
if (*res->cmd==':')
res->cmd++;
}
// find all arguments, initialise argc / argv && null terminate strings
{
char *p=res->cmd;
res->argc=0;
while (*p){
if (*p==':'){
*p=0;
res->argv[res->argc]=p+1;
res->argc++;
}
p++;
}
}
if (res->dataBytes){
res->data=(unsigned char *)&res->buffer[i+1];
res->state = STATE_DATA;
}else{
res->data=NULL;
res->state = STATE_READY;
}
break;
}
}
}
// make sure all the data has arrived
if (res->state == STATE_DATA){
if (res->bufferBytes >= res->dataBytes + res->cmdBytes){
res->state = STATE_READY;
}
}
// ok, now we can try to process the command
if (res->state == STATE_READY){
int handled=0;
int i;
// call all handlers that match (yes there might be more than one)
for (i=0;i<handler_count;i++){
/* since we know res->cmd is terminated with a '\n',
and there shouldn't be a '\n' in h->command,
this shouldn't run past the end of the buffer */
if (handlers[i].handler && (!handlers[i].command || strcase_startswith(res->cmd,handlers[i].command, NULL))){
if (handlers[i].handler(res->cmd, res->argc, res->argv, res->data, res->dataBytes, handlers[i].context)>0)
handled=1;
}
}
if (!handled){
INFOF("Event \"%s\" was not handled", res->cmd);
}
// shuffle any unprocessed bytes
int remaining = res->bufferBytes - (res->dataBytes + res->cmdBytes);
if (remaining>0){
bcopy(res->buffer+res->dataBytes + res->cmdBytes,res->buffer,remaining);
}
res->bufferBytes=remaining;
res->cmdBytes=0;
res->dataBytes=0;
res->state = STATE_INIT;
res->cmd = (char *)res->buffer;
oldOffset = 0;
goto again;
}
return 0;
} }

@ -16,34 +16,16 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#include <stdio.h> struct monitor_state;
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#ifdef WIN32 struct monitor_command_handler{
#include "win32/win32.h" char *command;
#endif void *context;
#include <unistd.h> int (*handler)(char *cmd, int argc, char **argv, unsigned char *data, int dataLen, void *context);
#ifdef HAVE_SYS_SOCKET_H };
#include <sys/socket.h>
#endif
#include <sys/un.h>
#include <fcntl.h>
#include "constants.h" int monitor_client_open(struct monitor_state **res);
#include "conf.h" int monitor_client_writeline(int fd,char *fmt, ...);
#include "log.h" int monitor_client_writeline_and_data(int fd,unsigned char *data,int bytes,char *fmt,...);
int monitor_client_read(int fd, struct monitor_state *res, struct monitor_command_handler *handlers, int handler_count);
#define MONITOR_CLIENT_BUFFER_SIZE 8192 int monitor_client_close(int fd, struct monitor_state *res);
typedef struct monitor_result {
char line[MONITOR_CLIENT_BUFFER_SIZE];
unsigned char data[MONITOR_CLIENT_BUFFER_SIZE];
int dataBytes;
} monitor_result;

@ -60,6 +60,28 @@ struct sched_ent named_socket;
struct profile_total named_stats; struct profile_total named_stats;
struct profile_total client_stats; struct profile_total client_stats;
int monitor_socket_name(struct sockaddr_un *name){
int len;
#ifdef linux
/* Use abstract namespace as Android has no writable FS which supports sockets.
Abstract namespace is just plain better, anyway, as no dead files end up
hanging around. */
name->sun_path[0]=0;
/* XXX: 104 comes from OSX sys/un.h - no #define (note Linux has UNIX_PATH_MAX and it's 108(!)) */
snprintf(&name->sun_path[1],104-2,
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
/* Doesn't include trailing nul */
len = 1+strlen(&name->sun_path[1]) + sizeof(name->sun_family);
#else
snprintf(name->sun_path,104-1,"%s/%s",
serval_instancepath(),
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
/* Includes trailing nul */
len = 1+strlen(name->sun_path) + sizeof(name->sun_family);
#endif
return len;
}
int monitor_setup_sockets() int monitor_setup_sockets()
{ {
struct sockaddr_un name; struct sockaddr_un name;
@ -74,23 +96,9 @@ int monitor_setup_sockets()
goto error; goto error;
} }
#ifdef linux len = monitor_socket_name(&name);
/* Use abstract namespace as Android has no writable FS which supports sockets. #ifndef linux
Abstract namespace is just plain better, anyway, as no dead files end up
hanging around. */
name.sun_path[0]=0;
/* XXX: 104 comes from OSX sys/un.h - no #define (note Linux has UNIX_PATH_MAX and it's 108(!)) */
snprintf(&name.sun_path[1],104-2,
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
/* Doesn't include trailing nul */
len = 1+strlen(&name.sun_path[1]) + sizeof(name.sun_family);
#else
snprintf(name.sun_path,104-1,"%s/%s",
serval_instancepath(),
confValueGet("monitor.socket",DEFAULT_MONITOR_SOCKET_NAME));
unlink(name.sun_path); unlink(name.sun_path);
/* Includes trailing nul */
len = 1+strlen(name.sun_path) + sizeof(name.sun_family);
#endif #endif
if(bind(sock, (struct sockaddr *)&name, len)==-1) { if(bind(sock, (struct sockaddr *)&name, len)==-1) {
@ -176,7 +184,7 @@ void monitor_client_close(struct monitor_context *c){
void monitor_client_poll(struct sched_ent *alarm) void monitor_client_poll(struct sched_ent *alarm)
{ {
/* Read from any open connections */ /* Read available data from a monitor socket */
struct monitor_context *c=(struct monitor_context *)alarm; struct monitor_context *c=(struct monitor_context *)alarm;
errno=0; errno=0;
int bytes; int bytes;
@ -288,9 +296,12 @@ static void monitor_new_client(int s) {
#endif #endif
if (otheruid != getuid()) { if (otheruid != getuid()) {
WHYF("monitor.socket client has wrong uid (%d versus %d)", otheruid,getuid()); int allowed_id = confValueGetInt64("monitor.uid",-1);
write_str(s, "\nCLOSE:Incorrect UID\n"); if (otheruid != allowed_id){
goto error; WHYF("monitor.socket client has wrong uid (%d versus %d)", otheruid,getuid());
write_str(s, "\nCLOSE:Incorrect UID\n");
goto error;
}
} }
if (monitor_socket_count >= MAX_MONITOR_SOCKETS if (monitor_socket_count >= MAX_MONITOR_SOCKETS
||monitor_socket_count < 0) { ||monitor_socket_count < 0) {
@ -333,7 +344,6 @@ int monitor_process_command(struct monitor_context *c)
} }
char msg[1024]; char msg[1024];
int flag;
if (cmd[0]=='*') { if (cmd[0]=='*') {
/* command with content */ /* command with content */
@ -346,7 +356,7 @@ int monitor_process_command(struct monitor_context *c)
c->data_offset=0; c->data_offset=0;
c->sample_codec=-1; c->sample_codec=-1;
if (sscanf(cmd,"AUDIO:%x:%d", if (sscanf(cmd,"AUDIO %x %d",
&callSessionToken,&sampleType)==2) &callSessionToken,&sampleType)==2)
{ {
/* Start getting sample */ /* Start getting sample */
@ -356,18 +366,18 @@ int monitor_process_command(struct monitor_context *c)
} }
} }
} }
else if (!strcasecmp(cmd,"monitor vomp")) else if (strcase_startswith(cmd,"monitor vomp",NULL))
// TODO add supported codec list argument // TODO add supported codec list argument
c->flags|=MONITOR_VOMP; c->flags|=MONITOR_VOMP;
else if (!strcasecmp(cmd,"ignore vomp")) else if (strcase_startswith(cmd,"ignore vomp",NULL))
c->flags&=~MONITOR_VOMP; c->flags&=~MONITOR_VOMP;
else if (!strcasecmp(cmd,"monitor rhizome")) else if (strcase_startswith(cmd,"monitor rhizome", NULL))
c->flags|=MONITOR_RHIZOME; c->flags|=MONITOR_RHIZOME;
else if (!strcasecmp(cmd,"ignore rhizome")) else if (strcase_startswith(cmd,"ignore rhizome", NULL))
c->flags&=~MONITOR_RHIZOME; c->flags&=~MONITOR_RHIZOME;
else if (!strcasecmp(cmd,"monitor peers")) else if (strcase_startswith(cmd,"monitor peers", NULL))
c->flags|=MONITOR_PEERS; c->flags|=MONITOR_PEERS;
else if (!strcasecmp(cmd,"ignore peers")) else if (strcase_startswith(cmd,"ignore peers", NULL))
c->flags&=~MONITOR_PEERS; c->flags&=~MONITOR_PEERS;
else if (sscanf(cmd,"call %s %s %s",sid,localDid,remoteDid)==3) { else if (sscanf(cmd,"call %s %s %s",sid,localDid,remoteDid)==3) {
DEBUG("here"); DEBUG("here");
@ -490,18 +500,12 @@ int monitor_announce_bundle(rhizome_manifest *m)
int monitor_announce_peer(const unsigned char *sid) int monitor_announce_peer(const unsigned char *sid)
{ {
char msg[1024]; return monitor_tell_formatted(MONITOR_PEERS, "\nNEWPEER:%s\n", alloca_tohex_sid(sid));
int n = snprintf(msg, sizeof msg, "\nNEWPEER:%s\n", alloca_tohex_sid(sid));
monitor_tell_clients(msg, n, MONITOR_PEERS);
return 0;
} }
int monitor_announce_unreachable_peer(const unsigned char *sid) int monitor_announce_unreachable_peer(const unsigned char *sid)
{ {
char msg[1024]; return monitor_tell_formatted(MONITOR_PEERS, "\nOLDPEER:%s\n", alloca_tohex_sid(sid));
int n = snprintf(msg, sizeof msg, "\nOLDPEER:%s\n", alloca_tohex_sid(sid));
monitor_tell_clients(msg, n, MONITOR_PEERS);
return 0;
} }
// test if any monitor clients are interested in a particular type of event // test if any monitor clients are interested in a particular type of event
@ -532,3 +536,15 @@ int monitor_tell_clients(char *msg, int msglen, int mask)
} }
RETURN(0); RETURN(0);
} }
int monitor_tell_formatted(int mask, char *fmt, ...){
char msg[1024];
int n;
va_list ap;
va_start(ap, fmt);
n=vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
monitor_tell_clients(msg, n, mask);
return 0;
}

@ -1092,6 +1092,7 @@ int monitor_get_fds(struct pollfd *fds,int *fdcount,int fdmax);
int monitor_announce_peer(const unsigned char *sid); int monitor_announce_peer(const unsigned char *sid);
int monitor_announce_unreachable_peer(const unsigned char *sid); int monitor_announce_unreachable_peer(const unsigned char *sid);
int monitor_tell_clients(char *msg, int msglen, int mask); int monitor_tell_clients(char *msg, int msglen, int mask);
int monitor_tell_formatted(int mask, char *fmt, ...);
int monitor_client_interested(int mask); int monitor_client_interested(int mask);
extern int monitor_socket_count; extern int monitor_socket_count;

167
vomp.c

@ -34,26 +34,40 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# incoming command from monitor client # incoming command from monitor client
$ outgoing monitor status $ outgoing monitor status
<> vomp packet with state change sent across the network <> vomp packet with state change sent across the network
Monitor Init Monitor Init
# MONITOR VOMP # MONITOR VOMP [supported codec list]
Dialing Dialing
# CALL [sid] [myDid] [TheirDid] [token] // client requests an outgoing call
# CALL [sid] [myDid] [TheirDid]
> CALLPREP + codecs > CALLPREP + codecs
$ CALLTO [token] // let the client know what token we are going to use for the remainder of the call
$ CALLTO [token] [mySid] [myDid] [TheirSid] [TheirDid]
// allocate a session number and tell them our codecs,
// but we don't need to do anything else yet,
// this might be a replay attack
< NOCALL + codecs < NOCALL + codecs
// Ok, we have a network path, lets try to establish the call
> RINGOUT > RINGOUT
$ CALLFROM [token] [sid] [myDid] [TheirDid] // (Note that if both parties are trying to dial each other,
// the call should jump straight to INCALL)
// inform client about the call request
$ CALLFROM [token] [mySid] [myDid] [TheirSid] [TheirDid]
// Note that we may need to wait for other external processes
// before a phone is actually ringing
# RING [token] # RING [token]
< RINGIN (they start ringing) < RINGIN
// All good, there's a phone out there ringing, you can indicate that to the user
$ RINGING [token] $ RINGING [token]
Answering Answering
# PICKUP [token] # PICKUP [token]
< INCALL < INCALL
// The client can now start sending audio
> INCALL > INCALL
$ INCALL [token] $ INCALL [token]
// The client can now start sending audio
$ INCALL [token] $ INCALL [token]
Tell any clients that the call hasn't timed out yet Tell any clients that the call hasn't timed out yet
@ -68,7 +82,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
$ HANGUP [token] $ HANGUP [token]
*/ */
// ideally these id's should only be used on the network, with monitor events to inform clients of state changes
#define VOMP_STATE_NOCALL 1
#define VOMP_STATE_CALLPREP 2
#define VOMP_STATE_RINGINGOUT 3
#define VOMP_STATE_RINGINGIN 4
#define VOMP_STATE_INCALL 5
#define VOMP_STATE_CALLENDED 6
struct vomp_call_state { struct vomp_call_state {
struct sched_ent alarm; struct sched_ent alarm;
@ -147,9 +167,7 @@ int vomp_generate_session_id()
struct vomp_call_state *vomp_create_call(unsigned char *remote_sid, struct vomp_call_state *vomp_create_call(unsigned char *remote_sid,
unsigned char *local_sid, unsigned char *local_sid,
unsigned int remote_session, unsigned int remote_session,
unsigned int local_session, unsigned int local_session)
int remote_state,
int local_state)
{ {
int i; int i;
if (!local_session) if (!local_session)
@ -164,8 +182,8 @@ struct vomp_call_state *vomp_create_call(unsigned char *remote_sid,
bcopy(remote_sid,call->remote.sid,SID_SIZE); bcopy(remote_sid,call->remote.sid,SID_SIZE);
call->local.session=local_session; call->local.session=local_session;
call->remote.session=remote_session; call->remote.session=remote_session;
call->local.state=local_state; call->local.state=VOMP_STATE_NOCALL;
call->remote.state=remote_state; call->remote.state=VOMP_STATE_NOCALL;
call->last_sent_status=-1; call->last_sent_status=-1;
call->create_time=gettime_ms(); call->create_time=gettime_ms();
call->last_activity=call->create_time; call->last_activity=call->create_time;
@ -245,7 +263,7 @@ struct vomp_call_state *vomp_find_or_create_call(unsigned char *remote_sid,
/* Only create a call record if either party is in CALLPREP state */ /* Only create a call record if either party is in CALLPREP state */
if (sender_state==VOMP_STATE_CALLPREP || recvr_state==VOMP_STATE_CALLPREP) if (sender_state==VOMP_STATE_CALLPREP || recvr_state==VOMP_STATE_CALLPREP)
return vomp_create_call(remote_sid, local_sid, sender_session, recvr_session, VOMP_STATE_NOCALL, VOMP_STATE_NOCALL); return vomp_create_call(remote_sid, local_sid, sender_session, recvr_session);
return NULL; return NULL;
} }
@ -448,10 +466,9 @@ int monitor_send_audio(struct vomp_call_state *call, int audio_codec, unsigned i
Put newline at start of these so that receiving data in command Put newline at start of these so that receiving data in command
mode doesn't confuse the parser. */ mode doesn't confuse the parser. */
int msglen = snprintf(msg, 1024, int msglen = snprintf(msg, 1024,
"\n*%d:AUDIOPACKET:%06x:%06x:%d:%d:%d:%d:%d\n", "\n*%d:AUDIOPACKET:%x:%d:%d:%d\n",
sample_bytes, sample_bytes,
call->local.session,call->remote.session, call->local.session,
call->local.state,call->remote.state,
audio_codec, start_time, end_time); audio_codec, start_time, end_time);
bcopy(audio, &msg[msglen], sample_bytes); bcopy(audio, &msg[msglen], sample_bytes);
@ -461,6 +478,54 @@ int monitor_send_audio(struct vomp_call_state *call, int audio_codec, unsigned i
return 0; return 0;
} }
// update local state and notify interested clients with the correct message
int vomp_update_local_state(struct vomp_call_state *call, int new_state){
if (call->local.state>=new_state)
return 0;
switch(new_state){
case VOMP_STATE_CALLPREP:
// tell client our session id.
monitor_tell_formatted(MONITOR_VOMP, "\nCALLTO:%x:%s:%s:%s:%s\n",
call->local.session,
alloca_tohex_sid(call->local.sid), call->local.did,
alloca_tohex_sid(call->remote.sid), call->remote.did);
break;
case VOMP_STATE_CALLENDED:
monitor_tell_formatted(MONITOR_VOMP, "\nHANGUP:%x\n", call->local.session);
break;
}
call->local.state=new_state;
return 0;
}
// update remote state and notify interested clients with the correct message
int vomp_update_remote_state(struct vomp_call_state *call, int new_state){
if (call->remote.state>=new_state)
return 0;
switch(new_state){
case VOMP_STATE_RINGINGOUT:
monitor_tell_formatted(MONITOR_VOMP, "\nCALLFROM:%x:%s:%s:%s:%s\n",
call->local.session,
alloca_tohex_sid(call->local.sid), call->local.did,
alloca_tohex_sid(call->remote.sid), call->remote.did);
break;
case VOMP_STATE_RINGINGIN:
monitor_tell_formatted(MONITOR_VOMP, "\nRINGING:%x\n", call->local.session);
break;
case VOMP_STATE_INCALL:
if (call->remote.state==VOMP_STATE_RINGINGIN){
monitor_tell_formatted(MONITOR_VOMP, "\nANSWERED:%x\n", call->local.session);
}
break;
}
call->remote.state=new_state;
return 0;
}
// send call state updates if required. // send call state updates if required.
int vomp_update(struct vomp_call_state *call) int vomp_update(struct vomp_call_state *call)
{ {
@ -568,7 +633,7 @@ int vomp_ringing(struct vomp_call_state *call){
if (debug & DEBUG_VOMP) if (debug & DEBUG_VOMP)
DEBUGF("RING RING!"); DEBUGF("RING RING!");
if (call->local.state<VOMP_STATE_RINGINGIN && call->remote.state==VOMP_STATE_RINGINGOUT){ if (call->local.state<VOMP_STATE_RINGINGIN && call->remote.state==VOMP_STATE_RINGINGOUT){
call->local.state=VOMP_STATE_RINGINGIN; vomp_update_local_state(call, VOMP_STATE_RINGINGIN);
vomp_update(call); vomp_update(call);
} }
} }
@ -584,8 +649,7 @@ int vomp_call_destroy(struct vomp_call_state *call)
DEBUGF("Destroying call %s <--> %s", call->local.did,call->remote.did); DEBUGF("Destroying call %s <--> %s", call->local.did,call->remote.did);
/* tell everyone the call has died */ /* tell everyone the call has died */
call->local.state=VOMP_STATE_CALLENDED; call->remote.state=VOMP_STATE_CALLENDED; vomp_update_local_state(call, VOMP_STATE_CALLENDED);
vomp_update(call); vomp_update(call);
/* now release the call structure */ /* now release the call structure */
@ -621,11 +685,8 @@ int vomp_dial(unsigned char *local_sid, unsigned char *remote_sid, char *local_d
remote_sid, remote_sid,
local_sid, local_sid,
0, 0,
0, 0);
VOMP_STATE_NOCALL, vomp_update_local_state(call, VOMP_STATE_CALLPREP);
VOMP_STATE_CALLPREP
);
// remember that we initiated this call, not the other party // remember that we initiated this call, not the other party
call->initiated_call = 1; call->initiated_call = 1;
@ -643,7 +704,7 @@ int vomp_pickup(struct vomp_call_state *call)
DEBUG("Picking up"); DEBUG("Picking up");
if (call->local.state!=VOMP_STATE_RINGINGIN) if (call->local.state!=VOMP_STATE_RINGINGIN)
return WHY("Call is not ringing"); return WHY("Call is not ringing");
call->local.state=VOMP_STATE_INCALL; vomp_update_local_state(call, VOMP_STATE_INCALL);
call->create_time=gettime_ms(); call->create_time=gettime_ms();
/* state machine does job of starting audio stream, just tell everyone about /* state machine does job of starting audio stream, just tell everyone about
the changed state. */ the changed state. */
@ -658,7 +719,7 @@ int vomp_hangup(struct vomp_call_state *call)
if (debug & DEBUG_VOMP) if (debug & DEBUG_VOMP)
DEBUG("Hanging up"); DEBUG("Hanging up");
if (call->local.state==VOMP_STATE_INCALL) vomp_call_stop_audio(call); if (call->local.state==VOMP_STATE_INCALL) vomp_call_stop_audio(call);
call->local.state=VOMP_STATE_CALLENDED; vomp_update_local_state(call, VOMP_STATE_CALLENDED);
vomp_update(call); vomp_update(call);
} }
return 0; return 0;
@ -947,6 +1008,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
if (!recvr_session && (debug & DEBUG_VOMP)) if (!recvr_session && (debug & DEBUG_VOMP))
DEBUG("recvr_session==0, created call"); DEBUG("recvr_session==0, created call");
recvr_state = call->local.state;
// TODO ignore state changes if sequence is stale? // TODO ignore state changes if sequence is stale?
// TODO ignore state changes that seem to go backwards? // TODO ignore state changes that seem to go backwards?
@ -954,17 +1016,17 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
if ((!vomp_interested_usock_count) if ((!vomp_interested_usock_count)
&&(!monitor_socket_count) &&(!monitor_socket_count)
&&(!monitor_client_interested(MONITOR_VOMP))) &&(!monitor_client_interested(MONITOR_VOMP)))
{ {
/* No registered listener, so we cannot answer the call, so just reject /* No registered listener, so we cannot answer the call, so just reject
it. */ it. */
if (debug & DEBUG_VOMP) if (debug & DEBUG_VOMP)
DEBUGF("Rejecting call due to lack of a listener: states=%d,%d", call->local.state,sender_state); DEBUGF("Rejecting call due to lack of a listener: states=%d,%d", recvr_state, sender_state);
call->local.state=VOMP_STATE_CALLENDED; recvr_state=VOMP_STATE_CALLENDED;
/* now let the state machine progress to destroy the call */ /* now let the state machine progress to destroy the call */
} }
if (call->local.state < VOMP_STATE_RINGINGOUT && sender_state < VOMP_STATE_RINGINGOUT){ if (recvr_state < VOMP_STATE_RINGINGOUT && sender_state < VOMP_STATE_RINGINGOUT){
// the other party should have given us their list of supported codecs // the other party should have given us their list of supported codecs
vomp_extract_remote_codec_list(call,mdp); vomp_extract_remote_codec_list(call,mdp);
} }
@ -974,7 +1036,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
so we must also move to CALLENDED no matter what state we were in */ so we must also move to CALLENDED no matter what state we were in */
if (call->audio_started) vomp_call_stop_audio(call); if (call->audio_started) vomp_call_stop_audio(call);
call->local.state=VOMP_STATE_CALLENDED; recvr_state=VOMP_STATE_CALLENDED;
} }
/* Consider states: our actual state, sender state, what the sender thinks /* Consider states: our actual state, sender state, what the sender thinks
@ -982,7 +1044,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
breaks down to what we think our state is, and what they think their breaks down to what we think our state is, and what they think their
state is. That leaves us with just 6X6=36 cases. state is. That leaves us with just 6X6=36 cases.
*/ */
int combined_state=call->local.state<<3 | sender_state; int combined_state=recvr_state<<3 | sender_state;
switch(combined_state) { switch(combined_state) {
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_CALLPREP: case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_CALLPREP:
@ -1000,7 +1062,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
if (call->initiated_call) if (call->initiated_call)
// hey, quit it, we were trying to call you. // hey, quit it, we were trying to call you.
call->local.state=VOMP_STATE_CALLENDED; recvr_state=VOMP_STATE_CALLENDED;
else{ else{
// Don't automatically transition to RINGIN, wait for a client to tell us when. // Don't automatically transition to RINGIN, wait for a client to tell us when.
} }
@ -1016,9 +1078,9 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
*/ */
if (call->initiated_call){ if (call->initiated_call){
// TODO fail the call if we can't agree on codec's // TODO fail the call if we can't agree on codec's
call->local.state=VOMP_STATE_RINGINGOUT; recvr_state=VOMP_STATE_RINGINGOUT;
}else{ }else{
call->local.state=VOMP_STATE_CALLENDED; recvr_state=VOMP_STATE_CALLENDED;
} }
break; break;
@ -1035,7 +1097,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_RINGINGOUT: case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_RINGINGOUT:
/* Woah, we're trying to dial each other?? That must have been well timed. /* Woah, we're trying to dial each other?? That must have been well timed.
Jump to INCALL and start audio */ Jump to INCALL and start audio */
call->local.state=VOMP_STATE_INCALL; recvr_state=VOMP_STATE_INCALL;
// reset create time when call is established // reset create time when call is established
call->create_time=gettime_ms(); call->create_time=gettime_ms();
break; break;
@ -1047,7 +1109,7 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_INCALL: case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_INCALL:
/* They have answered, we can jump to incall as well */ /* They have answered, we can jump to incall as well */
call->local.state=VOMP_STATE_INCALL; recvr_state=VOMP_STATE_INCALL;
// reset create time when call is established // reset create time when call is established
call->create_time=gettime_ms(); call->create_time=gettime_ms();
// Fall through // Fall through
@ -1073,11 +1135,14 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
Any state not explicitly listed above is considered invalid and possibly stale, Any state not explicitly listed above is considered invalid and possibly stale,
the packet will be completely ignored. the packet will be completely ignored.
*/ */
WHYF("Ignoring invalid call state %d.%d",sender_state,recvr_state);
return 0; return 0;
} }
call->remote.sequence=sender_seq; call->remote.sequence=sender_seq;
call->remote.state=sender_state;
vomp_update_remote_state(call, sender_state);
vomp_update_local_state(call, recvr_state);
call->last_activity=gettime_ms(); call->last_activity=gettime_ms();
// TODO if we hear a stale echo of our state should we force another outgoing packet now? // TODO if we hear a stale echo of our state should we force another outgoing packet now?
@ -1086,8 +1151,8 @@ int vomp_mdp_received(overlay_mdp_frame *mdp)
/* send an update to the call status if required */ /* send an update to the call status if required */
vomp_update(call); vomp_update(call);
if (call->remote.state==VOMP_STATE_CALLENDED if (sender_state==VOMP_STATE_CALLENDED
&&call->local.state==VOMP_STATE_CALLENDED) &&recvr_state==VOMP_STATE_CALLENDED)
return vomp_call_destroy(call); return vomp_call_destroy(call);
} }
return 0; return 0;
@ -1512,22 +1577,10 @@ static void vomp_process_tick(struct sched_ent *alarm)
slots getting full of cruft. */ slots getting full of cruft. */
vomp_call_destroy(call); vomp_call_destroy(call);
return; return;
} else if (call->last_activity+VOMP_CALL_TIMEOUT<now) } else if (call->last_activity+VOMP_CALL_TIMEOUT<now){
switch(call->local.state) /* Call timed out, so just immmediately tear the call down */
{ vomp_call_destroy(call);
case VOMP_STATE_INCALL: return;
/* Timeout while call in progress, so end call.
Keep call structure hanging around for a bit so that we can
synchonrise with the far end if possible. */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_stop_audio(call);
call->last_activity=now;
break;
default:
/* Call timed out while not actually in progress, so just immmediately
tear the call down */
vomp_call_destroy(call);
return;
} }
/* update everyone if the state has changed */ /* update everyone if the state has changed */