#include <string.h>
#include <assert.h>
#include <termios.h>
+#include <ctype.h>
#include <glib.h>
struct ring_buffer *buf; /* Current read buffer */
guint read_so_far; /* Number of bytes processed */
gboolean disconnecting; /* Whether we're disconnecting */
+ GAtDebugFunc debugf; /* debugging output function */
+ gpointer debug_data; /* Data to pass to debug func */
char *pdu_notify; /* Unsolicited Resp w/ PDU */
GSList *response_lines; /* char * lines of the response */
char *wakeup; /* command sent to wakeup modem */
g_at_chat_shutdown(p);
}
+static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len)
+{
+ char type = in ? '<' : '>';
+ gsize escaped = 2; /* Enough for '<', ' ' */
+ char *escaped_str;
+ const char *esc = "<ESC>";
+ gsize esc_size = strlen(esc);
+ const char *ctrlz = "<CtrlZ>";
+ gsize ctrlz_size = strlen(ctrlz);
+ gsize i;
+
+ if (!chat->debugf || !len)
+ return;
+
+ for (i = 0; i < len; i++) {
+ char c = str[i];
+
+ if (isprint(c))
+ escaped += 1;
+ else if (c == '\r' || c == '\t' || c == '\n')
+ escaped += 2;
+ else if (c == 26)
+ escaped += ctrlz_size;
+ else if (c == 25)
+ escaped += esc_size;
+ else
+ escaped += 4;
+ }
+
+ escaped_str = g_malloc(escaped + 1);
+ escaped_str[0] = type;
+ escaped_str[1] = ' ';
+ escaped_str[2] = '\0';
+ escaped_str[escaped] = '\0';
+
+ for (escaped = 2, i = 0; i < len; i++) {
+ char c = str[i];
+
+ switch (c) {
+ case '\r':
+ escaped_str[escaped++] = '\\';
+ escaped_str[escaped++] = 'r';
+ break;
+ case '\t':
+ escaped_str[escaped++] = '\\';
+ escaped_str[escaped++] = 't';
+ break;
+ case '\n':
+ escaped_str[escaped++] = '\\';
+ escaped_str[escaped++] = 'n';
+ break;
+ case 26:
+ strncpy(&escaped_str[escaped], ctrlz, ctrlz_size);
+ escaped += ctrlz_size;
+ break;
+ case 25:
+ strncpy(&escaped_str[escaped], esc, esc_size);
+ escaped += esc_size;
+ break;
+ default:
+ if (isprint(c))
+ escaped_str[escaped++] = c;
+ else {
+ escaped_str[escaped++] = '\\';
+ escaped_str[escaped++] = '0' + ((c >> 6) & 07);
+ escaped_str[escaped++] = '0' + ((c >> 3) & 07);
+ escaped_str[escaped++] = '0' + (c & 07);
+ }
+ }
+ }
+
+ chat->debugf(escaped_str, chat->debug_data);
+ g_free(escaped_str);
+}
+
static gboolean received_data(GIOChannel *channel, GIOCondition cond,
gpointer data)
{
buf = ring_buffer_write_ptr(chat->buf);
err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
+ debug_chat(chat, TRUE, (char *)buf, rbytes);
total_read += rbytes;
return FALSE;
}
+ debug_chat(chat, FALSE, cmd->cmd + chat->cmd_bytes_written,
+ bytes_written);
chat->cmd_bytes_written += bytes_written;
if (bytes_written < towrite)
chat->ref_count = 1;
chat->next_cmd_id = 1;
chat->next_notify_id = 1;
+ chat->debugf = NULL;
chat->buf = ring_buffer_new(4096);
return TRUE;
}
+gboolean g_at_chat_set_debug(GAtChat *chat, GAtDebugFunc func, gpointer user)
+{
+ if (chat == NULL)
+ return FALSE;
+
+ chat->debugf = func;
+ chat->debug_data = user;
+
+ return TRUE;
+}
+
static guint send_common(GAtChat *chat, const char *cmd,
const char **prefix_list,
GAtNotifyFunc listing, GAtResultFunc func,
gpointer user_data);
typedef void (*GAtNotifyFunc)(GAtResult *result, gpointer user_data);
typedef void (*GAtDisconnectFunc)(gpointer user_data);
-
-enum _GAtChatFlags {
- G_AT_CHAT_FLAG_NO_LEADING_CRLF = 1, /* Some emulators are broken */
- G_AT_CHAT_FLAG_EXTRA_PDU_CRLF = 2,
-};
-
-typedef enum _GAtChatFlags GAtChatFlags;
+typedef void (*GAtDebugFunc)(const char *str, gpointer user_data);
GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax);
GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax);
GAtDisconnectFunc disconnect, gpointer user_data);
/*!
+ * If the function is not NULL, then on every read/write from the GIOChannel
+ * provided to GAtChat the logging function will be called with the
+ * input/output string and user data
+ */
+gboolean g_at_chat_set_debug(GAtChat *chat, GAtDebugFunc func, gpointer user);
+
+/*!
* Queue an AT command for execution. The command contents are given
* in cmd. Once the command executes, the callback function given by
* func is called with user provided data in user_data.
GSMV1_STATE_GUESS_MULTILINE_RESPONSE,
GSMV1_STATE_MULTILINE_RESPONSE,
GSMV1_STATE_MULTILINE_TERMINATOR_CR,
+ GSMV1_STATE_PDU_CHECK_EXTRA_CR,
+ GSMV1_STATE_PDU_CHECK_EXTRA_LF,
GSMV1_STATE_PDU,
GSMV1_STATE_PDU_CR,
GSMV1_STATE_PROMPT,
{
switch (hint) {
case G_AT_SYNTAX_EXPECT_PDU:
- syntax->state = GSMV1_STATE_PDU;
+ syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_CR;
break;
case G_AT_SYNTAX_EXPECT_MULTILINE:
syntax->state = GSMV1_STATE_GUESS_MULTILINE_RESPONSE;
goto out;
+ /* Some 27.007 compliant modems still get this wrong. They
+ * insert an extra CRLF between the command and he PDU,
+ * in effect making them two separate lines. We try to
+ * handle this case gracefully
+ */
+ case GSMV1_STATE_PDU_CHECK_EXTRA_CR:
+ if (byte == '\r')
+ syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_LF;
+ else
+ syntax->state = GSMV1_STATE_PDU;
+ break;
+
+ case GSMV1_STATE_PDU_CHECK_EXTRA_LF:
+ res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
+ syntax->state = GSMV1_STATE_PDU;
+
+ if (byte == '\n')
+ i += 1;
+
+ goto out;
+
case GSMV1_STATE_PDU:
if (byte == '\r')
syntax->state = GSMV1_STATE_PDU_CR;