* apps/netutils/chat/chat.c
*
* Copyright (C) 2016 Vladimir Komendantskiy. All rights reserved.
* Author: Vladimir Komendantskiy <vladimir@moixaenergy.com>
* Partly based on code by Max Nekludov <macscomp@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "chat.h"
* Pre-processor Definitions
****************************************************************************/
#define CHAT_TOKEN_SIZE 128
* Pivate types
****************************************************************************/
struct chat_token
{
FAR char *string;
bool no_termin;
FAR struct chat_token *next;
};
* Private Functions
****************************************************************************/
void chat_init(FAR struct chat *priv, FAR struct chat_ctl *ctl)
{
DEBUGASSERT(priv != NULL && ctl != NULL && ctl->timeout >= 0);
memcpy(&priv->ctl, ctl, sizeof(struct chat_ctl));
priv->script = NULL;
}
static int chat_tokenise(FAR struct chat *priv,
FAR const char *script,
FAR struct chat_token **first_tok)
{
FAR const char *cursor = script;
unsigned int quoted = 0;
* 1: quoted (expecting a closing quote)
* 0: not quoted */
unsigned int escaped = 0;
char tok_str[CHAT_TOKEN_SIZE];
int tok_pos = 0;
FAR struct chat_token *tok = NULL;
bool no_termin;
* true if line terminator is deasserted */
int ret = 0;
int tok_on_delimiter(void)
{
if (!tok_pos && !quoted && !no_termin)
{
* b) the previous character was a delimiter,
* and in both cases it is not the empty string token,
* hence skipping.
*/
return 0;
}
tok_str[tok_pos] = '\0';
if (tok)
{
tok->next = malloc(sizeof(struct chat_token));
tok = tok->next;
}
else
{
*first_tok = malloc(sizeof(struct chat_token));
tok = *first_tok;
}
if (!tok)
{
return -ENOMEM;
}
tok->string = strdup(tok_str);
tok->no_termin = no_termin;
tok->next = NULL;
tok_pos = 0;
ninfo("%s (%d)\n", tok->string, tok->no_termin);
return 0;
}
DEBUGASSERT(script != NULL);
ninfo("%s\n", script);
while (!ret && *cursor != '\0')
{
no_termin = false;
switch (*cursor)
{
case '\\':
if (!quoted)
{
if (escaped)
{
tok_str[tok_pos++] = '\\';
}
escaped ^= 1;
}
else
{
tok_str[tok_pos++] = '\\';
}
break;
case '"':
if (escaped)
{
tok_str[tok_pos++] = '"';
escaped = 0;
}
else
{
if (quoted && !tok_pos)
{
ret = tok_on_delimiter();
}
quoted ^= 1;
}
break;
case ' ':
case '\n':
case '\r':
case '\t':
if (quoted)
{
tok_str[tok_pos++] = *cursor;
}
else
{
ret = tok_on_delimiter();
}
break;
default:
if (escaped)
{
switch (*cursor)
{
case 'n':
tok_str[tok_pos++] = '\n';
break;
case 'r':
tok_str[tok_pos++] = '\r';
break;
case 'c':
no_termin = true;
break;
default:
ret = -EINVAL;
break;
}
escaped = 0;
}
else
{
tok_str[tok_pos++] = *cursor;
}
break;
}
cursor++;
}
if (!ret && *cursor == '\0')
{
ret = tok_on_delimiter();
}
ninfo("result %d\n", ret);
return ret;
}
static int chat_internalise(FAR struct chat *priv,
FAR struct chat_token *tok)
{
unsigned int rhs = 0;
* 0 otherwise */
FAR struct chat_line *line = NULL;
int len;
int ret = 0;
while (tok && !ret)
{
DEBUGASSERT(tok->string);
ninfo("(%c) %s\n", rhs ? 'R' : 'L', tok->string);
if (!rhs)
{
if (!line)
{
line = malloc(sizeof(struct chat_line));
priv->script = line;
}
else
{
line->next = malloc(sizeof(struct chat_line));
line = line->next;
}
if (!line)
{
ret = -ENOMEM;
break;
}
line->next = NULL;
}
if (rhs)
{
len = strlen(tok->string) + 1;
if (!tok->no_termin)
{
len += 2;
}
line->rhs = malloc(len);
if (line->rhs)
{
snprintf(line->rhs, len,
tok->no_termin ? "%s" : "%s\r\n", tok->string);
}
else
{
ret = -ENOMEM;
}
}
else
{
if (!strcmp(tok->string, "ABORT"))
{
line->type = CHAT_LINE_TYPE_COMMAND;
line->lhs.command = CHAT_COMMAND_ABORT;
}
else if (!strcmp(tok->string, "ECHO"))
{
line->type = CHAT_LINE_TYPE_COMMAND;
line->lhs.command = CHAT_COMMAND_ECHO;
}
else if (!strcmp(tok->string, "PAUSE"))
{
line->type = CHAT_LINE_TYPE_COMMAND;
line->lhs.command = CHAT_COMMAND_PAUSE;
}
else if (!strcmp(tok->string, "SAY"))
{
line->type = CHAT_LINE_TYPE_COMMAND;
line->lhs.command = CHAT_COMMAND_SAY;
}
else if (!strcmp(tok->string, "TIMEOUT"))
{
line->type = CHAT_LINE_TYPE_COMMAND;
line->lhs.command = CHAT_COMMAND_TIMEOUT;
}
else
{
line->type = CHAT_LINE_TYPE_EXPECT_SEND;
line->lhs.expect = strdup(tok->string);
if (!line->lhs.expect)
{
ret = -ENOMEM;
}
}
line->rhs = NULL;
}
rhs ^= 1;
tok = tok->next;
}
if (!ret && rhs)
{
ret = -ENODATA;
}
ninfo("result %d, rhs %d\n", ret, rhs);
return ret;
}
static void chat_tokens_free(FAR struct chat_token *first_tok)
{
FAR struct chat_token *next_tok;
while (first_tok)
{
next_tok = first_tok->next;
DEBUGASSERT(first_tok->string != NULL);
free(first_tok->string);
free(first_tok);
first_tok = next_tok;
}
ninfo("tokens freed\n");
}
static int chat_script_parse(FAR struct chat *priv, FAR const char *script)
{
FAR struct chat_token *first_tok;
int ret;
ret = chat_tokenise(priv, script, &first_tok);
if (!ret)
{
ret = chat_internalise(priv, first_tok);
}
chat_tokens_free(first_tok);
return ret;
}
* successful read, stores 1 read byte at location pointed by 'c'. Otherwise
* returns a negative error code.
*/
static int chat_readb(FAR struct chat *priv, FAR char *c, int timeout_ms)
{
struct pollfd fds;
int ret;
fds.fd = priv->ctl.fd;
fds.events = POLLIN;
fds.revents = 0;
ret = poll(&fds, 1, timeout_ms);
if (ret <= 0)
{
ninfo("poll timed out\n");
return -ETIMEDOUT;
}
ret = read(priv->ctl.fd, c, 1);
if (ret != 1)
{
ninfo("read failed\n");
return -EPERM;
}
if (priv->ctl.echo)
{
fputc(*c, stderr);
}
ninfo("read \'%c\' (0x%02X)\n", *c, *c);
return 0;
}
static void chat_flush(FAR struct chat *priv)
{
char c;
ninfo("starting\n");
while (chat_readb(priv, &c, 0) == 0);
ninfo("done\n");
}
static int chat_expect(FAR struct chat *priv, FAR const char *s)
{
char c;
struct timespec abstime;
struct timespec endtime;
int timeout_ms;
int s_len = strlen(s);
int i_match = 0;
int ret = 0;
clock_gettime(CLOCK_REALTIME, (FAR struct timespec *)&abstime);
endtime.tv_sec = abstime.tv_sec + priv->ctl.timeout;
endtime.tv_nsec = abstime.tv_nsec;
while (!ret && i_match < s_len &&
(abstime.tv_sec < endtime.tv_sec ||
(abstime.tv_sec == endtime.tv_sec &&
abstime.tv_nsec < endtime.tv_nsec)))
{
timeout_ms =
(endtime.tv_sec - abstime.tv_sec) * 1000 +
(endtime.tv_nsec - abstime.tv_nsec) / 1000000;
DEBUGASSERT(timeout_ms >= 0);
ret = chat_readb(priv, &c, timeout_ms);
if (!ret)
{
if (c == s[i_match])
{
i_match++;
}
else
{
i_match = 0;
}
clock_gettime(CLOCK_REALTIME, (FAR struct timespec *)&abstime);
}
}
ninfo("result %d\n", ret);
return ret;
}
static int chat_send(FAR struct chat *priv, FAR const char *s)
{
int ret = 0;
int len = strlen(s);
ret = write(priv->ctl.fd, s, len);
ninfo("wrote %d out of %d bytes of \'%s\'\n", ret, len, s);
if (ret > 0)
{
ret = 0;
}
return ret;
}
static int chat_line_run(FAR struct chat *priv,
FAR const struct chat_line *line)
{
int ret = 0;
int numarg;
ninfo("type %d, rhs %s\n", line->type, line->rhs);
switch (line->type)
{
case CHAT_LINE_TYPE_COMMAND:
if (priv->ctl.verbose)
{
fprintf(stderr, "chat: cmd %d, arg %s\n",
line->lhs.command, line->rhs);
}
switch (line->lhs.command)
{
case CHAT_COMMAND_ABORT:
break;
case CHAT_COMMAND_ECHO:
if (strcmp(line->rhs, "ON"))
{
priv->ctl.echo = true;
}
else
{
priv->ctl.echo = false;
}
break;
case CHAT_COMMAND_PAUSE:
numarg = atoi(line->rhs);
if (numarg < 0)
{
numarg = 0;
}
sleep(numarg);
break;
case CHAT_COMMAND_SAY:
fprintf(stderr, "%s\n", line->rhs);
break;
case CHAT_COMMAND_TIMEOUT:
numarg = atoi(line->rhs);
if (numarg < 0)
{
ninfo("invalid timeout string %s\n", line->rhs);
}
else
{
ninfo("timeout is %d s\n", numarg);
priv->ctl.timeout = numarg;
}
break;
default:
break;
}
break;
case CHAT_LINE_TYPE_EXPECT_SEND:
if (priv->ctl.verbose)
{
fprintf(stderr, "chat: %s %s\n", line->lhs.expect, line->rhs);
}
ret = chat_expect(priv, line->lhs.expect);
if (!ret)
{
chat_flush(priv);
ret = chat_send(priv, line->rhs);
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int chat_script_run(FAR struct chat *priv)
{
FAR struct chat_line *line = priv->script;
int ret = 0;
#ifdef CONFIG_DEBUG_INFO
int line_num = 0;
#endif
while (!ret && line)
{
ret = chat_line_run(priv, line);
if (!ret)
{
line = line->next;
#ifdef CONFIG_DEBUG_INFO
line_num++;
#endif
}
}
#ifdef CONFIG_DEBUG_INFO
ninfo("Script result %d, exited on line %d\n", ret, line_num);
#endif
return ret;
}
static int chat_script_free(FAR struct chat *priv)
{
FAR struct chat_line *line = priv->script;
FAR struct chat_line *next_line;
int ret = 0;
while (line)
{
next_line = line->next;
if (line->type == CHAT_LINE_TYPE_EXPECT_SEND)
{
free(line->lhs.expect);
}
free(line->rhs);
free(line);
line = next_line;
}
priv->script = NULL;
return ret;
}
* Public Functions
****************************************************************************/
int chat(FAR struct chat_ctl *ctl, FAR const char *script)
{
int ret = 0;
struct chat priv;
DEBUGASSERT(script != NULL);
chat_init(&priv, ctl);
ret = chat_script_parse(&priv, script);
if (!ret)
{
ret = chat_script_run(&priv);
}
chat_script_free(&priv);
return ret;
}