| /* |
| * |
| * 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 <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| |
| #include <glib.h> |
| |
| #include "ringbuffer.h" |
| #include "gatserver.h" |
| #include "gatio.h" |
| |
| #define BUF_SIZE 4096 |
| /* <cr><lf> + the max length of information text + <cr><lf> */ |
| #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=<val> */ |
| char s3; /* set by S3=<val> */ |
| char s4; /* set by S4=<val> */ |
| char s5; /* set by S5=<val> */ |
| int s6; /* set by S6=<val> */ |
| int s7; /* set by S7=<val> */ |
| int s8; /* set by S8=<val> */ |
| int s10; /* set by S10=<val> */ |
| gboolean echo; /* set by E<val> */ |
| gboolean quiet; /* set by Q<val> */ |
| gboolean is_v1; /* set by V<val>, v0 or v1 */ |
| int res_format; /* set by X<val> */ |
| int c109; /* set by &C<val> */ |
| int c108; /* set by &D<val> */ |
| char l; /* set by L<val> */ |
| char m; /* set by M<val> */ |
| 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; |
| } |
| /* fall through */ |
| |
| 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; |
| } |