/* * * AT server library with GLib integration * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "ringbuffer.h" #include "gatserver.h" #include "gatio.h" #define BUF_SIZE 4096 /* + the max length of information text + */ #define MAX_TEXT_SIZE 2052 /* #define WRITE_SCHEDULER_DEBUG 1 */ enum ParserState { PARSER_STATE_IDLE, PARSER_STATE_A, PARSER_STATE_COMMAND, PARSER_STATE_GARBAGE, }; enum ParserResult { PARSER_RESULT_COMMAND, PARSER_RESULT_EMPTY_COMMAND, PARSER_RESULT_REPEAT_LAST, PARSER_RESULT_GARBAGE, PARSER_RESULT_UNSURE, }; /* V.250 Table 1/V.250 Result codes */ static const char *server_result_to_string(GAtServerResult result) { switch (result) { case G_AT_SERVER_RESULT_OK: return "OK"; case G_AT_SERVER_RESULT_CONNECT: return "CONNECT"; case G_AT_SERVER_RESULT_RING: return "RING"; case G_AT_SERVER_RESULT_NO_CARRIER: return "NO CARRIER"; case G_AT_SERVER_RESULT_ERROR: return "ERROR"; case G_AT_SERVER_RESULT_NO_DIALTONE: return "NO DIALTONE"; case G_AT_SERVER_RESULT_BUSY: return "BUSY"; case G_AT_SERVER_RESULT_NO_ANSWER: return "NO ANSWER"; default: return NULL; } } /* Basic command setting for V.250 */ struct v250_settings { char s0; /* set by S0= */ char s3; /* set by S3= */ char s4; /* set by S4= */ char s5; /* set by S5= */ int s6; /* set by S6= */ int s7; /* set by S7= */ int s8; /* set by S8= */ int s10; /* set by S10= */ gboolean echo; /* set by E */ gboolean quiet; /* set by Q */ gboolean is_v1; /* set by V, v0 or v1 */ int res_format; /* set by X */ int c109; /* set by &C */ int c108; /* set by &D */ char l; /* set by L */ char m; /* set by M */ char dial_mode; /* set by P or T */ }; /* AT command set that server supported */ struct at_command { GAtServerNotifyFunc notify; gpointer user_data; GDestroyNotify destroy_notify; }; struct _GAtServer { gint ref_count; /* Ref count */ struct v250_settings v250; /* V.250 command setting */ GAtIO *io; /* Server IO */ guint read_so_far; /* Number of bytes processed */ GAtDisconnectFunc user_disconnect; /* User disconnect func */ gpointer user_disconnect_data; /* User disconnect data */ GAtDebugFunc debugf; /* Debugging output function */ gpointer debug_data; /* Data to pass to debug func */ GHashTable *command_list; /* List of AT commands */ GQueue *write_queue; /* Write buffer queue */ guint max_read_attempts; /* Max reads per select */ enum ParserState parser_state; gboolean destroyed; /* Re-entrancy guard */ char *last_line; /* Last read line */ unsigned int cur_pos; /* Where we are on the line */ GAtServerResult last_result; gboolean final_sent; gboolean final_async; gboolean in_read_handler; GAtServerFinishFunc finishf; /* Callback when cmd finishes */ gpointer finish_data; /* Finish func data */ }; static void server_wakeup_writer(GAtServer *server); static void server_parse_line(GAtServer *server); static struct ring_buffer *allocate_next(GAtServer *server) { struct ring_buffer *buf = ring_buffer_new(BUF_SIZE); if (buf == NULL) return NULL; g_queue_push_tail(server->write_queue, buf); return buf; } static void send_common(GAtServer *server, const char *buf, unsigned int len) { gsize towrite = len; gsize bytes_written = 0; struct ring_buffer *write_buf; write_buf = g_queue_peek_tail(server->write_queue); while (bytes_written < towrite) { gsize wbytes = MIN((gsize)ring_buffer_avail(write_buf), towrite - bytes_written); bytes_written += ring_buffer_write(write_buf, buf + bytes_written, wbytes); /* * Make sure we don't allocate a buffer if we've written * everything out already */ if (ring_buffer_avail(write_buf) == 0 && bytes_written < towrite) write_buf = allocate_next(server); } server_wakeup_writer(server); } static void send_result_common(GAtServer *server, const char *result) { struct v250_settings v250 = server->v250; char buf[MAX_TEXT_SIZE + 1]; char t = v250.s3; char r = v250.s4; unsigned int len; if (v250.quiet) return; if (result == NULL) return; if (strlen(result) > 2048) return; if (v250.is_v1) len = sprintf(buf, "%c%c%s%c%c", t, r, result, t, r); else len = sprintf(buf, "%s%c", result, t); send_common(server, buf, len); } static inline void send_final_common(GAtServer *server, const char *result) { send_result_common(server, result); server->final_async = FALSE; if (server->finishf) server->finishf(server, server->finish_data); } static inline void send_final_numeric(GAtServer *server, GAtServerResult result) { char buf[1024]; if (server->v250.is_v1) sprintf(buf, "%s", server_result_to_string(result)); else sprintf(buf, "%u", (unsigned int)result); send_final_common(server, buf); } void g_at_server_send_final(GAtServer *server, GAtServerResult result) { if (server == NULL) return; if (server->final_sent != FALSE) return; server->final_sent = TRUE; server->last_result = result; if (result == G_AT_SERVER_RESULT_OK) { if (server->final_async) server_parse_line(server); return; } send_final_numeric(server, result); } void g_at_server_send_ext_final(GAtServer *server, const char *result) { server->final_sent = TRUE; server->last_result = G_AT_SERVER_RESULT_EXT_ERROR; send_final_common(server, result); } void g_at_server_send_intermediate(GAtServer *server, const char *result) { send_result_common(server, result); } void g_at_server_send_unsolicited(GAtServer *server, const char *result) { send_result_common(server, result); } void g_at_server_send_info(GAtServer *server, const char *line, gboolean last) { char buf[MAX_TEXT_SIZE + 1]; char t = server->v250.s3; char r = server->v250.s4; unsigned int len; if (strlen(line) > 2048) return; if (last) len = sprintf(buf, "%c%c%s%c%c", t, r, line, t, r); else len = sprintf(buf, "%c%c%s", t, r, line); send_common(server, buf, len); } static gboolean get_result_value(GAtServer *server, GAtResult *result, int min, int max, int *value) { GAtResultIter iter; int val; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "")) return FALSE; if (!g_at_result_iter_next_number(&iter, &val)) return FALSE; if (val < min || val > max) return FALSE; if (value) *value = val; return TRUE; } static void v250_settings_create(struct v250_settings *v250) { v250->s0 = 0; v250->s3 = '\r'; v250->s4 = '\n'; v250->s5 = '\b'; v250->s6 = 2; v250->s7 = 50; v250->s8 = 2; v250->s10 = 2; v250->echo = TRUE; v250->quiet = FALSE; v250->is_v1 = TRUE; v250->res_format = 0; v250->c109 = 1; v250->c108 = 0; v250->l = 0; v250->m = 1; v250->dial_mode = 'T'; } static void s_template_cb(GAtServerRequestType type, GAtResult *result, GAtServer *server, char *sreg, const char *prefix, int min, int max) { char buf[20]; int tmp; switch (type) { case G_AT_SERVER_REQUEST_TYPE_SET: if (!get_result_value(server, result, min, max, &tmp)) { g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); return; } *sreg = tmp; g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; case G_AT_SERVER_REQUEST_TYPE_QUERY: tmp = *sreg; sprintf(buf, "%03d", tmp); g_at_server_send_info(server, buf, TRUE); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; case G_AT_SERVER_REQUEST_TYPE_SUPPORT: sprintf(buf, "%s: (%d-%d)", prefix, min, max); g_at_server_send_info(server, buf, TRUE); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static void at_s0_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.s0, "S0", 0, 7); } static void at_s3_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.s3, "S3", 0, 127); } static void at_s4_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.s4, "S4", 0, 127); } static void at_s5_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.s5, "S5", 0, 127); } static void at_l_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.l, "L", 0, 3); } static void at_m_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { s_template_cb(type, result, server, &server->v250.m, "M", 0, 2); } static void at_template_cb(GAtServerRequestType type, GAtResult *result, GAtServer *server, int *value, const char *prefix, int min, int max, int deftval) { char buf[20]; int tmp; switch (type) { case G_AT_SERVER_REQUEST_TYPE_SET: if (!get_result_value(server, result, min, max, &tmp)) { g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); return; } *value = tmp; g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; case G_AT_SERVER_REQUEST_TYPE_QUERY: tmp = *value; sprintf(buf, "%s: %d", prefix, tmp); g_at_server_send_info(server, buf, TRUE); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; case G_AT_SERVER_REQUEST_TYPE_SUPPORT: sprintf(buf, "%s: (%d-%d)", prefix, min, max); g_at_server_send_info(server, buf, TRUE); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY: *value = deftval; g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static void at_e_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.echo, "E", 0, 1, 1); } static void at_q_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.quiet, "Q", 0, 1, 0); } static void at_v_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.is_v1, "V", 0, 1, 1); } static void at_x_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.res_format, "X", 0, 4, 4); } static void at_s6_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.s6, "S6", 0, 1, 1); } static void at_s7_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.s7, "S7", 1, 255, 50); } static void at_s8_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.s8, "S8", 1, 255, 2); } static void at_s10_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.s10, "S10", 1, 254, 2); } static void at_c109_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.c109, "&C", 0, 1, 1); } static void at_c108_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { at_template_cb(type, result, server, &server->v250.c108, "&D", 0, 2, 2); } /* According to ITU V.250 6.3.2 and 6.3.3: "Implementation of this command * is mandatory; however, if DTMF or pulse dialling is not implemented, * this command will have no effect" */ static void at_t_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { switch (type) { case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY: server->v250.dial_mode = 'T'; g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static void at_p_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { switch (type) { case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY: server->v250.dial_mode = 'P'; g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static void at_f_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { switch (type) { case G_AT_SERVER_REQUEST_TYPE_SET: if (!get_result_value(server, result, 0, 0, NULL)) { g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); return; } /* intentional fallback here */ case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY: /* default behavior on AT&F same as ATZ */ v250_settings_create(&server->v250); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static void at_z_cb(GAtServer *server, GAtServerRequestType type, GAtResult *result, gpointer user_data) { switch (type) { case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY: v250_settings_create(&server->v250); g_at_server_send_final(server, G_AT_SERVER_RESULT_OK); break; default: g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); break; } } static inline gboolean is_extended_command_prefix(const char c) { switch (c) { case '+': case '*': case '!': case '%': return TRUE; default: return FALSE; } } static void at_command_notify(GAtServer *server, char *command, char *prefix, GAtServerRequestType type) { struct at_command *node; GAtResult result; node = g_hash_table_lookup(server->command_list, prefix); if (node == NULL) { g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); return; } result.lines = g_slist_prepend(NULL, command); result.final_or_pdu = 0; node->notify(server, type, &result, node->user_data); g_slist_free(result.lines); } static unsigned int parse_extended_command(GAtServer *server, char *buf) { const char *valid_extended_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789!%-./:_"; const char *separators = ";?="; unsigned int prefix_len, i; gboolean in_string = FALSE; gboolean seen_equals = FALSE; char prefix[18]; /* According to V250, 5.4.1 */ GAtServerRequestType type; char tmp; unsigned int cmd_start; prefix_len = strcspn(buf, separators); if (prefix_len > 17 || prefix_len < 2) return 0; /* Convert to upper case, we will always use upper case naming */ for (i = 0; i < prefix_len; i++) prefix[i] = g_ascii_toupper(buf[i]); prefix[prefix_len] = '\0'; if (strspn(prefix + 1, valid_extended_chars) != (prefix_len - 1)) return 0; /* * V.250 Section 5.4.1: "The first character following "+" shall be * an alphabetic character in the range "A" through "Z". */ if (prefix[1] <= 'A' || prefix[1] >= 'Z') return 0; if (buf[i] != '\0' && buf[i] != ';' && buf[i] != '?' && buf[i] != '=') return 0; type = G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY; cmd_start = prefix_len; /* Continue until we hit eol or ';' */ while (buf[i] && !(buf[i] == ';' && in_string == FALSE)) { if (buf[i] == '"') { in_string = !in_string; goto next; } if (in_string == TRUE) goto next; if (buf[i] == '?') { if (seen_equals && buf[i-1] != '=') return 0; if (buf[i + 1] != '\0' && buf[i + 1] != ';') return 0; type = G_AT_SERVER_REQUEST_TYPE_QUERY; cmd_start += 1; if (seen_equals) type = G_AT_SERVER_REQUEST_TYPE_SUPPORT; } else if (buf[i] == '=') { if (seen_equals) return 0; seen_equals = TRUE; type = G_AT_SERVER_REQUEST_TYPE_SET; cmd_start += 1; } next: i++; } /* We can scratch in this buffer, so mark ';' as null */ tmp = buf[i]; buf[i] = '\0'; at_command_notify(server, buf + cmd_start, prefix, type); buf[i] = tmp; /* Also consume the terminating null */ return i + 1; } static int get_basic_prefix_size(const char *buf) { if (g_ascii_isalpha(buf[0])) { if (g_ascii_toupper(buf[0]) == 'S') { int size; /* V.250 5.3.2 'S' command follows with a parameter * number. */ for (size = 1; g_ascii_isdigit(buf[size]); size++) ; /* * Do some basic sanity checking, don't accept 00, 01, * etc or empty S values */ if (size == 1) return 0; if (size > 2 && buf[1] == '0') return 0; return size; } /* All other cases it is a simple 1 character prefix */ return 1; } if (buf[0] == '&') { if (g_ascii_isalpha(buf[1]) == FALSE) return 0; return 2; } return 0; } static unsigned int parse_basic_command(GAtServer *server, char *buf) { gboolean seen_equals = FALSE; char prefix[4], tmp; unsigned int i, prefix_size; GAtServerRequestType type; unsigned int cmd_start; prefix_size = get_basic_prefix_size(buf); if (prefix_size == 0) return 0; i = prefix_size; prefix[0] = g_ascii_toupper(buf[0]); cmd_start = prefix_size; if (prefix[0] == 'D') { type = G_AT_SERVER_REQUEST_TYPE_SET; /* All characters appearing on the same line, up to a * semicolon character (IA5 3/11) or the end of the * command line is the part of the call. */ while (buf[i] != '\0' && buf[i] != ';') i += 1; if (buf[i] == ';') i += 1; goto done; } type = G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY; /* Match '?', '=', '=?' and '=xxx' */ if (buf[i] == '=') { seen_equals = TRUE; i += 1; cmd_start += 1; } if (buf[i] == '?') { i += 1; cmd_start += 1; if (seen_equals) type = G_AT_SERVER_REQUEST_TYPE_SUPPORT; else type = G_AT_SERVER_REQUEST_TYPE_QUERY; } else { int before = i; /* V.250 5.3.1 The subparameter (if any) are all digits */ while (g_ascii_isdigit(buf[i])) i++; if (i - before > 0) type = G_AT_SERVER_REQUEST_TYPE_SET; } done: if (prefix_size <= 3) { memcpy(prefix + 1, buf + 1, prefix_size - 1); prefix[prefix_size] = '\0'; tmp = buf[i]; buf[i] = '\0'; at_command_notify(server, buf + cmd_start, prefix, type); buf[i] = tmp; } else /* Handle S-parameter with 100+ */ g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); /* * Commands like ATA, ATZ cause the remainder linevto be ignored. * In GSM/UMTS the ATD uses the separator ';' character as a voicecall * modifier, so we ignore everything coming after that character * as well. */ if (prefix[0] == 'A' || prefix[0] == 'Z' || prefix[0] == 'D') return strlen(buf); /* Consume the seperator ';' */ if (buf[i] == ';') i += 1; return i; } static void server_parse_line(GAtServer *server) { char *line = server->last_line; unsigned int pos = server->cur_pos; unsigned int len = strlen(line); while (pos < len) { unsigned int consumed; server->final_sent = FALSE; server->final_async = FALSE; if (is_extended_command_prefix(line[pos])) consumed = parse_extended_command(server, line + pos); else consumed = parse_basic_command(server, line + pos); if (consumed == 0) { g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR); return; } pos += consumed; server->cur_pos = pos; /* * We wait the callback until it finished processing * the command and called the send_final. */ if (server->final_sent == FALSE) { server->final_async = TRUE; return; } if (server->last_result != G_AT_SERVER_RESULT_OK) return; } send_final_numeric(server, G_AT_SERVER_RESULT_OK); } static enum ParserResult server_feed(GAtServer *server, const char *bytes, gsize *len) { gsize i = 0; enum ParserResult res = PARSER_RESULT_UNSURE; char s3 = server->v250.s3; while (i < *len) { char byte = bytes[i]; switch (server->parser_state) { case PARSER_STATE_IDLE: if (byte == s3) { i += 1; res = PARSER_RESULT_EMPTY_COMMAND; goto out; } else if (byte == '\n') { i += 1; res = PARSER_RESULT_GARBAGE; goto out; } else if (byte == 'A' || byte == 'a') server->parser_state = PARSER_STATE_A; else if (byte != ' ' && byte != '\t') server->parser_state = PARSER_STATE_GARBAGE; break; case PARSER_STATE_A: if (byte == s3) { server->parser_state = PARSER_STATE_IDLE; i += 1; res = PARSER_RESULT_GARBAGE; goto out; } else if (byte == '/') { server->parser_state = PARSER_STATE_IDLE; i += 1; res = PARSER_RESULT_REPEAT_LAST; goto out; } else if (byte == 'T' || byte == 't') server->parser_state = PARSER_STATE_COMMAND; else server->parser_state = PARSER_STATE_GARBAGE; break; case PARSER_STATE_COMMAND: if (byte == s3) { server->parser_state = PARSER_STATE_IDLE; i += 1; res = PARSER_RESULT_COMMAND; goto out; } break; case PARSER_STATE_GARBAGE: /* Detect CR or HDLC frame marker flag */ if (byte == s3 || byte == '~') { server->parser_state = PARSER_STATE_IDLE; i += 1; res = PARSER_RESULT_GARBAGE; goto out; } break; default: break; }; i += 1; } out: *len = i; return res; } static char *extract_line(GAtServer *p, struct ring_buffer *rbuf) { unsigned int wrap = ring_buffer_len_no_wrap(rbuf); unsigned int pos = 0; unsigned char *buf = ring_buffer_read_ptr(rbuf, pos); int strip_front = 0; int line_length = 0; gboolean in_string = FALSE; char s3 = p->v250.s3; char s5 = p->v250.s5; char *line; int i; while (pos < p->read_so_far) { if (*buf == '"') in_string = !in_string; if (in_string == FALSE && (*buf == ' ' || *buf == '\t')) { if (line_length == 0) strip_front += 1; } else line_length += 1; buf += 1; pos += 1; if (pos == wrap) buf = ring_buffer_read_ptr(rbuf, pos); } /* We will strip AT and S3 */ line_length -= 3; line = g_try_new(char, line_length + 1); if (line == NULL) { ring_buffer_drain(rbuf, p->read_so_far); return NULL; } /* Strip leading whitespace + AT */ ring_buffer_drain(rbuf, strip_front + 2); pos = 0; i = 0; wrap = ring_buffer_len_no_wrap(rbuf); buf = ring_buffer_read_ptr(rbuf, pos); while (pos < (p->read_so_far - strip_front - 2)) { if (*buf == '"') in_string = !in_string; if (*buf == s5) { if (i != 0) i -= 1; } else if ((*buf == ' ' || *buf == '\t') && in_string == FALSE) ; /* Skip */ else if (*buf != s3) line[i++] = *buf; buf += 1; pos += 1; if (pos == wrap) buf = ring_buffer_read_ptr(rbuf, pos); } /* Strip S3 */ ring_buffer_drain(rbuf, p->read_so_far - strip_front - 2); line[i] = '\0'; return line; } static void new_bytes(struct ring_buffer *rbuf, gpointer user_data) { GAtServer *p = user_data; unsigned int len = ring_buffer_len(rbuf); unsigned int wrap = ring_buffer_len_no_wrap(rbuf); unsigned char *buf = ring_buffer_read_ptr(rbuf, p->read_so_far); enum ParserResult result; /* We do not support command abortion, so ignore input */ if (p->final_async) { ring_buffer_drain(rbuf, len); return; } p->in_read_handler = TRUE; while (p->io && (p->read_so_far < len)) { gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far); result = server_feed(p, (char *)buf, &rbytes); if (p->v250.echo) send_common(p, (char *)buf, rbytes); buf += rbytes; p->read_so_far += rbytes; if (p->read_so_far == wrap) { buf = ring_buffer_read_ptr(rbuf, p->read_so_far); wrap = len; } switch (result) { case PARSER_RESULT_UNSURE: continue; case PARSER_RESULT_EMPTY_COMMAND: /* * According to section 5.2.4 and 5.6 of V250, * Empty commands must be OK by the DCE */ g_at_server_send_final(p, G_AT_SERVER_RESULT_OK); ring_buffer_drain(rbuf, p->read_so_far); break; case PARSER_RESULT_COMMAND: { g_free(p->last_line); p->last_line = extract_line(p, rbuf); p->cur_pos = 0; if (p->last_line) server_parse_line(p); else g_at_server_send_final(p, G_AT_SERVER_RESULT_ERROR); break; } case PARSER_RESULT_REPEAT_LAST: p->cur_pos = 0; ring_buffer_drain(rbuf, p->read_so_far); if (p->last_line) server_parse_line(p); else g_at_server_send_final(p, G_AT_SERVER_RESULT_OK); break; case PARSER_RESULT_GARBAGE: ring_buffer_drain(rbuf, p->read_so_far); break; } len -= p->read_so_far; wrap -= p->read_so_far; p->read_so_far = 0; /* * Handle situations where we receive two command lines in * one read, which should not be possible (and implies the * earlier command should be canceled. * * e.g. AT+CMD1\rAT+CMD2 */ if (result != PARSER_RESULT_GARBAGE) { ring_buffer_drain(rbuf, len); break; } } p->in_read_handler = FALSE; if (p->destroyed) g_free(p); } static gboolean can_write_data(gpointer data) { GAtServer *server = data; gsize bytes_written; gsize towrite; struct ring_buffer *write_buf; unsigned char *buf; #ifdef WRITE_SCHEDULER_DEBUG int limiter; #endif if (!server->write_queue) return FALSE; /* Write data out from the head of the queue */ write_buf = g_queue_peek_head(server->write_queue); buf = ring_buffer_read_ptr(write_buf, 0); towrite = ring_buffer_len_no_wrap(write_buf); #ifdef WRITE_SCHEDULER_DEBUG limiter = towrite; if (limiter > 5) limiter = 5; #endif bytes_written = g_at_io_write(server->io, (char *)buf, #ifdef WRITE_SCHEDULER_DEBUG limiter #else towrite #endif ); if (bytes_written == 0) return FALSE; ring_buffer_drain(write_buf, bytes_written); /* All data in current buffer is written, free it * unless it's the last buffer in the queue. */ if ((ring_buffer_len(write_buf) == 0) && (g_queue_get_length(server->write_queue) > 1)) { write_buf = g_queue_pop_head(server->write_queue); ring_buffer_free(write_buf); write_buf = g_queue_peek_head(server->write_queue); } if (ring_buffer_len(write_buf) > 0) return TRUE; return FALSE; } static void write_queue_free(GQueue *write_queue) { struct ring_buffer *write_buf; while ((write_buf = g_queue_pop_head(write_queue))) ring_buffer_free(write_buf); g_queue_free(write_queue); } static void g_at_server_cleanup(GAtServer *server) { /* Cleanup pending data to write */ write_queue_free(server->write_queue); g_hash_table_destroy(server->command_list); server->command_list = NULL; g_free(server->last_line); g_at_io_unref(server->io); server->io = NULL; } static void io_disconnect(gpointer user_data) { GAtServer *server = user_data; g_at_server_cleanup(server); if (server->user_disconnect) server->user_disconnect(server->user_disconnect_data); } static void server_wakeup_writer(GAtServer *server) { g_at_io_set_write_handler(server->io, can_write_data, server); } static void at_notify_node_destroy(gpointer data) { struct at_command *node = data; if (node->destroy_notify) node->destroy_notify(node->user_data); g_free(node); } static void basic_command_register(GAtServer *server) { g_at_server_register(server, "S0", at_s0_cb, NULL, NULL); g_at_server_register(server, "S3", at_s3_cb, NULL, NULL); g_at_server_register(server, "S4", at_s4_cb, NULL, NULL); g_at_server_register(server, "S5", at_s5_cb, NULL, NULL); g_at_server_register(server, "E", at_e_cb, NULL, NULL); g_at_server_register(server, "Q", at_q_cb, NULL, NULL); g_at_server_register(server, "V", at_v_cb, NULL, NULL); g_at_server_register(server, "X", at_x_cb, NULL, NULL); g_at_server_register(server, "S6", at_s6_cb, NULL, NULL); g_at_server_register(server, "S7", at_s7_cb, NULL, NULL); g_at_server_register(server, "S8", at_s8_cb, NULL, NULL); g_at_server_register(server, "S10", at_s10_cb, NULL, NULL); g_at_server_register(server, "&C", at_c109_cb, NULL, NULL); g_at_server_register(server, "&D", at_c108_cb, NULL, NULL); g_at_server_register(server, "Z", at_z_cb, NULL, NULL); g_at_server_register(server, "&F", at_f_cb, NULL, NULL); g_at_server_register(server, "L", at_l_cb, NULL, NULL); g_at_server_register(server, "M", at_m_cb, NULL, NULL); g_at_server_register(server, "T", at_t_cb, NULL, NULL); g_at_server_register(server, "P", at_p_cb, NULL, NULL); } GAtServer *g_at_server_new(GIOChannel *io) { GAtServer *server; if (io == NULL) return NULL; server = g_try_new0(GAtServer, 1); if (server == NULL) return NULL; server->ref_count = 1; v250_settings_create(&server->v250); server->io = g_at_io_new(io); if (!server->io) goto error; g_at_io_set_disconnect_function(server->io, io_disconnect, server); server->command_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, at_notify_node_destroy); server->write_queue = g_queue_new(); if (!server->write_queue) goto error; if (allocate_next(server) == NULL) goto error; server->max_read_attempts = 3; g_at_io_set_read_handler(server->io, new_bytes, server); basic_command_register(server); return server; error: g_at_io_unref(server->io); if (server->command_list) g_hash_table_destroy(server->command_list); if (server->write_queue) write_queue_free(server->write_queue); if (server) g_free(server); return NULL; } GIOChannel *g_at_server_get_channel(GAtServer *server) { if (server == NULL || server->io == NULL) return NULL; return g_at_io_get_channel(server->io); } GAtIO *g_at_server_get_io(GAtServer *server) { if (server == NULL) return NULL; return server->io; } GAtServer *g_at_server_ref(GAtServer *server) { if (server == NULL) return NULL; g_atomic_int_inc(&server->ref_count); return server; } void g_at_server_suspend(GAtServer *server) { if (server == NULL) return; g_at_io_set_write_handler(server->io, NULL, NULL); g_at_io_set_read_handler(server->io, NULL, NULL); g_at_io_set_debug(server->io, NULL, NULL); } void g_at_server_resume(GAtServer *server) { if (server == NULL) return; if (g_at_io_get_channel(server->io) == NULL) { io_disconnect(server); return; } g_at_io_set_disconnect_function(server->io, io_disconnect, server); g_at_io_set_debug(server->io, server->debugf, server->debug_data); g_at_io_set_read_handler(server->io, new_bytes, server); if (g_queue_get_length(server->write_queue) > 0) server_wakeup_writer(server); } void g_at_server_unref(GAtServer *server) { gboolean is_zero; if (server == NULL) return; is_zero = g_atomic_int_dec_and_test(&server->ref_count); if (is_zero == FALSE) return; if (server->io) { g_at_server_suspend(server); g_at_server_cleanup(server); } g_at_server_shutdown(server); /* glib delays the destruction of the watcher until it exits, this * means we can't free the data just yet, even though we've been * destroyed already. We have to wait until the read_watcher * destroy function gets called */ if (server->in_read_handler) server->destroyed = TRUE; else g_free(server); } gboolean g_at_server_shutdown(GAtServer *server) { if (server == NULL) return FALSE; /* Don't trigger user disconnect on shutdown */ server->user_disconnect = NULL; server->user_disconnect_data = NULL; return TRUE; } gboolean g_at_server_set_echo(GAtServer *server, gboolean echo) { if (server == NULL) return FALSE; server->v250.echo = echo; return TRUE; } gboolean g_at_server_set_disconnect_function(GAtServer *server, GAtDisconnectFunc disconnect, gpointer user_data) { if (server == NULL) return FALSE; server->user_disconnect = disconnect; server->user_disconnect_data = user_data; return TRUE; } gboolean g_at_server_set_debug(GAtServer *server, GAtDebugFunc func, gpointer user_data) { if (server == NULL) return FALSE; server->debugf = func; server->debug_data = user_data; g_at_io_set_debug(server->io, server->debugf, server->debug_data); return TRUE; } gboolean g_at_server_register(GAtServer *server, const char *prefix, GAtServerNotifyFunc notify, gpointer user_data, GDestroyNotify destroy_notify) { struct at_command *node; if (server == NULL || server->command_list == NULL) return FALSE; if (notify == NULL) return FALSE; if (prefix == NULL || strlen(prefix) == 0) return FALSE; node = g_try_new0(struct at_command, 1); if (node == NULL) return FALSE; node->notify = notify; node->user_data = user_data; node->destroy_notify = destroy_notify; g_hash_table_replace(server->command_list, g_strdup(prefix), node); return TRUE; } gboolean g_at_server_unregister(GAtServer *server, const char *prefix) { struct at_command *node; if (server == NULL || server->command_list == NULL) return FALSE; if (prefix == NULL || strlen(prefix) == 0) return FALSE; node = g_hash_table_lookup(server->command_list, prefix); if (node == NULL) return FALSE; g_hash_table_remove(server->command_list, prefix); return TRUE; } gboolean g_at_server_set_finish_callback(GAtServer *server, GAtServerFinishFunc finishf, gpointer user_data) { if (server == NULL) return FALSE; server->finishf = finishf; server->finish_data = user_data; return TRUE; } gboolean g_at_server_command_pending(GAtServer *server) { if (server == NULL) return FALSE; return server->final_async; }