summaryrefslogtreecommitdiff
path: root/src/sms_list.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sms_list.c')
-rw-r--r--src/sms_list.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/sms_list.c b/src/sms_list.c
new file mode 100644
index 0000000..0f9a9e6
--- /dev/null
+++ b/src/sms_list.c
@@ -0,0 +1,502 @@
+/*
+ * SMS List Messages
+ *
+ * Copyright (C) 2010 by Multi-Tech Systems
+ *
+ * Author: James Maki <jmaki@multitech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "global.h"
+#include "cmd_options.h"
+#include "sms_utils.h"
+#include "list.h"
+#include "sms_list.h"
+#include "pdu_decode.h"
+#include "utils.h"
+#include "atcmd.h"
+
+#define CMGL_HEADER_START "+CMGL: "
+#define CMGR_HEADER_START "+CMGR: "
+
+static int process_header(struct msg_list_info *list_info, char *buf, size_t len)
+{
+ char *save = buf;
+ char *token;
+
+ struct sms_msg *msg;
+ int index;
+ int status;
+ char *name;
+ int msg_len;
+
+ token = atcmd_response_brk(&save);
+ if (!token) {
+ log_debug("response tokenizing failed at start");
+ return -1;
+ }
+
+ switch (list_info->cmd_type) {
+ case CMD_TYPE_CMGL:
+ token = atcmd_value_tok(&save);
+ if (!token) {
+ log_debug("response tokenizing failed at index");
+ return -2;
+ }
+ index = atoi(token);
+
+ break;
+ case CMD_TYPE_CMGR:
+ index = list_info->index;
+ break;
+ default:
+ return false;
+ }
+
+ token = atcmd_value_tok(&save);
+ if (!token) {
+ log_debug("response tokenizing failed at msg-status");
+ return -3;
+ }
+ status = atoi(token);
+
+ token = atcmd_value_tok(&save);
+ if (!token) {
+ log_debug("response tokenizing failed at name");
+ return -4;
+ }
+ name = token;
+
+ token = atcmd_value_tok(&save);
+ if (!token) {
+ log_debug("response tokenizing failed at msg-len");
+ return -5;
+ }
+ msg_len = atoi(token);
+
+ switch (status) {
+ case SMS_MSG_REC_UNREAD:
+ list_info->nr_unread++;
+ break;
+ case SMS_MSG_REC_READ:
+ list_info->nr_read++;
+ break;
+ case SMS_MSG_STO_UNSENT:
+ list_info->nr_unsent++;
+ break;
+ case SMS_MSG_STO_SENT:
+ list_info->nr_sent++;
+ break;
+ default:
+ log_warning("msg-status unknown");
+ list_info->nr_unknown++;
+ }
+
+ msg = sms_msg_alloc();
+ if (!msg) {
+ log_error("out of memory");
+ return -6;
+ }
+
+ memset(msg, 0, sizeof(*msg));
+
+ msg->index = index;
+ msg->status = status;
+ snprintf(msg->name, sizeof(msg->name), "%s", name);
+ msg->len = msg_len;
+
+ list_add_tail(&msg->list, &list_info->msg_list);
+
+ list_info->nr_msgs++;
+
+ return 0;
+}
+
+static int print_list_yaml(struct msg_list_info *list_info)
+{
+ struct sms_msg *msg;
+ char buf[64];
+ int indent;
+
+ indent = 0;
+
+ indentf(indent, "---\n");
+ indentf(indent, "\n");
+
+ indentf(indent, "messages:\n");
+
+ list_for_each_entry(msg, &list_info->msg_list, list) {
+ indent = YAML_INDENT;
+
+ indentf(indent, "- index: %d\n", msg->index);
+ indent += YAML_INDENT;
+
+ indentf(indent, "message-status: %d\n", msg->status);
+ indentf(indent, "name: %s\n", msg->name);
+ if (Global.core.verbose) {
+ indentf(indent, "length: %d\n", msg->len);
+ }
+
+ indentf(indent, "pdu:\n");
+ indent += YAML_INDENT;
+
+ if (Global.core.verbose) {
+ indentf(indent, "message-length: %d\n", msg->pdu.msg_len);
+ }
+
+ if (Global.core.verbose) {
+ indentf(indent, "smsc-addr-length: 0x%02X\n", msg->pdu.smsc_addr.len);
+ }
+ indentf(indent, "smsc-addr-type: 0x%02X\n", msg->pdu.smsc_addr.type);
+ indentf(indent, "smsc-addr: %s\n", msg->pdu.smsc_addr.addr);
+
+ indentf(indent, "type: 0x%02X\n", msg->pdu.type.type);
+ indentf(indent, "message-reference: 0x%02X\n", msg->pdu.msg_reference);
+ indentf(indent, "protocol-id: 0x%02X\n", msg->pdu.protocol_id);
+
+ if (Global.core.verbose) {
+ indentf(indent, "addr-length: 0x%02X\n", msg->pdu.addr.len);
+ }
+ indentf(indent, "addr-type: 0x%02X\n", msg->pdu.addr.type);
+ indentf(indent, "addr: %s\n", msg->pdu.addr.addr);
+
+ pdu_format_vp(&msg->pdu, buf, sizeof(buf));
+ indentf(indent, "validity-period: %s\n", buf);
+ pdu_format_timestamp(&msg->pdu, buf, sizeof(buf), "%Y-%m-%d %T %z");
+ indentf(indent, "timestamp: %s\n", buf);
+
+ if (Global.core.verbose) {
+ indentf(indent, "user-data-length: 0x%02X\n", msg->pdu.user_data_len);
+ }
+ indentf(indent, "user-data: \"%.*J\"\n", msg->pdu.user_data_len, msg->pdu.user_data);
+ indentf(indent, "\n");
+ }
+
+ indent = 0;
+
+ if (Global.core.verbose) {
+ indentf(indent, "\n");
+
+ indentf(indent, "statistics:\n");
+ indent += YAML_INDENT;
+
+ indentf(indent, "unread: %d\n", list_info->nr_unread);
+ indentf(indent, "read: %d\n", list_info->nr_read);
+ indentf(indent, "unsent: %d\n", list_info->nr_unsent);
+ indentf(indent, "sent: %d\n", list_info->nr_sent);
+ indentf(indent, "unknown: %d\n", list_info->nr_unknown);
+ indentf(indent, "total: %d\n", list_info->nr_msgs);
+ indentf(indent, "\n");
+ }
+
+ indent = 0;
+
+ indentf(indent, "...\n");
+
+ return 1;
+}
+
+enum {
+ LIST_STATE_BLANK,
+ LIST_STATE_OPEN,
+ LIST_STATE_PDU,
+ LIST_STATE_CLOSE,
+};
+
+static int list_info_callback(char *buf, size_t len, void *data)
+{
+ int err;
+ struct msg_list_info *list_info = (struct msg_list_info *) data;
+ struct sms_msg *msg;
+
+ list_info->nr_lines++;
+
+ switch (list_info->state) {
+ case LIST_STATE_BLANK:
+ log_debug("state: LIST_STATE_BLANK");
+
+ if (*buf) {
+ list_info->state = LIST_STATE_CLOSE;
+ log_debug("AT response error: %s", buf);
+ return -1;
+ }
+
+ list_info->state = LIST_STATE_OPEN;
+
+ return 0;
+
+ case LIST_STATE_OPEN:
+ log_debug("state: LIST_STATE_OPEN");
+
+ if (!strncmp(buf, CMGL_HEADER_START, strlen(CMGL_HEADER_START)) ||
+ !strncmp(buf, CMGR_HEADER_START, strlen(CMGR_HEADER_START))) {
+ list_info->state = LIST_STATE_PDU;
+
+ return process_header(list_info, buf, len);
+ } else if (!strcmp(buf, "OK")) {
+ list_info->state = LIST_STATE_CLOSE;
+ return 1;
+ } else if (*buf) {
+ list_info->state = LIST_STATE_CLOSE;
+
+ log_debug("AT response error: %s", buf);
+
+ return -1;
+ } else {
+ return 0;
+ }
+
+ case LIST_STATE_PDU:
+ log_debug("state: LIST_STATE_PDU");
+
+ if (list_empty(&list_info->msg_list)) {
+ log_debug("empty list");
+ return -1;
+ }
+
+ msg = list_entry(list_info->msg_list.prev, typeof(*msg), list);
+ if (msg->len > 0) {
+ err = pdu_decode(buf, &msg->pdu);
+ if (err < 0) {
+ log_warning("pdu decode failed: %d", err);
+ }
+ }
+
+ list_info->state = LIST_STATE_OPEN;
+
+ return 0;
+
+ default:
+ return -1;
+ }
+
+ return -1;
+}
+
+void sms_list_free(struct msg_list_info *list_info)
+{
+ struct sms_msg *msg;
+ struct sms_msg *msg_save;
+
+ list_for_each_entry_safe(msg, msg_save, &list_info->msg_list, list) {
+ list_del(&msg->list);
+ sms_msg_free(msg);
+ }
+}
+
+int sms_list_get(int fd, struct msg_list_info *list_info)
+{
+ int tmp;
+
+ INIT_LIST_HEAD(&list_info->msg_list);
+
+ list_info->state = LIST_STATE_BLANK;
+ list_info->nr_lines = 0;
+ list_info->nr_unread = 0;
+ list_info->nr_read = 0;
+ list_info->nr_unsent = 0;
+ list_info->nr_sent = 0;
+ list_info->nr_unknown = 0;
+ list_info->nr_msgs = 0;
+
+ tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE);
+ if (tmp < 0) {
+ log_error("setting pdu mode failed");
+ return -1;
+ }
+
+ switch (list_info->cmd_type) {
+ case CMD_TYPE_CMGL:
+ atcmd_writeline(fd, "AT+CMGL=%d", list_info->status);
+ break;
+
+ case CMD_TYPE_CMGR:
+ atcmd_writeline(fd, "AT+CMGR=%d", list_info->index);
+ break;
+
+ default:
+ return -1;
+ }
+
+ tmp = atcmd_response_foreach_line(fd, list_info_callback, list_info);
+ if (tmp < 0) {
+ log_error("listing failed");
+ sms_list_free(list_info);
+
+ return tmp;
+ }
+
+ return 0;
+}
+
+static int do_list(int fd, int cmd_type, int argc, char **argv)
+{
+ int tmp;
+ char *arg;
+ struct msg_list_info list_info;
+
+ memset(&list_info, 0, sizeof(list_info));
+
+ if (argc < 1) {
+ log_error("msg-status or index expected");
+ return false;
+ }
+ arg = *argv;
+ argc--; argv++;
+
+ list_info.cmd_type = cmd_type;
+
+ switch (list_info.cmd_type) {
+ case CMD_TYPE_CMGL:
+ if (!strcmp(arg, "unread")) {
+ list_info.status = SMS_MSG_REC_UNREAD;
+ } else if (!strcmp(arg, "read")) {
+ list_info.status = SMS_MSG_REC_READ;
+ } else if (!strcmp(arg, "unsent")) {
+ list_info.status = SMS_MSG_STO_UNSENT;
+ } else if (!strcmp(arg, "sent")) {
+ list_info.status = SMS_MSG_STO_SENT;
+ } else if (!strcmp(arg, "all")) {
+ list_info.status = SMS_MSG_ALL;
+ } else {
+ log_error("invalid msg-status %s", arg);
+ return false;
+ }
+
+ break;
+
+ case CMD_TYPE_CMGR:
+ list_info.index = atoi(arg);
+ break;
+
+ default:
+ return false;
+ }
+
+ tmp = sms_list_get(fd, &list_info);
+ if (tmp < 0) {
+ log_error("failed to get msg list");
+ return false;
+ }
+
+ print_list_yaml(&list_info);
+
+ sms_list_free(&list_info);
+
+ return true;
+}
+
+static char *short_options = "";
+static struct option long_options[] = {
+ {NULL, 0, NULL, 0},
+};
+
+void sms_list_help(FILE *out) {
+ fprintf(out, "usage: list STATUS\n");
+ fprintf(out, "where STATUS := { \n");
+ fprintf(out, " unread\n");
+ fprintf(out, " read\n");
+ fprintf(out, " unsent\n");
+ fprintf(out, " sent\n");
+ fprintf(out, " all\n");
+ fprintf(out, " }\n");
+ fprintf(out, "\n");
+}
+
+void sms_read_help(FILE *out) {
+ fprintf(out, "usage: read <index>\n");
+ fprintf(out, "\n");
+}
+
+static void help_select(FILE *out, int cmd_type)
+{
+ switch (cmd_type) {
+ case CMD_TYPE_CMGL:
+ sms_list_help(out);
+ break;
+ case CMD_TYPE_CMGR:
+ sms_read_help(out);
+ break;
+ default:
+ fprintf(out, "usage: ?\n");
+ }
+}
+
+int sms_list(int argc, char **argv)
+{
+ int i;
+ int option_index;
+ int ret;
+ int fd;
+
+ if (argc < 1) {
+ log_debug("should have received at least one argument");
+ return false;
+ }
+
+ char *object = *argv;
+ int cmd_type;
+
+ if (!tokcmp(object, "list")) {
+ cmd_type = CMD_TYPE_CMGL;
+ } else if (!tokcmp(object, "read")) {
+ cmd_type = CMD_TYPE_CMGR;
+ } else {
+ log_debug("object should be one of { list | read }");
+ return false;
+ }
+
+ while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) {
+ switch (i) {
+ case 0:
+ break;
+
+ default:
+ help_select(stderr, cmd_type);
+ return false;
+ }
+ }
+
+ if (optind >= argc) {
+ help_select(stderr, cmd_type);
+ return false;
+ }
+ argc -= optind;
+ argv += optind;
+
+ fd = tty_open(Global.core.device, Global.core.baud_rate);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open tty device %s\n", Global.core.device);
+ return false;
+ }
+ atcmd_init(fd, Global.core.read_timeout);
+
+ ret = do_list(fd, cmd_type, argc, argv);
+
+ tty_close(fd);
+
+ return ret;
+}