1333 lines
37 KiB
C
1333 lines
37 KiB
C
/**
|
|
* @file ecasoundc_sa.cpp Standalone C implementation of the
|
|
* ecasound control interface
|
|
*/
|
|
|
|
/* FIXME: add check for big sync-error -> ecasound probably
|
|
* died so better to give an error */
|
|
/* FIXME: add check for msgsize errors */
|
|
|
|
/** ------------------------------------------------------------------------
|
|
* ecasoundc.cpp: Standalone C implementation of the
|
|
* ecasound control interface
|
|
* Copyright (C) 2000-2006,2008,2009 Kai Vehmanen
|
|
* Copyright (C) 2003 Michael Ewe
|
|
* Copyright (C) 2001 Aymeric Jeanneau
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
* History of major changes:
|
|
*
|
|
* 2009-02-08 Kai Vehmanen
|
|
* - Finally got rid of the fixed-size parsing buffers.
|
|
* - Added handling (or proper ignoring) of SIGPIPE signals.
|
|
* 2006-12-06 Kai Vehmanen
|
|
* - Fixed severe string termination bug in handling lists of
|
|
* strings.
|
|
* - Fixed mechanism for waiting on grandchild ecasound process to exit.
|
|
* 2003-12-24 Michael Ewe
|
|
* - Fixed signaling issues on FreeBSD. Modified to perform a
|
|
* double-fork to better decouple ECI stack and the ecasound
|
|
* engine process.
|
|
* 2002-10-04 Kai Vehmanen
|
|
* - Rewritten as a standalone implementation.
|
|
* 2001-06-04 Aymeric Jeanneau
|
|
* - Added reentrant versions of all public ECI functions.
|
|
* 2000-12-06 Kai Vehmanen
|
|
* - Initial version.
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h> /* ANSI-C: printf(), ... */
|
|
#include <stdlib.h> /* ANSI-C: calloc(), free() */
|
|
#include <string.h> /* ANSI-C: strlen() */
|
|
#include <errno.h> /* ANSI-C: errno */
|
|
#include <stdbool.h>
|
|
|
|
#include <fcntl.h> /* POSIX: fcntl() */
|
|
#include <sys/poll.h> /* XPG4-UNIX: poll() */
|
|
#include <unistd.h> /* POSIX: pipe(), fork() */
|
|
#include <sys/stat.h> /* POSIX: stat() */
|
|
#include <sys/types.h> /* POSIX: fork() */
|
|
#include <sys/wait.h> /* POSIX: wait() */
|
|
#include <signal.h> /* POSIX: signal handling */
|
|
|
|
#include "ecasoundc.h"
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Options
|
|
*/
|
|
|
|
// #define ECI_ENABLE_DEBUG
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Definitions and constants
|
|
*/
|
|
|
|
#define ECI_PARSER_BUF_SIZE 65536
|
|
|
|
#define ECI_MAX_DYN_ALLOC_SIZE 16777216 /* assert if reached */
|
|
#define ECI_MAX_FLOAT_BUF_SIZE 32
|
|
#define ECI_MAX_RETURN_TYPE_SIZE 4
|
|
#define ECI_INI_STRING_SIZE 64
|
|
#define ECI_MAX_RESYNC_ATTEMPTS 9
|
|
#define ECI_MAX_LAST_COMMAND_SIZE 64
|
|
|
|
#define ECI_READ_TIMEOUT_MS 5000
|
|
#define ECI_READ_RETVAL_TIMEOUT_MS 30000
|
|
|
|
#define ECI_STATE_INIT 0
|
|
#define ECI_STATE_LOGLEVEL 1
|
|
#define ECI_STATE_MSGSIZE 2
|
|
#define ECI_STATE_COMMON_CR_1 3
|
|
#define ECI_STATE_COMMON_LF_1 4
|
|
#define ECI_STATE_RET_TYPE 5
|
|
#define ECI_STATE_COMMON_CONTENT 6
|
|
#define ECI_STATE_COMMON_CR_2 7
|
|
#define ECI_STATE_COMMON_LF_2 8
|
|
#define ECI_STATE_COMMON_CR_3 9
|
|
#define ECI_STATE_COMMON_LF_3 10
|
|
#define ECI_STATE_SEEK_TO_LF 11
|
|
|
|
#define ECI_STATE_MSG_GEN 0
|
|
#define ECI_STATE_MSG_RETURN 1
|
|
|
|
#define ECI_TOKEN_PHASE_NONE 0
|
|
#define ECI_TOKEN_PHASE_READING 1
|
|
#define ECI_TOKEN_PHASE_VALIDATE 2
|
|
|
|
#define ECI_RETURN_TYPE_LOGLEVEL 256
|
|
|
|
#ifdef ECI_ENABLE_DEBUG
|
|
#define ECI_DEBUG(x) fprintf(stderr,x)
|
|
#define ECI_DEBUG_1(x,y) fprintf(stderr,x,y)
|
|
#define ECI_DEBUG_2(x,y,z) fprintf(stderr,x,y,z)
|
|
#define ECI_DEBUG_3(x,y,z,t) fprintf(stderr,x,y,z,t)
|
|
#else
|
|
#define ECI_DEBUG(x) ((void) 0)
|
|
#define ECI_DEBUG_1(x,y) ((void) 0)
|
|
#define ECI_DEBUG_2(x,y,z) ((void) 0)
|
|
#define ECI_DEBUG_3(x,y,z,t) ((void) 0)
|
|
#endif
|
|
|
|
#define DBC_REQUIRE(expr) \
|
|
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_REQUIRE failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
|
|
#define DBC_ENSURE(expr) \
|
|
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_ENSURE failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
|
|
#define DBC_CHECK(expr) \
|
|
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_CHECK failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
|
|
#define DBC_DECLARE(expr) expr
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Data structures
|
|
*/
|
|
|
|
struct eci_string_s {
|
|
char *d; /* buffer to string contents */
|
|
int slen; /* string length in octets, including terminating null */
|
|
int size; /* buffer length in octets, including terminating null */
|
|
};
|
|
typedef struct eci_string_s eci_string;
|
|
|
|
struct eci_los_list {
|
|
struct eci_los_list* prev_repp;
|
|
struct eci_los_list* next_repp;
|
|
eci_string data_repp;
|
|
};
|
|
|
|
struct eci_parser {
|
|
|
|
int state_rep;
|
|
int state_msg_rep;
|
|
|
|
double last_f_rep;
|
|
long int last_li_rep;
|
|
int last_i_rep;
|
|
int last_counter_rep;
|
|
char last_type_repp[ECI_MAX_RETURN_TYPE_SIZE];
|
|
struct eci_los_list* last_los_repp;
|
|
eci_string last_error_repp;
|
|
eci_string last_s_repp;
|
|
|
|
eci_string buffer_rep;
|
|
|
|
int msgsize_rep;
|
|
int loglevel_rep;
|
|
|
|
int token_phase_rep;
|
|
int buffer_current_rep;
|
|
|
|
bool sync_lost_rep;
|
|
};
|
|
|
|
struct eci_internal {
|
|
int pid_of_child_rep;
|
|
int pid_of_parent_rep;
|
|
int cmd_read_fd_rep;
|
|
int cmd_write_fd_rep;
|
|
|
|
char last_command_repp[ECI_MAX_LAST_COMMAND_SIZE];
|
|
int commands_counter_rep;
|
|
|
|
struct eci_parser* parser_repp;
|
|
|
|
char farg_buf_repp[ECI_MAX_FLOAT_BUF_SIZE];
|
|
char raw_buffer_repp[ECI_PARSER_BUF_SIZE];
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Global variables
|
|
*/
|
|
|
|
static eci_handle_t static_eci_rep = 0;
|
|
|
|
/**
|
|
* Message shown if ECASOUND is not defined.
|
|
*/
|
|
const char* eci_str_no_ecasound_env =
|
|
"\n"
|
|
"***********************************************************************\n"
|
|
"* Message from libecasoundc:\n"
|
|
"* \n"
|
|
"* 'ECASOUND' environment variable not set. Using the default value \n"
|
|
"* value 'ECASOUND=ecasound'.\n"
|
|
"***********************************************************************\n"
|
|
"\n";
|
|
|
|
const char* eci_str_null_handle =
|
|
"\n"
|
|
"***********************************************************************\n"
|
|
"* Message from libecasoundc:\n"
|
|
"* \n"
|
|
"* A null client handle detected. This is usually caused by a bug \n"
|
|
"* in the ECI application. Please report this bug to the author of\n"
|
|
"* the program.\n"
|
|
"***********************************************************************\n"
|
|
"\n";
|
|
|
|
const char* eci_str_sync_lost =
|
|
"\n"
|
|
"***********************************************************************\n"
|
|
"* Message from libecasoundc:\n"
|
|
"* \n"
|
|
"* Connection to the processing engine was lost. Check that ecasound \n"
|
|
"* is correctly installed. Also make sure that ecasound is either \n"
|
|
"* in some directory listed in PATH, or the environment variable\n"
|
|
"* 'ECASOUND' contains the path to a working ecasound executable.\n"
|
|
"***********************************************************************\n"
|
|
"\n";
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Declarations of static functions
|
|
*/
|
|
|
|
static void eci_impl_check_handle(struct eci_internal* eci_rep);
|
|
static void eci_impl_free_parser(struct eci_internal* eci_rep);
|
|
static void eci_impl_clean_last_values(struct eci_parser* parser);
|
|
static void eci_impl_dump_parser_state(eci_handle_t ptr, const char* message);
|
|
static ssize_t eci_impl_fd_read(int fd, void *buf, size_t count, int timeout);
|
|
static const char* eci_impl_get_ecasound_path(void);
|
|
static struct eci_los_list *eci_impl_los_list_add_item(struct eci_los_list* headptr, char* stmp, int len);
|
|
static struct eci_los_list *eci_impl_los_list_alloc_item(void);
|
|
static void eci_impl_los_list_clear(struct eci_los_list *ptr);
|
|
static void eci_impl_read_return_value(struct eci_internal* eci_rep, int timeout);
|
|
static void eci_impl_set_last_los_value(struct eci_parser* parser);
|
|
static void eci_impl_set_last_values(struct eci_parser* parser);
|
|
static void eci_impl_update_state(struct eci_parser* eci_rep, char c);
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Constructing and destructing
|
|
*/
|
|
|
|
/**
|
|
* Initializes session. This call clears all status info and
|
|
* prepares ecasound for processing. Can be used to "restart"
|
|
* the library.
|
|
*/
|
|
void eci_init(void)
|
|
{
|
|
DBC_CHECK(static_eci_rep == NULL);
|
|
static_eci_rep = eci_init_r();
|
|
}
|
|
|
|
/**
|
|
* Initializes session. This call creates a new ecasound
|
|
* instance and prepares it for processing.
|
|
*
|
|
* @return NULL if initialization fails
|
|
*/
|
|
eci_handle_t eci_init_r(void)
|
|
{
|
|
struct eci_internal* eci_rep = NULL;
|
|
int cmd_send_pipe[2], cmd_receive_pipe[2];
|
|
const char* ecasound_exec = eci_impl_get_ecasound_path();
|
|
|
|
/* step: launch ecasound process and setup two-way communication */
|
|
if (ecasound_exec != NULL &&
|
|
(pipe(cmd_receive_pipe) == 0 && pipe(cmd_send_pipe) == 0)) {
|
|
int fork_pid = fork();
|
|
/* step: 1st fork */
|
|
if (fork_pid == 0) {
|
|
/* first child (phase-1) */
|
|
|
|
/* -c = interactive mode, -D = direct prompts and banners to stderr */
|
|
const char* args[4] = { NULL, "-c", "-D", NULL };
|
|
int res = 0;
|
|
struct sigaction sa;
|
|
pid_t pid;
|
|
|
|
sa.sa_handler=SIG_IGN;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags=0;
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
setsid();
|
|
|
|
/* step: 2nd fork (to detach from parent) */
|
|
if (fork() != 0)
|
|
_exit(0); /* first child terminates here (phase-2) */
|
|
|
|
/* second child continues (phase-2) */
|
|
|
|
args[0] = ecasound_exec;
|
|
|
|
/* close all unused descriptors and resources */
|
|
|
|
close(0);
|
|
close(1);
|
|
|
|
dup2(cmd_send_pipe[0], 0);
|
|
dup2(cmd_receive_pipe[1], 1);
|
|
|
|
close(cmd_receive_pipe[0]);
|
|
close(cmd_receive_pipe[1]);
|
|
close(cmd_send_pipe[0]);
|
|
close(cmd_send_pipe[1]);
|
|
|
|
freopen("/dev/null", "w", stderr);
|
|
|
|
/* step: write second child's pid to the (grand) parent */
|
|
pid = getpid();
|
|
write(1, &pid, sizeof(pid));
|
|
|
|
/* step: notify the parent that we're up */
|
|
res = write(1, args, 1);
|
|
|
|
res = execvp(args[0], (char**)args);
|
|
if (res < 0) printf("(ecasoundc_sa) launching ecasound FAILED!\n");
|
|
|
|
close(0);
|
|
close(1);
|
|
|
|
_exit(res);
|
|
ECI_DEBUG("(ecasoundc_sa) You shouldn't see this!\n");
|
|
}
|
|
else {
|
|
/* step: parent (phase-1) */
|
|
int res;
|
|
char buf[1];
|
|
int status;
|
|
int pid;
|
|
|
|
/* set up signal handling */
|
|
struct sigaction ign_handler;
|
|
ign_handler.sa_handler = SIG_IGN;
|
|
sigemptyset(&ign_handler.sa_mask);
|
|
ign_handler.sa_flags = 0;
|
|
/* ignore the following signals */
|
|
sigaction(SIGPIPE, &ign_handler, 0);
|
|
sigaction(SIGFPE, &ign_handler, 0);
|
|
|
|
eci_rep = (struct eci_internal*)calloc(1, sizeof(struct eci_internal));
|
|
eci_rep->parser_repp = (struct eci_parser*)calloc(1, sizeof(struct eci_parser));
|
|
|
|
/* step: initialize variables */
|
|
eci_rep->pid_of_child_rep = fork_pid;
|
|
eci_rep->commands_counter_rep = 0;
|
|
eci_rep->parser_repp->last_counter_rep = 0;
|
|
eci_rep->parser_repp->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
eci_rep->parser_repp->buffer_current_rep = 0;
|
|
eci_rep->parser_repp->sync_lost_rep = false;
|
|
eci_impl_clean_last_values(eci_rep->parser_repp);
|
|
|
|
/*
|
|
waits for first child to prevent the zombie
|
|
read grand child prozess id from pipe
|
|
*/
|
|
waitpid(eci_rep->pid_of_child_rep, &status, 0);
|
|
res = read(cmd_receive_pipe[0], &pid, sizeof(pid));
|
|
if ( res != sizeof(pid) ) {
|
|
ECI_DEBUG_1("(ecasoundc_sa) fork() of %s FAILED!\n", ecasound_exec);
|
|
eci_impl_free_parser(eci_rep);
|
|
free(eci_rep);
|
|
eci_rep = NULL;
|
|
}
|
|
eci_rep->pid_of_child_rep = pid;
|
|
eci_rep->pid_of_parent_rep = getpid();
|
|
|
|
eci_rep->cmd_read_fd_rep = cmd_receive_pipe[0];
|
|
close(cmd_receive_pipe[1]);
|
|
eci_rep->cmd_write_fd_rep = cmd_send_pipe[1];
|
|
close(cmd_send_pipe[0]);
|
|
|
|
/* step: switch to non-blocking mode for read */
|
|
fcntl(eci_rep->cmd_read_fd_rep, F_SETFL, O_NONBLOCK);
|
|
fcntl(eci_rep->cmd_write_fd_rep, F_SETFL, O_NONBLOCK);
|
|
|
|
/* step: check that fork succeeded() */
|
|
res = eci_impl_fd_read(eci_rep->cmd_read_fd_rep, buf, 1, ECI_READ_TIMEOUT_MS);
|
|
if (res != 1) {
|
|
ECI_DEBUG_1("(ecasoundc_sa) fork() of %s FAILED!\n", ecasound_exec);
|
|
eci_impl_free_parser(eci_rep);
|
|
free(eci_rep);
|
|
eci_rep = NULL;
|
|
}
|
|
else {
|
|
write(eci_rep->cmd_write_fd_rep, "debug 256\n", strlen("debug 256\n"));
|
|
write(eci_rep->cmd_write_fd_rep, "int-set-float-to-string-precision 17\n", strlen("int-set-float-to-string-precision 17\n"));
|
|
write(eci_rep->cmd_write_fd_rep, "int-output-mode-wellformed\n", strlen("int-output-mode-wellformed\n"));
|
|
eci_rep->commands_counter_rep ++;
|
|
|
|
/* step: check that exec() succeeded */
|
|
eci_impl_read_return_value(eci_rep, ECI_READ_TIMEOUT_MS);
|
|
if (eci_rep->commands_counter_rep != eci_rep->parser_repp->last_counter_rep) {
|
|
ECI_DEBUG_3("(ecasoundc_sa) exec() of %s FAILED (%d=%d)!\n", ecasound_exec, eci_rep->commands_counter_rep, eci_rep->parser_repp->last_counter_rep);
|
|
eci_impl_free_parser(eci_rep);
|
|
free(eci_rep);
|
|
eci_rep = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (eci_handle_t)eci_rep;
|
|
}
|
|
|
|
/**
|
|
* Checks whether ECI is ready for use.
|
|
*
|
|
* @return non-zero if ready, zero otherwise
|
|
*/
|
|
int eci_ready(void)
|
|
{
|
|
return eci_ready_r(static_eci_rep);
|
|
}
|
|
|
|
/**
|
|
* Checks whether ECI is ready for use.
|
|
*
|
|
* @return non-zero if ready, zero otherwise
|
|
*/
|
|
int eci_ready_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
if (!ptr)
|
|
return 0;
|
|
|
|
if (eci_rep->pid_of_child_rep <= 0 ||
|
|
eci_rep->cmd_read_fd_rep < 0 ||
|
|
eci_rep->cmd_write_fd_rep < 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Frees all resources.
|
|
*/
|
|
void eci_cleanup(void)
|
|
{
|
|
if (static_eci_rep != NULL) {
|
|
eci_cleanup_r(static_eci_rep);
|
|
static_eci_rep = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Frees all resources.
|
|
*/
|
|
void eci_cleanup_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
ssize_t resread = 1, respoll;
|
|
char buf[1];
|
|
struct pollfd fds[1];
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
ECI_DEBUG("\n(ecasoundc_sa) requesting to terminatte ecasound process.\n");
|
|
|
|
write(eci_rep->cmd_write_fd_rep, "quit\n", strlen("quit\n"));
|
|
eci_rep->commands_counter_rep++;
|
|
|
|
/* as we use double-fork, we cannot use waitpid() --
|
|
* to block until ecasound grandchild has exited, we
|
|
* use a combination of poll+read(),
|
|
* ref: http://www.greenend.org.uk/rjk/2001/06/poll.html
|
|
*/
|
|
|
|
ECI_DEBUG_1("\n(ecasoundc_sa) cleaning up. waiting for grandchild ecasound process %d.\n", eci_rep->pid_of_child_rep);
|
|
|
|
while (resread > 0) {
|
|
fds[0].fd = eci_rep->cmd_read_fd_rep;
|
|
fds[0].events = POLLIN;
|
|
fds[0].revents = 0;
|
|
respoll = poll(fds, 1, ECI_READ_RETVAL_TIMEOUT_MS);
|
|
if (fds[0].revents & (POLLIN | POLLHUP))
|
|
resread = read(eci_rep->cmd_read_fd_rep, buf, 1);
|
|
else if (fds[0].revents & POLLERR)
|
|
resread = -2;
|
|
|
|
ECI_DEBUG_3("(ecasoundc_sa) waiting for ecasound, poll=%d, read=%d, revents=0x%02x)\n", respoll, resread, fds[0].revents);
|
|
}
|
|
|
|
ECI_DEBUG("(ecasoundc_sa) child exited\n");
|
|
|
|
if (eci_rep != 0) {
|
|
/* close descriptors */
|
|
close(eci_rep->cmd_read_fd_rep);
|
|
close(eci_rep->cmd_write_fd_rep);
|
|
|
|
/* free lists of strings, if any */
|
|
eci_impl_clean_last_values(eci_rep->parser_repp);
|
|
|
|
eci_impl_free_parser(eci_rep);
|
|
free(eci_rep);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Issuing EIAM commands
|
|
*/
|
|
|
|
/**
|
|
* Sends a command to the ecasound engine. See ecasound-iam(5) for
|
|
* more info.
|
|
*/
|
|
void eci_command(const char* command) { eci_command_r(static_eci_rep, command); }
|
|
|
|
/**
|
|
* Sends a command to the ecasound engine. See ecasound-iam(5) for
|
|
* more info.
|
|
*/
|
|
void eci_command_r(eci_handle_t ptr, const char* command)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
int timeout = ECI_READ_RETVAL_TIMEOUT_MS;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
if (eci_ready_r(ptr) == 0) {
|
|
ECI_DEBUG("(ecasoundc_sa) not ready, unable to process commands\n");
|
|
return;
|
|
}
|
|
|
|
ECI_DEBUG_2("(ecasoundc_sa) writing command '%s' (cmd-counter=%d).\n",
|
|
command, eci_rep->commands_counter_rep + 1);
|
|
|
|
memcpy(eci_rep->last_command_repp, command, ECI_MAX_LAST_COMMAND_SIZE);
|
|
|
|
eci_impl_clean_last_values(eci_rep->parser_repp);
|
|
|
|
write(eci_rep->cmd_write_fd_rep, command, strlen(command));
|
|
write(eci_rep->cmd_write_fd_rep, "\n", 1);
|
|
|
|
/* 'run' is the only blocking function */
|
|
if (strncmp(command, "run", 3) == 0) {
|
|
ECI_DEBUG("(ecasoundc_sa) 'run' detected; disabling reply timeout!\n");
|
|
timeout = -1;
|
|
}
|
|
|
|
eci_rep->commands_counter_rep++;
|
|
|
|
if (eci_rep->commands_counter_rep - 1 !=
|
|
eci_rep->parser_repp->last_counter_rep) {
|
|
eci_impl_dump_parser_state(ptr, "sync error");
|
|
eci_rep->parser_repp->sync_lost_rep = true;
|
|
}
|
|
|
|
if (eci_rep->commands_counter_rep >=
|
|
eci_rep->parser_repp->last_counter_rep) {
|
|
eci_impl_read_return_value(eci_rep, timeout);
|
|
}
|
|
|
|
ECI_DEBUG_2("(ecasoundc_sa) set return value type='%s' (read-counter=%d).\n",
|
|
eci_rep->parser_repp->last_type_repp, eci_rep->parser_repp->last_counter_rep);
|
|
|
|
if (eci_rep->commands_counter_rep >
|
|
eci_rep->parser_repp->last_counter_rep) {
|
|
fprintf(stderr, "%s", eci_str_sync_lost);
|
|
eci_rep->parser_repp->sync_lost_rep = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A specialized version of 'eci_command()' taking a double value
|
|
* as the 2nd argument.
|
|
*/
|
|
void eci_command_float_arg(const char* command, double arg) { eci_command_float_arg_r(static_eci_rep, command, arg); }
|
|
|
|
/**
|
|
* A specialized version of 'eci_command()' taking a double value
|
|
* as the 2nd argument.
|
|
*/
|
|
void eci_command_float_arg_r(eci_handle_t ptr, const char* command, double arg)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
snprintf(eci_rep->farg_buf_repp, ECI_MAX_FLOAT_BUF_SIZE-1, "%s %.32f", command, arg);
|
|
eci_command_r(ptr, eci_rep->farg_buf_repp);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Getting return values
|
|
*/
|
|
|
|
/**
|
|
* Returns the number of strings returned by the
|
|
* last ECI command.
|
|
*/
|
|
int eci_last_string_list_count(void) { return(eci_last_string_list_count_r(static_eci_rep)); }
|
|
|
|
/**
|
|
* Returns the number of strings returned by the
|
|
* last ECI command.
|
|
*/
|
|
int eci_last_string_list_count_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
struct eci_los_list* i;
|
|
int count = 0;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
for(i = eci_rep->parser_repp->last_los_repp;
|
|
i != NULL;
|
|
i = i->next_repp) {
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Returns the nth item of the list containing
|
|
* strings returned by the last ECI command.
|
|
*
|
|
* require:
|
|
* n >= 0 && n < eci_last_string_list_count()
|
|
*/
|
|
const char* eci_last_string_list_item(int n) { return(eci_last_string_list_item_r(static_eci_rep, n)); }
|
|
|
|
/**
|
|
* Returns the nth item of the list containing
|
|
* strings returned by the last ECI command.
|
|
*
|
|
* require:
|
|
* n >= 0 && n < eci_last_string_list_count()
|
|
*/
|
|
const char* eci_last_string_list_item_r(eci_handle_t ptr, int n)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
struct eci_los_list* i;
|
|
int count = 0;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
for(i = eci_rep->parser_repp->last_los_repp;
|
|
i != NULL;
|
|
i = i->next_repp) {
|
|
if (count++ == n) {
|
|
return i->data_repp.d;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char* eci_last_string(void) { return(eci_last_string_r(static_eci_rep)); }
|
|
|
|
const char* eci_last_string_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_s_repp.d;
|
|
}
|
|
|
|
double eci_last_float(void) { return(eci_last_float_r(static_eci_rep)); }
|
|
|
|
double eci_last_float_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_f_rep;
|
|
}
|
|
|
|
int eci_last_integer(void) { return(eci_last_integer_r(static_eci_rep)); }
|
|
|
|
int eci_last_integer_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_i_rep;
|
|
}
|
|
|
|
long int eci_last_long_integer(void) { return(eci_last_long_integer_r(static_eci_rep)); }
|
|
|
|
long int eci_last_long_integer_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_li_rep;
|
|
}
|
|
|
|
/**
|
|
* Returns pointer to a null-terminated string containing
|
|
* information about the last occured error.
|
|
*/
|
|
const char* eci_last_error(void) { return(eci_last_error_r(static_eci_rep)); }
|
|
|
|
/**
|
|
* Returns pointer to a null-terminated string containing
|
|
* information about the last occured error.
|
|
*/
|
|
const char* eci_last_error_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_error_repp.d;
|
|
}
|
|
|
|
|
|
const char* eci_last_type(void) { return(eci_last_type_r(static_eci_rep)); }
|
|
|
|
const char* eci_last_type_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
return eci_rep->parser_repp->last_type_repp;
|
|
}
|
|
|
|
/**
|
|
* Whether an error has occured?
|
|
*
|
|
* @return zero if not in error state
|
|
*/
|
|
int eci_error(void) { return(eci_error_r(static_eci_rep)); }
|
|
|
|
/**
|
|
* Whether an error has occured?
|
|
*
|
|
* @return zero if not in error state
|
|
*/
|
|
int eci_error_r(eci_handle_t ptr)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
int res;
|
|
|
|
eci_impl_check_handle(eci_rep);
|
|
|
|
if (eci_ready_r(ptr) == 0) {
|
|
ECI_DEBUG("(ecasoundc_sa) not ready, raising an error\n");
|
|
return 1;
|
|
}
|
|
|
|
if (eci_rep->parser_repp->sync_lost_rep == true) {
|
|
ECI_DEBUG("(ecasoundc_sa) sync lost, raising an error\n");
|
|
return 1;
|
|
}
|
|
|
|
res = (eci_rep->parser_repp->last_type_repp[0] == 'e') ? 1 : 0;
|
|
|
|
ECI_DEBUG_1("(ecasoundc_sa) checking for error, returning %d", res);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Events
|
|
*/
|
|
|
|
int eci_events_available(void) { return(eci_events_available_r(static_eci_rep)); }
|
|
int eci_events_available_r(eci_handle_t ptr) { return(0); }
|
|
void eci_next_event(void) { eci_next_event_r(static_eci_rep); }
|
|
void eci_next_event_r(eci_handle_t ptr) { }
|
|
const char* eci_current_event(void) { return(eci_current_event_r(static_eci_rep)); }
|
|
const char* eci_current_event_r(eci_handle_t ptr) { return(0); }
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Implementation of static functions
|
|
*/
|
|
|
|
static void eci_string_add(eci_string *dst, int at, char const *src, int len);
|
|
|
|
/**
|
|
* Clears the string contents.
|
|
*
|
|
* @post eci_string_len(str)==0
|
|
* @post strlen(str->d)==0
|
|
*/
|
|
static void eci_string_clear(eci_string *str)
|
|
{
|
|
DBC_CHECK(str);
|
|
str->slen = 0;
|
|
if (str->size == 0)
|
|
eci_string_add(str, 0, NULL, 0);
|
|
else
|
|
str->d[0] = 0;
|
|
DBC_CHECK(str->d[0] == 0);
|
|
}
|
|
|
|
/**
|
|
* Initializes the string object for use.
|
|
* This must only be called after initial
|
|
* object allocation.
|
|
*/
|
|
static void eci_string_init(eci_string *str)
|
|
{
|
|
DBC_CHECK(str);
|
|
str->slen = 0;
|
|
str->size = 0;
|
|
str->d = 0;
|
|
}
|
|
|
|
static void eci_string_free(eci_string *str)
|
|
{
|
|
DBC_CHECK(str);
|
|
free(str->d);
|
|
str->size = 0;
|
|
str->slen = 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the string length.
|
|
*/
|
|
static int eci_string_len(eci_string *str)
|
|
{
|
|
DBC_CHECK(str);
|
|
return str->slen;
|
|
}
|
|
|
|
/**
|
|
* Adds 'len' octets from buffer 'src' to the string
|
|
* at position 'at' (position 0 being the first character).
|
|
*/
|
|
static void eci_string_add(eci_string *dst, int at, char const *src, int len)
|
|
{
|
|
int space_needed = at + len + 1;
|
|
|
|
DBC_CHECK(dst);
|
|
|
|
if (space_needed > dst->size) {
|
|
int newsize =
|
|
dst->size ? dst->size * 2 : ECI_INI_STRING_SIZE;
|
|
char *newbuf;
|
|
while (space_needed > newsize) {
|
|
newsize *= 2;
|
|
}
|
|
assert(newsize <= ECI_MAX_DYN_ALLOC_SIZE);
|
|
newbuf = realloc(dst->d, newsize);
|
|
assert(newbuf);
|
|
dst->size = newsize;
|
|
dst->d = newbuf;
|
|
}
|
|
|
|
DBC_CHECK(space_needed <= dst->size);
|
|
|
|
memcpy(&dst->d[at], src, len);
|
|
dst->d[at + len] = 0;
|
|
}
|
|
|
|
static void eci_impl_check_handle(struct eci_internal* eci_rep)
|
|
{
|
|
if (eci_rep == NULL) {
|
|
fprintf(stderr, "%s", eci_str_null_handle);
|
|
DBC_CHECK(eci_rep != NULL);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
static void eci_impl_free_parser(struct eci_internal* eci_rep)
|
|
{
|
|
DBC_CHECK(eci_rep);
|
|
eci_string_free(&eci_rep->parser_repp->last_error_repp);
|
|
eci_string_free(&eci_rep->parser_repp->last_s_repp);
|
|
eci_string_free(&eci_rep->parser_repp->buffer_rep);
|
|
free(eci_rep->parser_repp);
|
|
eci_rep->parser_repp = 0;
|
|
}
|
|
|
|
static void eci_impl_clean_last_values(struct eci_parser* parser)
|
|
{
|
|
DBC_CHECK(parser != 0);
|
|
|
|
eci_impl_los_list_clear(parser->last_los_repp);
|
|
parser->last_los_repp = NULL;
|
|
parser->last_i_rep = 0;
|
|
parser->last_li_rep = 0;
|
|
parser->last_f_rep = 0.0f;
|
|
eci_string_clear(&parser->last_error_repp);
|
|
eci_string_clear(&parser->last_s_repp);
|
|
}
|
|
|
|
static void eci_impl_dump_parser_state(eci_handle_t ptr, const char* message)
|
|
{
|
|
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
|
|
|
|
fprintf(stderr, "\n(ecasoundc_sa) Error='%s', cmd='%s' last_error='%s' cmd_cnt=%d last_cnt=%d.\n",
|
|
message,
|
|
eci_rep->last_command_repp,
|
|
eci_last_error_r(ptr),
|
|
eci_rep->commands_counter_rep,
|
|
eci_rep->parser_repp->last_counter_rep);
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to read up to 'count' bytes from file descriptor 'fd'
|
|
* into the buffer starting at 'buf'. If no data is available
|
|
* for reading, up to 'timeout' milliseconds will be waited.
|
|
* A negative value means infinite timeout.
|
|
*/
|
|
static ssize_t eci_impl_fd_read(int fd, void *buf, size_t count, int timeout)
|
|
{
|
|
int nfds = 1;
|
|
struct pollfd ufds;
|
|
ssize_t rescount = 0;
|
|
int ret;
|
|
|
|
ufds.fd = fd;
|
|
ufds.events = POLLIN | POLLPRI;
|
|
ufds.revents = 0;
|
|
|
|
ret = poll(&ufds, nfds, timeout);
|
|
if (ret > 0) {
|
|
if (ufds.revents & POLLIN ||
|
|
ufds.revents & POLLPRI) {
|
|
rescount = read(fd, buf, count);
|
|
}
|
|
}
|
|
else if (ret == 0) {
|
|
/* timeout */
|
|
rescount = -1;
|
|
}
|
|
return rescount;
|
|
}
|
|
|
|
static const char* eci_impl_get_ecasound_path(void)
|
|
{
|
|
const char* result = getenv("ECASOUND");
|
|
|
|
if (result == NULL) {
|
|
fprintf(stderr, "%s", eci_str_no_ecasound_env);
|
|
result = "ecasound";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static struct eci_los_list *eci_impl_los_list_add_item(struct eci_los_list* head, char* stmp, int len)
|
|
{
|
|
struct eci_los_list* i = head;
|
|
struct eci_los_list* last = NULL;
|
|
|
|
/* find end of list */
|
|
while(i != NULL) {
|
|
last = i;
|
|
i = i->next_repp;
|
|
}
|
|
|
|
/* add to the end, copy data */
|
|
i = eci_impl_los_list_alloc_item();
|
|
eci_string_add(&i->data_repp, 0, stmp, len);
|
|
if (last != NULL) last->next_repp = i;
|
|
|
|
/* ECI_DEBUG_3("(ecasoundc_sa) adding item '%s' to los list; head=%p, i=%p\n", stmp, (void*)head, (void*)i); */
|
|
|
|
/* created a new list, return the new item */
|
|
if (head == NULL)
|
|
return i;
|
|
|
|
/* return the old head */
|
|
return head;
|
|
}
|
|
|
|
struct eci_los_list *eci_impl_los_list_alloc_item(void)
|
|
{
|
|
struct eci_los_list *item;
|
|
/* ECI_DEBUG("(ecasoundc_sa) list alloc item\n"); */
|
|
item = (struct eci_los_list*)calloc(1, sizeof(struct eci_los_list));
|
|
DBC_CHECK(item != NULL);
|
|
item->next_repp = item->prev_repp = NULL;
|
|
eci_string_clear(&item->data_repp);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void eci_impl_los_list_clear(struct eci_los_list *ptr)
|
|
{
|
|
struct eci_los_list *i = ptr;
|
|
|
|
ECI_DEBUG_1("(ecasoundc_sa) clearing list, i=%p\n", (void*)i);
|
|
|
|
while(i != NULL) {
|
|
/* ECI_DEBUG_1("(ecasoundc_sa) freeing list item %p\n", (void*)i); */
|
|
struct eci_los_list* next = i->next_repp;
|
|
eci_string_free(&i->data_repp);
|
|
free(i);
|
|
i = next;
|
|
}
|
|
}
|
|
|
|
static void eci_impl_read_return_value(struct eci_internal* eci_rep, int timeout)
|
|
{
|
|
char* raw_buffer = eci_rep->raw_buffer_repp;
|
|
int attempts = 0;
|
|
|
|
DBC_CHECK(eci_rep->commands_counter_rep >=
|
|
eci_rep->parser_repp->last_counter_rep);
|
|
|
|
while(attempts < ECI_MAX_RESYNC_ATTEMPTS) {
|
|
int res = eci_impl_fd_read(eci_rep->cmd_read_fd_rep, raw_buffer, ECI_PARSER_BUF_SIZE-1, timeout);
|
|
if (res > 0) {
|
|
int n;
|
|
|
|
raw_buffer[res] = 0;
|
|
/* ECI_DEBUG_2("\n(ecasoundc_sa) read %u bytes:\n--cut--\n%s\n--cut--\n", res, raw_buffer); */
|
|
|
|
for(n = 0; n < res; n++) {
|
|
/* int old = eci_rep->parser_repp->state_rep; */
|
|
eci_impl_update_state(eci_rep->parser_repp, raw_buffer[n]);
|
|
/* if (old != eci_rep->parser_repp->state_rep) ECI_DEBUG_3("state change %d-%d, c=[%02X].\n", old, eci_rep->parser_repp->state_rep, raw_buffer[n]); */
|
|
}
|
|
|
|
if (eci_rep->commands_counter_rep ==
|
|
eci_rep->parser_repp->last_counter_rep) break;
|
|
|
|
/* read return values until the correct one is found */
|
|
}
|
|
else {
|
|
if (res < 0) {
|
|
ECI_DEBUG_1("(ecasoundc_sa) timeout when reading return values (attempts=%d)!\n", attempts);
|
|
eci_rep->parser_repp->sync_lost_rep = true;
|
|
break;
|
|
}
|
|
}
|
|
++attempts;
|
|
}
|
|
|
|
if (eci_rep->commands_counter_rep !=
|
|
eci_rep->parser_repp->last_counter_rep) {
|
|
eci_impl_dump_parser_state(eci_rep, "read() error");
|
|
eci_rep->parser_repp->sync_lost_rep = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the last 'list of strings' values.
|
|
*
|
|
* @pre parser != 0
|
|
* @pre parser->state_rep == ECI_STATE_COMMON_LF_3
|
|
*/
|
|
static void eci_impl_set_last_los_value(struct eci_parser* parser)
|
|
{
|
|
struct eci_los_list* i = parser->last_los_repp;
|
|
int quoteflag = 0, m = 0, n;
|
|
eci_string stmp;
|
|
eci_string_init(&stmp);
|
|
|
|
DBC_CHECK(parser != 0);
|
|
DBC_CHECK(parser->state_rep == ECI_STATE_COMMON_LF_3);
|
|
|
|
ECI_DEBUG_2("(ecasoundc_sa) parsing a list '%s' (count=%d)\n", parser->buffer_rep.d, parser->buffer_current_rep);
|
|
|
|
eci_impl_los_list_clear(i);
|
|
parser->last_los_repp = NULL;
|
|
|
|
for(n = 0; n < parser->buffer_current_rep && n < parser->msgsize_rep; n++) {
|
|
char c = parser->buffer_rep.d[n];
|
|
|
|
if (c == '\"') {
|
|
quoteflag = !quoteflag;
|
|
}
|
|
else if (c == '\\') {
|
|
n++;
|
|
eci_string_add(&stmp, m++, &parser->buffer_rep.d[n], 1);
|
|
}
|
|
else if (c != ',' || quoteflag == 1) {
|
|
eci_string_add(&stmp, m++, &parser->buffer_rep.d[n], 1);
|
|
}
|
|
else {
|
|
if (m == 0) continue;
|
|
i = eci_impl_los_list_add_item(i, stmp.d, m);
|
|
m = 0;
|
|
}
|
|
}
|
|
if (m > 0) {
|
|
i = eci_impl_los_list_add_item(i, stmp.d, m);
|
|
}
|
|
|
|
parser->last_los_repp = i;
|
|
|
|
eci_string_free(&stmp);
|
|
}
|
|
|
|
/**
|
|
* Sets the 'last value' fields in the given 'parser'
|
|
* object.
|
|
*
|
|
* @pre parser != 0
|
|
* @pre parser->state_rep == ECI_STATE_COMMON_LF_3
|
|
*/
|
|
static void eci_impl_set_last_values(struct eci_parser* parser)
|
|
{
|
|
DBC_CHECK(parser != 0);
|
|
DBC_CHECK(parser->state_rep == ECI_STATE_COMMON_LF_3);
|
|
|
|
switch(parser->last_type_repp[0])
|
|
{
|
|
case 's':
|
|
eci_string_add(&parser->last_s_repp, 0, parser->buffer_rep.d, parser->buffer_current_rep);
|
|
break;
|
|
|
|
case 'S':
|
|
eci_impl_set_last_los_value(parser);
|
|
break;
|
|
|
|
case 'i':
|
|
parser->last_i_rep = atoi(parser->buffer_rep.d);
|
|
break;
|
|
|
|
case 'l':
|
|
parser->last_li_rep = atol(parser->buffer_rep.d);
|
|
break;
|
|
|
|
case 'f':
|
|
parser->last_f_rep = atof(parser->buffer_rep.d);
|
|
break;
|
|
|
|
case 'e':
|
|
eci_string_add(&parser->last_error_repp, 0, parser->buffer_rep.d, parser->buffer_current_rep);
|
|
break;
|
|
|
|
default: {}
|
|
|
|
}
|
|
}
|
|
|
|
static void eci_impl_update_state(struct eci_parser* parser, char c)
|
|
{
|
|
switch(parser->state_rep)
|
|
{
|
|
case ECI_STATE_INIT:
|
|
if (c >= 0x30 && c <= 0x39) {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
|
|
parser->buffer_current_rep = 0;
|
|
eci_string_clear(&parser->buffer_rep);
|
|
parser->state_rep = ECI_STATE_LOGLEVEL;
|
|
}
|
|
else {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
}
|
|
break;
|
|
|
|
case ECI_STATE_LOGLEVEL:
|
|
if (c == ' ') {
|
|
parser->loglevel_rep = atoi(parser->buffer_rep.d);
|
|
|
|
if (parser->loglevel_rep == ECI_RETURN_TYPE_LOGLEVEL) {
|
|
/* ECI_DEBUG_3("\n(ecasoundc_sa) found rettype loglevel '%s' (i=%d,len=%d).\n", parser->buffer_repp, parser->loglevel_rep, parser->buffer_current_rep); */
|
|
parser->state_msg_rep = ECI_STATE_MSG_RETURN;
|
|
}
|
|
else {
|
|
/* ECI_DEBUG_3("\n(ecasoundc_sa) found loglevel '%s' (i=%d,parser->buffer_current_rep=%d).\n", buf, parser->loglevel_rep, parser->buffer_current_rep); */
|
|
parser->state_msg_rep = ECI_STATE_MSG_GEN;
|
|
}
|
|
|
|
parser->state_rep = ECI_STATE_MSGSIZE;
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
}
|
|
else if (c < 0x30 && c > 0x39) {
|
|
parser->state_rep = ECI_STATE_SEEK_TO_LF;
|
|
}
|
|
|
|
break;
|
|
|
|
case ECI_STATE_MSGSIZE:
|
|
if ((c == ' ' && parser->state_msg_rep == ECI_STATE_MSG_RETURN) ||
|
|
(c == 0x0d && parser->state_msg_rep == ECI_STATE_MSG_GEN)) {
|
|
|
|
parser->msgsize_rep = atoi(parser->buffer_rep.d);
|
|
|
|
/* ECI_DEBUG_3("(ecasoundc_sa) found msgsize '%s' (i=%d,len=%d).\n", parser->buffer_repp, parser->msgsize_rep, parser->buffer_current_rep); */
|
|
|
|
if (parser->state_msg_rep == ECI_STATE_MSG_GEN) {
|
|
parser->state_rep = ECI_STATE_COMMON_LF_1;
|
|
}
|
|
else {
|
|
parser->state_rep = ECI_STATE_RET_TYPE;
|
|
}
|
|
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
|
|
}
|
|
else if (c < 0x30 && c > 0x39) {
|
|
parser->state_rep = ECI_STATE_SEEK_TO_LF;
|
|
}
|
|
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
|
|
parser->buffer_current_rep = 0;
|
|
eci_string_clear(&parser->buffer_rep);
|
|
}
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_CR_1:
|
|
if (c == 0x0d)
|
|
parser->state_rep = ECI_STATE_COMMON_LF_1;
|
|
else
|
|
parser->state_rep = ECI_STATE_INIT;
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_LF_1:
|
|
if (c == 0x0a) {
|
|
parser->state_rep = ECI_STATE_COMMON_CONTENT;
|
|
}
|
|
else
|
|
parser->state_rep = ECI_STATE_INIT;
|
|
break;
|
|
|
|
case ECI_STATE_RET_TYPE:
|
|
if (c == 0x0d) {
|
|
/* parse return type */
|
|
/* set 'parser->last_type_repp' */
|
|
int len = (parser->buffer_current_rep < ECI_MAX_RETURN_TYPE_SIZE) ? parser->buffer_current_rep : (ECI_MAX_RETURN_TYPE_SIZE - 1);
|
|
|
|
memcpy(parser->last_type_repp, parser->buffer_rep.d, len);
|
|
parser->last_type_repp[len] = 0;
|
|
|
|
ECI_DEBUG_2("(ecasoundc_sa) found rettype '%s' (len=%d)\n", parser->last_type_repp, parser->buffer_current_rep);
|
|
|
|
parser->state_rep = ECI_STATE_COMMON_LF_1;
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
|
|
}
|
|
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
|
|
parser->buffer_current_rep = 0;
|
|
eci_string_clear(&parser->buffer_rep);
|
|
}
|
|
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_CONTENT:
|
|
if (c == 0x0d) {
|
|
/* parse return type */
|
|
/* set 'parser->last_xxx_yyy' */
|
|
|
|
/* handle empty content */
|
|
if (parser->msgsize_rep == 0)
|
|
eci_string_clear(&parser->buffer_rep);
|
|
|
|
ECI_DEBUG_2("(ecasoundc_sa) found content, loglevel=%d, msgsize=%d", parser->loglevel_rep, parser->msgsize_rep);
|
|
if (parser->state_msg_rep == ECI_STATE_MSG_GEN)
|
|
ECI_DEBUG(".\n");
|
|
else
|
|
ECI_DEBUG_1(" type='%s'.\n", parser->last_type_repp);
|
|
|
|
parser->state_rep = ECI_STATE_COMMON_LF_2;
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_VALIDATE;
|
|
|
|
}
|
|
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
|
|
parser->buffer_current_rep = 0;
|
|
eci_string_clear(&parser->buffer_rep);
|
|
}
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_CR_2:
|
|
if (c == 0x0d)
|
|
parser->state_rep = ECI_STATE_COMMON_LF_2;
|
|
else
|
|
parser->state_rep = ECI_STATE_COMMON_CONTENT;
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_LF_2:
|
|
if (c == 0x0a)
|
|
parser->state_rep = ECI_STATE_COMMON_CR_3;
|
|
else
|
|
parser->state_rep = ECI_STATE_COMMON_CONTENT;
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_CR_3:
|
|
if (c == 0x0d)
|
|
parser->state_rep = ECI_STATE_COMMON_LF_3;
|
|
else
|
|
parser->state_rep = ECI_STATE_COMMON_CONTENT;
|
|
break;
|
|
|
|
case ECI_STATE_COMMON_LF_3:
|
|
if (c == 0x0a) {
|
|
if (parser->state_msg_rep == ECI_STATE_MSG_RETURN) {
|
|
ECI_DEBUG_1("(ecasoundc_sa) rettype-content validated: <<<%s>>>\n", parser->buffer_rep.d);
|
|
eci_impl_set_last_values(parser);
|
|
parser->last_counter_rep++;
|
|
}
|
|
else {
|
|
ECI_DEBUG_1("(ecasoundc_sa) gen-content validated: <<<%s>>>\n", parser->buffer_rep.d);
|
|
}
|
|
parser->state_rep = ECI_STATE_INIT;
|
|
}
|
|
else
|
|
parser->state_rep = ECI_STATE_COMMON_CONTENT;
|
|
break;
|
|
|
|
case ECI_STATE_SEEK_TO_LF:
|
|
if (c == 0x0a) {
|
|
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
|
|
parser->state_rep = ECI_STATE_INIT;
|
|
}
|
|
break;
|
|
|
|
default: {}
|
|
|
|
} /* end of switch() */
|
|
|
|
if (parser->token_phase_rep == ECI_TOKEN_PHASE_READING) {
|
|
eci_string_add(&parser->buffer_rep, parser->buffer_current_rep, &c, 1);
|
|
++parser->buffer_current_rep;
|
|
}
|
|
|
|
//ECI_DEBUG_2("(ecasoundc_sa) parser buf contents: '%s' (cur=%d)\n.", parser->buffer_rep.d, parser->buffer_current_rep);
|
|
}
|