/* * SMS List Messages * * Copyright (C) 2010 by Multi-Tech Systems * * Author: James Maki * * 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 #include #include #include #include #include #include #include #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, "tele-id: 0x%04X\n", msg->pdu.teleservice_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) { if (is_telit_3gpp2_format()) { log_debug("using CDMA pdu decoding"); err = pdu_decode_cdma(buf, &msg->pdu); } else { log_debug("using GSM pdu decoding"); 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; 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 \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; int ret_3gpp = false; int ret_3gpp2 = false; 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 = sms_device_open(); if (fd < 0) { return false; } // LNA7/LNA7D/L4G1 with Verizon SIM SMS 3GPP, 3GPP2 WORKAROUND if (is_quectel_dual_format()) { int tmp; tmp = atcmd_plus_qcfg_write(fd, SMS_FORMAT_3GPP); if (tmp == 0) { ret_3gpp = do_list(fd, cmd_type, argc, argv); } tmp = atcmd_plus_qcfg_write(fd, SMS_FORMAT_3GPP2); if (tmp == 0) { ret_3gpp2 = do_list(fd, cmd_type, argc, argv); } ret = (ret_3gpp || ret_3gpp2); } else { ret = do_list(fd, cmd_type, argc, argv); } sms_device_close(fd); return ret; }