fix for accepting modem with baudrate 115200

Anupama R <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <E73FABD9FF04AC4885C6699163C7A40003C04B49@BLRM201E.blr.spcnl.co.in>
Hello list,
I have attached the file in which I had
made a fix for accepting baudrate 115200.  Hope
this is the right fix to make.
The fix is quite minor, just having to add
115200 in at2_set_speed function.

Now, how is it I am supposed to proceed? ask for votes?

Best Regards
Anu
smsc_at2.c (application/octet-stream, 63.1 KB)
/*
 * smsc_at2.c
 * 
 * New driver for serial connected AT based
 * devices.
 * 4.9.2001
 * Andreas Fink <[email protected]>
 * 
 */

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <time.h>

#include "gwlib/gwlib.h"
#include "gwlib/charset.h"
#include "smscconn.h"
#include "smscconn_p.h"
#include "bb_smscconn_cb.h"
#include "msg.h"
#include "sms.h"
#include "dlr.h"
#include "smsc_at2.h"


static void at2_octstr_destroy(Octstr *ostr)
{
    octstr_destroy(ostr);
}


int	at2_open_device1(PrivAT2data *privdata)
{
    info(0, "AT2[%s]: opening device", octstr_get_cstr(privdata->name));
    privdata->fd = open(octstr_get_cstr(privdata->device), 
                        O_RDWR | O_NONBLOCK | O_NOCTTY);
    if (privdata->fd == -1) {
        error(errno, "AT2[%s]: open failed! ERRNO=%d", octstr_get_cstr(privdata->name), errno);
        privdata->fd = -1;
        return -1;
    }
    debug("bb.smsc.at2", 0, "AT2[%s]: device opened", octstr_get_cstr(privdata->name));

    return 0;
}


int	at2_open_device(PrivAT2data *privdata)
{
    struct termios tios;
    int ret;

    if ((ret = at2_open_device1(privdata)) != 0)
        return ret;

    tcgetattr(privdata->fd, &tios);

    kannel_cfmakeraw(&tios);
                      
    tios.c_iflag |= IGNBRK; /* ignore break & parity errors */
    tios.c_iflag &= ~INPCK; /* INPCK: disable parity check */
    tios.c_cflag |= HUPCL; /* hangup on close */
    tios.c_cflag |= CREAD; /* enable receiver */
    tios.c_cflag &= ~CSIZE; /* set to 8 bit */
    tios.c_cflag |= CS8;
    tios.c_oflag &= ~ONLCR; /* no NL to CR-NL mapping outgoing */
    tios.c_iflag |= IGNPAR; /* ignore parity */
    tios.c_iflag &= ~INPCK;
    tios.c_cflag |= CRTSCTS; /* enable hardware flow control */
    tios.c_cc[VSUSP] = 0; /* otherwhise we can not send CTRL Z */

    /*
    if ( ModemTypes[privdata->modemid].enable_parity )
    	tios.c_cflag ^= PARODD;
    */

    ret = tcsetattr(privdata->fd, TCSANOW, &tios); /* apply changes now */
    if (ret == -1) {
        error(errno, "AT2[%s]: at_data_link: fail to set termios attribute",
              octstr_get_cstr(privdata->name));
    }
    tcflush(privdata->fd, TCIOFLUSH);
         
    /* 
     * Nokia 7110 and 6210 need some time between opening
     * the connection and sending the first AT commands 
     */
    if (privdata->modem->need_sleep)
        sleep(1);
    debug("bb.smsc.at2", 0, "AT2[%s]: device opened", octstr_get_cstr(privdata->name));
    return 0;
}


void at2_close_device(PrivAT2data *privdata)
{
    info(0, "AT2[%s]: closing device", octstr_get_cstr(privdata->name));
    close(privdata->fd);
    privdata->fd = -1;
}


void at2_read_buffer(PrivAT2data *privdata)
{
    char buf[MAX_READ + 1];
    int s, ret;
    int count;
    fd_set read_fd;
    struct timeval tv;

    if (privdata->fd == -1) {
        error(errno, "AT2[%s]: at2_read_buffer: fd = -1. Can not read", 
              octstr_get_cstr(privdata->name));
        return ;
    }
    count = MAX_READ;

#ifdef SSIZE_MAX
    if (count > SSIZE_MAX)
        count = SSIZE_MAX;
#endif

    tv.tv_sec = 0;
    tv.tv_usec = 1000;

    FD_ZERO(&read_fd);
    FD_SET(privdata->fd, &read_fd);
    ret = select(privdata->fd + 1, &read_fd, NULL, NULL, &tv);
    if (ret == -1) {
        if (!(errno == EINTR || errno == EAGAIN))
            error(errno, "AT2[%s]: error on select", octstr_get_cstr(privdata->name));
        return;
    }

    s = read(privdata->fd, buf, count);
    if (s > 0)
        octstr_append_data(privdata->ilb, buf, s);
}


Octstr *at2_wait_line(PrivAT2data *privdata, time_t timeout, int gt_flag)
{
    Octstr *line;
    time_t end_time;
    time_t cur_time;

    time(&end_time);
    if (timeout == 0)
        timeout = 3;
    end_time += timeout;

    if (privdata->lines != NULL)
        octstr_destroy(privdata->lines);
    privdata->lines = octstr_create("");
    while (time(&cur_time) <= end_time) {
        line = at2_read_line(privdata, gt_flag);
        if (line)
            return line;
    }
    return NULL;
}


Octstr *at2_read_line(PrivAT2data *privdata, int gt_flag)
{
    int	eol;
    int gtloc;
    int len;
    Octstr *line;
    Octstr *buf2;
    int i;

    at2_read_buffer(privdata);

    len = octstr_len(privdata->ilb);
    if (len == 0)
        return NULL;

    if (gt_flag)
        /* looking for > if needed */
        gtloc = octstr_search_char(privdata->ilb, '>', 0); 
    else
        gtloc = -1;

    /*   
    if (gt_flag && (gtloc != -1))
        debug("bb.smsc.at2", 0, "in at2_read_line with gt_flag=1, gtloc=%d, ilb=%s",
              gtloc, octstr_get_cstr(privdata->ilb));
    */

    eol = octstr_search_char(privdata->ilb, '\r', 0); /* looking for CR */

    if ( (gtloc != -1) && ( (eol == -1) || (eol > gtloc) ) )
        eol = gtloc;

    if (eol == -1)
        return NULL;

    line = octstr_copy(privdata->ilb, 0, eol);
    buf2 = octstr_copy(privdata->ilb, eol + 1, len);
    octstr_destroy(privdata->ilb);
    privdata->ilb = buf2;

    /* remove any non printable chars (including linefeed for example) */
    for (i = 0; i < octstr_len(line); i++) {
        if (octstr_get_char(line, i) < 32)
            octstr_set_char(line, i, ' ');
    }
    octstr_strip_blanks(line);

    /* empty line, skipping */
    if ((strcmp(octstr_get_cstr(line), "") == 0) && ( gt_flag == 0)) 
    {
        octstr_destroy(line);
        return NULL;
    }
    if ((gt_flag) && (gtloc != -1)) {
        /* got to re-add it again as the parser needs to see it */
        octstr_append_cstr(line, ">"); 
    }
    debug("bb.smsc.at2", 0, "AT2[%s]: <-- %s", octstr_get_cstr(privdata->name), 
          octstr_get_cstr(line));
    return line;
}


int at2_write_line(PrivAT2data *privdata, char *line)
{
    int count;
    int s = 0;
    Octstr *linestr = NULL;

    linestr = octstr_format("%s\r", line);

    debug("bb.smsc.at2", 0, "AT2[%s]: --> %s^M", octstr_get_cstr(privdata->name), line);

    count = octstr_len(linestr);
    s = write(privdata->fd, octstr_get_cstr(linestr), count);
    O_DESTROY(linestr);
    if (s < 0) {
        debug("bb.smsc.at2", 0, "AT2[%s]: write failed with errno %d", 
              octstr_get_cstr(privdata->name), errno);
        return s;
    }
    tcdrain(privdata->fd);
    gwthread_sleep((double) (privdata->modem == NULL ? 
        100 : privdata->modem->sendline_sleep) / 1000);
    return s;
}


int at2_write_ctrlz(PrivAT2data *privdata)
{
    int s;
    char *ctrlz = "\032" ;

    debug("bb.smsc.at2", 0, "AT2[%s]: --> ^Z", octstr_get_cstr(privdata->name));
    s = write(privdata->fd, ctrlz, 1);
    if (s < 0) {
        debug("bb.smsc.at2", 0, "AT2[%s]: write failed with errno %d", 
              octstr_get_cstr(privdata->name), errno);
        return s;
    }
    tcdrain(privdata->fd);
    gwthread_sleep((double) privdata->modem->sendline_sleep / 1000);
    return s;
}
      

int at2_write(PrivAT2data *privdata, char *line)
{
    int count;
    int s;

    count = strlen(line);
    debug("bb.smsc.at2", 0, "AT2[%s]: --> %s", octstr_get_cstr(privdata->name), line);
    s = write(privdata->fd, line, count);
    tcdrain(privdata->fd);
    return s;
}


void at2_flush_buffer(PrivAT2data *privdata)
{
    at2_read_buffer(privdata);
    octstr_destroy(privdata->ilb);
    privdata->ilb = octstr_create("");
}


int	at2_init_device(PrivAT2data *privdata)
{
    int res;
    int ret;
    Octstr *setpin;

    info(0, "AT2[%s]: init device", octstr_get_cstr(privdata->name));

    at2_set_speed(privdata, privdata->speed);
    res = at2_send_modem_command(privdata, "AT", 0, 0);
    if (res == -1) {
        /* 
         * first try failed, maybe we need another one 
         * after just having changed the speed 
         */
        res = at2_send_modem_command(privdata, "AT", 0, 0);
    }
    if (res == -1) {
        error(0, "AT2[%s]: no answer from modem", octstr_get_cstr(privdata->name));
        return -1;
    }

    at2_flush_buffer(privdata);

    if (at2_send_modem_command(privdata, "AT&F", 0, 0) == -1)
        return -1;

    if (at2_send_modem_command(privdata, "ATE0", 0, 0) == -1)
        return -1;

    at2_flush_buffer(privdata);

    /* enable hardware handshake */
    if (octstr_len(privdata->modem->enable_hwhs)) {
        if (at2_send_modem_command(privdata, 
            octstr_get_cstr(privdata->modem->enable_hwhs), 0, 0) == -1)
            info(0, "AT2[%s]: cannot enable hardware handshake", 
                 octstr_get_cstr(privdata->name));
    }

    /*
     * Check does the modem require a PIN and, if so, send it.
     * This is not supported by the Nokia Premicell 
     */
    if (!privdata->modem->no_pin) {
        ret = at2_send_modem_command(privdata, "AT+CPIN?", 10, 0);

        if (!privdata->pin_ready) {
            if (ret == 2) {
                if (privdata->pin == NULL)
                    return -1;
                setpin = octstr_format("AT+CPIN=%s", octstr_get_cstr(privdata->pin));
                ret = at2_send_modem_command(privdata, octstr_get_cstr(setpin), 0, 0);
                octstr_destroy(setpin);
                if (ret != 0 )
                    return -1;
            } else if (ret == -1)
                return -1;
        }

        /* 
         * we have to wait until +CPIN: READY appears before issuing
         * the next command. 10 sec should be suficient 
         */
        if (!privdata->pin_ready) {
            at2_wait_modem_command(privdata, 10, 0, NULL);
            if (!privdata->pin_ready) {
                at2_send_modem_command(privdata, "AT+CPIN?", 10, 0);
                if (!privdata->pin_ready) {
                    return -1; /* give up */
                }
            }
        }
    }
    /* 
     * Set the GSM SMS message center address if supplied 
     */
    if (octstr_len(privdata->sms_center)) {
        Octstr *temp;
        temp = octstr_create("AT+CSCA=");
        octstr_append_char(temp, 34);
        octstr_append(temp, privdata->sms_center);
        octstr_append_char(temp, 34);
        /* 
         * XXX If some modem don't process the +, remove it and add ",145"
         * and ",129" to national numbers
         */
        ret = at2_send_modem_command(privdata, octstr_get_cstr(temp), 0, 0);
        octstr_destroy(temp);
        if (ret == -1)
            return -1;
        if (ret > 0) {
            info(0, "AT2[%s]: Cannot set SMS message center, continuing", 
                 octstr_get_cstr(privdata->name));
        }
    }

    /* Set the modem to PDU mode and autodisplay of new messages */
    ret = at2_send_modem_command(privdata, "AT+CMGF=0", 0, 0);
    if (ret != 0 )
        return -1;

    /* lets see if it supports GSM SMS 2+ mode */
    ret = at2_send_modem_command(privdata, "AT+CSMS=?", 0, 0);
    if (ret != 0) {
        /* if it doesnt even understand the command, I'm sure it wont support it */
        privdata->phase2plus = 0; 
    } else {
        /* we have to take a part a string like +CSMS: (0,1,128) */
        Octstr *ts;
        int i;
        List *vals;

        ts = privdata->lines;
        privdata->lines = NULL;

        i = octstr_search_char(ts, '(', 0);
        if (i > 0) {
            octstr_delete(ts, 0, i + 1);
        }
        i = octstr_search_char(ts, ')', 0);
        if (i > 0) {
            octstr_truncate(ts, i);
        }
        vals = octstr_split(ts, octstr_imm(","));
        octstr_destroy(ts);
        ts = list_search(vals, octstr_imm("1"), (void*) octstr_item_match);
        if (ts)
            privdata->phase2plus = 1;
        list_destroy(vals, (void*) at2_octstr_destroy);
    }
    if (privdata->phase2plus) {
        info(0, "AT2[%s]: Phase 2+ is supported", octstr_get_cstr(privdata->name));
        ret = at2_send_modem_command(privdata, "AT+CSMS=1", 0, 0);
        if (ret != 0)
            return -1;
    }

    /* send init string */
    ret = at2_send_modem_command(privdata, octstr_get_cstr(privdata->modem->init_string), 0, 0);
    if (ret != 0)
        return -1;

    if (privdata->sms_memory_poll_interval && privdata->modem->message_storage) {
        /* set message storage location for "SIM buffering" using the CPMS command */
        Octstr *temp;
        temp = octstr_create("AT+CPMS=");
        octstr_append_char(temp, 34);
        octstr_append(temp, privdata->modem->message_storage);
        octstr_append_char(temp, 34);
        ret = at2_send_modem_command(privdata, octstr_get_cstr(temp), 0, 0);
        octstr_destroy(temp);
        if (ret != 0)
            return -1;
    }

    info(0, "AT2[%s]: AT SMSC successfully opened.", octstr_get_cstr(privdata->name));
    return 0;
}


int at2_send_modem_command(PrivAT2data *privdata, char *cmd, time_t timeout, int gt_flag)
{
    at2_write_line(privdata, cmd);
    return at2_wait_modem_command(privdata, timeout, gt_flag, NULL);
}


int at2_wait_modem_command(PrivAT2data *privdata, time_t timeout, int gt_flag, 
                           int *messages_collected)
{
    Octstr *line = NULL;
    Octstr *line2 = NULL;
    Octstr *pdu = NULL;
    int ret;
    time_t end_time;
    time_t cur_time;
    Msg	*msg;
    int len;
    int cmgr_flag = 0;

    time(&end_time);
    if (timeout == 0)
        timeout = 3;
    end_time += timeout;

    if (privdata->lines != NULL)
        octstr_destroy(privdata->lines);
    privdata->lines = octstr_create("");
    while (time(&cur_time) <= end_time) {
        O_DESTROY(line);
        line = at2_read_line(privdata, gt_flag);
        if (line) {
            octstr_append(privdata->lines, line);
            octstr_append_cstr(privdata->lines, "\n");

            if (octstr_search(line, octstr_imm("SIM PIN"), 0) != -1) {
                ret = 2;
                goto end;
            }
            if (octstr_search(line, octstr_imm("OK"), 0) != -1) {
                ret = 0;
                goto end;
            }
            if ((gt_flag ) && (octstr_search(line, octstr_imm(">"), 0) != -1)) {
                ret = 1;
                goto end;
            }
            if (octstr_search(line, octstr_imm("RING"), 0) != -1) {
                at2_write_line(privdata, "ATH0");
                continue;
            }
            if (octstr_search(line, octstr_imm("+CPIN: READY"), 0) != -1) {
                privdata->pin_ready = 1;
                continue;
            }
            if ( -1 != octstr_search(line, octstr_imm("+CMS ERROR"), 0)) {
                error(0, "AT2[%s]: CMS ERROR: %s", octstr_get_cstr(privdata->name), 
                      octstr_get_cstr(line));
                ret = 1;
                goto end;
            }
            if (octstr_search(line, octstr_imm("+CMT:"), 0) != -1 || 
                ((octstr_search(line, octstr_imm("+CMGR:"), 0) != -1) && (cmgr_flag = 1)) ) {
                line2 = at2_wait_line(privdata, 1, 0);

                if (line2 == NULL) {
                    error(0, "AT2[%s]: got +CMT but waiting for next line timed out", 
                          octstr_get_cstr(privdata->name));
                } else {
                    octstr_append_cstr(line, "\n");
                    octstr_append(line, line2);
                    O_DESTROY(line2);
                    at2_pdu_extract(privdata, &pdu, line);

                    if (pdu == NULL) {
                        error(0, "AT2[%s]: got +CMT but pdu_extract failed", 
                              octstr_get_cstr(privdata->name));
                    } else {
                        /* count message even if I can't decode it */
                        if (messages_collected)
                            ++(*messages_collected);
                        msg = at2_pdu_decode(pdu, privdata);
                        if (msg != NULL) {
                            msg->sms.smsc_id = octstr_duplicate(privdata->conn->id);
                            bb_smscconn_receive(privdata->conn, msg);
                        }

                        if (!cmgr_flag) {
                            if (privdata->phase2plus)
                                at2_write_line(privdata, "AT+CNMA");
                        }

                        O_DESTROY(pdu);
                    }
                }
                continue;
            }

            if ( -1 != octstr_search(line, octstr_imm("ERROR"), 0)) {
                ret = -1;
                goto end;
            }
        }
    }

    len = octstr_len(privdata->ilb);
    /*
    error(0,"AT2[%s]: timeout. received <%s> until now, buffer size is %d, buf=%s",
          octstr_get_cstr(privdata->name),
          privdata->lines ? octstr_get_cstr(privdata->lines) : "<nothing>", len,
          privdata->ilb ? octstr_get_cstr(privdata->ilb) : "<nothing>");
    */
    O_DESTROY(line);
    O_DESTROY(line2);
    O_DESTROY(pdu);
    return -1; /* timeout */

end:
    octstr_append(privdata->lines, line);
    octstr_append_cstr(privdata->lines, "\n");
    O_DESTROY(line);
    O_DESTROY(line2);
    O_DESTROY(pdu);
    return ret;
}


void at2_read_sms_memory(PrivAT2data* privdata)
{
    char cmd[20];

    /* get memory status */
    if (at2_check_sms_memory(privdata) == -1) {
        debug("bb.smsc.at2", 0, "AT2[%s]: memory check error", octstr_get_cstr(privdata->name));
        return ;
    }

    if (privdata->sms_memory_usage) { 
        /*
         * that is - greater then 0, meaning there are some messages to fetch
         * now - I used to just loop over the first input_mem_sms_used locations, 
         * but it doesn't hold, since under load, messages may be received while 
         * we're in the loop, and get stored in locations towards the end of the list, 
         * thus creating 'holes' in the memory. 
         * 
         * There are two ways we can fix this : 
         *   (a) Just read the last message location, delete it and return.
         *       It's not a complete solution since holes can still be created if messages 
         *       are recieved between the memory check and the delete command, 
         *       and anyway - it will slow us down and won't hold well under pressure
         *   (b) Just scan the entire memory each call, bottom to top. 
         *       This will be slow too, but it'll be reliable.
         *
         * We can massivly improve performance by stopping after input_mem_sms_used messages
         * have been read, but send_modem_command returns 0 for no message as well as for a 
         * message read, and the only other way to implement it is by doing memory_check 
         * after each read and stoping when input_mem_sms_used get to 0. This is slow 
         * (modem commands take time) so we improve speed only if there are less then 10 
         * messages in memory.
         *
         * I implemented the alternative - changed at2_wait_modem_command to return the 
         * number of messages it collected.
         */
        int i;
        int message_count = 0; /* cound number of messages collected */

        debug("bb.smsc.at2", 0, "AT2[%s]: %d messages waiting in memory", 
              octstr_get_cstr(privdata->name), privdata->sms_memory_usage);

        /*
         * loop till end of memory or collected enouch messages
         */
        for (i = 1; i <= privdata->sms_memory_capacity &&
             message_count < privdata->sms_memory_usage; ++i) { 
            int old_message_count = message_count;
            sprintf(cmd, "AT+CMGR=%d", i);
            /* read one message from memory */
            at2_write_line(privdata, cmd);
            if (at2_wait_modem_command(privdata, 0, 0, &message_count) != 0) {
                debug("bb.smsc.at2", 0, "AT2[%s]: failed to get message %d.", 
                      octstr_get_cstr(privdata->name), i);
                continue; /* failed to read the message - skip to next message */
            }

            /* no need to delete if no message collected */
            if (old_message_count == message_count) { 
                debug("bb.smsc.at2", 0, "AT2[%s]: not deleted.", 
                      octstr_get_cstr(privdata->name));
                continue;
            }

            sprintf(cmd, "AT+CMGD=%d", i); /* delete the message we just read */
            /* 
             * 3 seconds is not enough with some modems if the message is large,
             * so we'll give it 7 seconds 
             */
            if (at2_send_modem_command(privdata, cmd, 7, 0) != 0) {  
                /* 
                 * failed to delete the message, we'll just ignore it for now, 
                 * this is bad, since if the message really didn't get deleted
                 * we'll see it next time around. 
                 */                
                debug("bb.smsc.at2", 0, "AT2[%s]: failed to delete message %d.", 
                      octstr_get_cstr(privdata->name), i);
                continue; 

            }
        }
    }
    /*
    at2_send_modem_command(privdata, ModemTypes[privdata->modemid].init1, 0, 0);
    */
}


int at2_check_sms_memory(PrivAT2data *privdata)
{
    long values[4]; /* array to put response data in */
    int pos; /* position of parser in data stream */
    int ret;
    Octstr* search_cpms = NULL;

    /* select memory type and get report */
    if ((ret = at2_send_modem_command(privdata, "AT+CPMS?", 0, 0)) != 0) { 
        debug("bb.smsc.at2.memory_check", 0, "failed to send mem select command to modem %d", ret);
        return -1;
    }

    search_cpms = octstr_create("+CPMS:");

    if ((pos = octstr_search(privdata->lines, search_cpms, 0)) != -1) {
        /* got back a +CPMS response */
        int index = 0; /* index in values array */
        pos += 6; /* position of parser in the stream - start after header */

        /* skip memory indication */
        pos = octstr_search(privdata->lines, octstr_imm(","), pos) + 1; 

        /* find all the values */
        while (index < 4 && pos < octstr_len(privdata->lines) &&
               (pos = octstr_parse_long(&values[index], privdata->lines, pos, 10)) != -1) { 
            ++pos; /* skip number seperator */
            ++index; /* increment array index */
            if (index == 2)
                /* skip second memory indication */
                pos = octstr_search(privdata->lines, octstr_imm(","), pos) + 1; 
        }

        if (index < 4) { 
            /* didn't get all memory data - I don't why, so I'll bail */
            debug("bb.smsc.at2", 0, "AT2[%s]: couldn't parse all memory locations : %d:'%s'.",
                  octstr_get_cstr(privdata->name), index, 
                  &(octstr_get_cstr(privdata->lines)[pos]));
            O_DESTROY(search_cpms);
            return -1;
        }

        privdata->sms_memory_usage = values[0];
        privdata->sms_memory_capacity = values[1];
        /*
        privdata->output_mem_sms_used = values[2];
        privdata->output_mem_sms_capacity = values[3];
        */

        /* everything's cool */
        ret = 0; 

        /*  clear the buffer */
        O_DESTROY(privdata->lines);

    } else {
        debug("bb.smsc.at2", 0, "AT2[%s]: no correct header for CPMS response.", 
              octstr_get_cstr(privdata->name));

        /* didn't get a +CPMS response - this is clearly an error */
        ret = -1; 
    }

    O_DESTROY(search_cpms);
    return ret;
}


void at2_set_speed(PrivAT2data *privdata, int bps)
{
    struct termios tios;
    int ret;
    int	speed;

    tcgetattr(privdata->fd, &tios);

    switch (bps) {
    case 300:
        speed = B300;
        break;
    case 1200:
        speed = B1200;
        break;
    case 2400:
        speed = B2400;
        break;
    case 4800:
        speed = B4800;
        break;
    case 9600:
        speed = B9600;
        break;
    case 19200:
        speed = B19200;
        break;
    case 38400:
        speed = B38400;
        break;
	case 115200:
		speed = B115200;
		break;
#ifdef B57600
    case 57600:
        speed = B57600;
        break;
#endif
    default:
        speed = B9600;
    }
    cfsetospeed(&tios, speed);
    cfsetispeed(&tios, speed);
    ret = tcsetattr(privdata->fd, TCSANOW, &tios); /* apply changes now */
    if (ret == -1) {
        error(errno, "AT2[%s]: at_data_link: fail to set termios attribute",
              octstr_get_cstr(privdata->name));
    }
    tcflush(privdata->fd, TCIOFLUSH);

    info(0, "AT2[%s]: speed set to %d", octstr_get_cstr(privdata->name), bps);
}


void at2_device_thread(void *arg)
{
    SMSCConn *conn = arg;
    PrivAT2data	*privdata = conn->data;

    int l, wait = 0;
    long idle_timeout, memory_poll_timeout = 0;

    conn->status = SMSCCONN_CONNECTING;

reconnect:

    do {
        if (wait) {
            if (conn->status == SMSCCONN_ACTIVE) {
                mutex_lock(conn->flow_mutex);
                conn->status = SMSCCONN_RECONNECTING;
                mutex_unlock(conn->flow_mutex);
            }
            info(0, "AT2[%s]: waiting for %d %s before trying to connect again", 
                 octstr_get_cstr(privdata->name), (wait < 60 ? wait : wait / 60),
                 (wait < 60 ? "seconds" : "minutes"));
            gwthread_sleep(wait);
            wait = wait > (privdata->retry ? 3600 : 600) ? 
                (privdata->retry ? 3600 : 600) : wait * 2;
        } else
            wait = 15;

        /* If modems->speed is defined, try to use it, else autodetect */
        if (privdata->speed == 0 && privdata->modem != NULL && 
	    privdata->modem->speed != 0) {

	    info(0, "AT2[%s]: trying to use speed <%ld> from modem definition", 
	         octstr_get_cstr(privdata->name), privdata->modem->speed);
	    if(0 == at2_test_speed(privdata, privdata->modem->speed)) { 
		privdata->speed = privdata->modem->speed;
		info(0, "AT2[%s]: speed is %ld", 
		     octstr_get_cstr(privdata->name), privdata->speed);
	    } else {
		info(0, "AT2[%s]: speed in modem definition don't work, will autodetect", 
		     octstr_get_cstr(privdata->name));
	    }
	}

        if (privdata->speed == 0) {
	    if (at2_detect_speed(privdata) == -1) {
		if (!privdata->retry)
		    return;
		else
		    continue;
            }
        }

        if (privdata->modem == NULL) {
            if (at2_detect_modem_type(privdata) == -1) {
                if (!privdata->retry)
                    return;
                else
                    continue;
            }
        }

        if (at2_open_device(privdata)) {
            error(errno, "AT2[%s]: at2_device_thread: open_at2_device failed. Terminating", 
                  octstr_get_cstr(privdata->name));
            if (!privdata->retry)
                return;
            else
                continue;
        }

        if (at2_init_device(privdata) != 0) {
            error(0, "AT2[%s]: Opening failed. Terminating", octstr_get_cstr(privdata->name));
            if (!privdata->retry) {
                privdata->shutdown = 1;
                return;
            } else
                continue;
        }

        /* If we got here, then the device is opened */
        break;
    } while (privdata->retry && !privdata->shutdown);

    conn->status = SMSCCONN_ACTIVE;
    bb_smscconn_connected(conn);

    idle_timeout = 0;
    while (!privdata->shutdown) {
        l = list_len(privdata->outgoing_queue);
        if (l > 0) {
            at2_send_messages(privdata);
            idle_timeout = time(NULL);
        } else
            at2_wait_modem_command(privdata, 1, 0, NULL);

        if (privdata->keepalive &&
            idle_timeout + privdata->keepalive < time(NULL)) {
            if (at2_send_modem_command(privdata, 
                octstr_get_cstr(privdata->modem->keepalive_cmd), 5, 0) < 0) {
                at2_close_device(privdata);
                conn->status = SMSCCONN_RECONNECTING;
                wait = 15;
                goto reconnect;
            }
            idle_timeout = time(NULL);
        }

        if (privdata->sms_memory_poll_interval &&
            memory_poll_timeout + privdata->sms_memory_poll_interval < time(NULL)) {
            at2_read_sms_memory(privdata);
            memory_poll_timeout = time(NULL);
        }
    }
    at2_close_device(privdata);
    conn->status = SMSCCONN_DISCONNECTED;
    /* maybe some cleanup here? */
    at2_destroy_modem(privdata->modem);
    gw_free(conn->data);
    conn->data = NULL;
    conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
    conn->status = SMSCCONN_DEAD;
    bb_smscconn_killed();
}


int at2_shutdown_cb(SMSCConn *conn, int finish_sending)
{
    PrivAT2data *privdata = conn->data;

    debug("bb.sms", 0, "AT2[%s]: Shutting down SMSCConn, %s",
          octstr_get_cstr(privdata->name),
          finish_sending ? "slow" : "instant");

    /* 
     * Documentation claims this would have been done by smscconn.c,
     * but isn't when this code is being written. 
     */
    conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
    privdata->shutdown = 1; 
    /* 
     * Separate from why_killed to avoid locking, as
     * why_killed may be changed from outside? 
     */
    if (finish_sending == 0) {
        Msg *msg;
        while ((msg = list_extract_first(privdata->outgoing_queue)) != NULL) {
            bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_SHUTDOWN);
        }
    }
    gwthread_wakeup(privdata->device_thread);
    return 0;

}


long at2_queued_cb(SMSCConn *conn)
{
    PrivAT2data *privdata = conn->data;
    long ret = list_len(privdata->outgoing_queue);

    /* use internal queue as load, maybe something else later */

    conn->load = ret;
    return ret;
}


void at2_start_cb(SMSCConn *conn)
{
    PrivAT2data *privdata = conn->data;

    /* in case there are messages in the buffer already */
    gwthread_wakeup(privdata->device_thread);
    debug("smsc.at2", 0, "AT2[%s]: start called", octstr_get_cstr(privdata->name));
}


int at2_add_msg_cb(SMSCConn *conn, Msg *sms)
{
    PrivAT2data *privdata = conn->data;
    Msg *copy;

    copy = msg_duplicate(sms);
    list_produce(privdata->outgoing_queue, copy);
    gwthread_wakeup(privdata->device_thread);
    return 0;
}


int smsc_at2_create(SMSCConn *conn, CfgGroup *cfg)
{
    PrivAT2data	*privdata;
    Octstr *modem_type_string;

    privdata = gw_malloc(sizeof(PrivAT2data));
    privdata->outgoing_queue = list_create();

    privdata->configfile = cfg_get_configfile(cfg);

    privdata->device = cfg_get(cfg, octstr_imm("device"));
    if (privdata->device == NULL) {
        error(0, "AT2[-]: 'device' missing in at2 configuration.");
        goto error;
    }

    privdata->name = cfg_get(cfg, octstr_imm("smsc-id"));
    if (privdata->name == NULL) {
        privdata->name = octstr_duplicate(privdata->device);
    }

    privdata->speed = 0;
    cfg_get_integer(&privdata->speed, cfg, octstr_imm("speed"));

    privdata->keepalive = 0;
    cfg_get_integer(&privdata->keepalive, cfg, octstr_imm("keepalive"));

    cfg_get_bool(&privdata->sms_memory_poll_interval, cfg, octstr_imm("sim-buffering"));
    if (privdata->sms_memory_poll_interval) {
        if (privdata->keepalive)
            privdata->sms_memory_poll_interval = privdata->keepalive;
        else
            privdata->sms_memory_poll_interval = AT2_DEFAULT_SMS_POLL_INTERVAL;
    }

    cfg_get_bool(&privdata->retry, cfg, octstr_imm("retry"));
    privdata->my_number = cfg_get(cfg, octstr_imm("my-number"));
    privdata->sms_center = cfg_get(cfg, octstr_imm("sms-center"));
    modem_type_string = cfg_get(cfg, octstr_imm("modemtype"));

    privdata->modem = NULL;

    if (modem_type_string != NULL) {
        if (octstr_compare(modem_type_string, octstr_imm("auto")) == 0 ||
            octstr_compare(modem_type_string, octstr_imm("autodetect")) == 0)
            O_DESTROY(modem_type_string);
    }

    if (octstr_len(modem_type_string) == 0) {
        info(0, "AT2[%s]: configuration doesn't show modemtype. will autodetect",
             octstr_get_cstr(privdata->name));
    } else {
        info(0, "AT2[%s]: configuration shows modemtype <%s>",
             octstr_get_cstr(privdata->name),
             octstr_get_cstr(modem_type_string));
        privdata->modem = at2_read_modems(privdata, privdata->configfile,
                                          modem_type_string, 0);
        if (privdata->modem == NULL) {
            info(0, "AT2[%s]: modemtype not found, revert to autodetect",
                 octstr_get_cstr(privdata->name));
        } else {
            info(0, "AT2[%s]: read modem definition for <%s>",
                 octstr_get_cstr(privdata->name),
                 octstr_get_cstr(privdata->modem->name));
        }
        O_DESTROY(modem_type_string);
    }

    privdata->ilb = octstr_create("");
    privdata->fd = -1;
    privdata->lines = NULL;
    privdata->pin = cfg_get(cfg, octstr_imm("pin"));
    privdata->pin_ready = 0;
    privdata->conn = conn;
    privdata->phase2plus = 0;
    privdata->validityperiod = cfg_get(cfg, octstr_imm("validityperiod"));

    cfg_get_bool(&privdata->alt_dcs, cfg, octstr_imm("alt-dcs"));

    conn->data = privdata;
    conn->name = octstr_format("AT2[%s]", octstr_get_cstr(privdata->name));

    privdata->shutdown = 0;

    conn->status = SMSCCONN_CONNECTING;
    conn->connect_time = time(NULL);

    if ((privdata->device_thread = gwthread_create(at2_device_thread, conn)) == -1) {
        privdata->shutdown = 1;
        goto error;
    }

    conn->shutdown = at2_shutdown_cb;
    conn->queued = at2_queued_cb;
    conn->start_conn = at2_start_cb;
    conn->send_msg = at2_add_msg_cb;
    return 0;

error:
    error(0, "AT2[%s]: Failed to create at2 smsc connection",
          octstr_len(privdata->name) ? octstr_get_cstr(privdata->name) : "");
    if (privdata != NULL) {
        list_destroy(privdata->outgoing_queue, NULL);
    }
    gw_free(privdata);
    conn->why_killed = SMSCCONN_KILLED_CANNOT_CONNECT;
    conn->status = SMSCCONN_DEAD;
    info(0, "AT2[%s]: exiting", octstr_get_cstr(privdata->name));
    return -1;
}


int at2_pdu_extract(PrivAT2data *privdata, Octstr **pdu, Octstr *line)
{
    Octstr *buffer;
    long len = 0;
    int pos = 0;
    int tmp;

    buffer = octstr_duplicate(line);
    /* find the beginning of a message from the modem*/
    if ((pos = octstr_search(buffer, octstr_imm("+CMT:"), 0)) != -1)
        pos += 5;
    else if ((pos = octstr_search(buffer, octstr_imm("+CMGR:"), 0)) != -1) {
        pos += 6;
        if ((pos = octstr_search(buffer, octstr_imm(","), pos)) != -1) 
            /* skip status field in +CMGR response */
            pos++;
        else
            goto nomsg;
    } else
        goto nomsg;

    tmp = octstr_search(buffer, octstr_imm(","), pos);
    if (! privdata->modem->broken && tmp == -1)
        goto nomsg;
    if (tmp != -1)
        pos = tmp + 1;

    /* The message length is after the comma */
    pos = octstr_parse_long(&len, buffer, pos, 10);
    if (pos == -1)
        goto nomsg;

    /* skip the spaces and line return */
    while (isspace(octstr_get_char(buffer, pos)))
        pos++;

    /* skip the SMSC address on some modem types */
    if (!privdata->modem->no_smsc) {
        tmp = at2_hexchar(octstr_get_char(buffer, pos)) * 16
              + at2_hexchar(octstr_get_char(buffer, pos + 1));
        if (tmp < 0)
            goto nomsg;
        tmp = 2 + tmp * 2;
        pos += tmp;
    }

    /* check if the buffer is long enough to contain the full message */
    if (!privdata->modem->broken && octstr_len(buffer) < len * 2 + pos)
        goto nomsg;

    if (privdata->modem->broken && octstr_len(buffer) < len * 2)
        goto nomsg;

    /* copy the PDU then remove it from the input buffer*/
    *pdu = octstr_copy(buffer, pos, len * 2);

    octstr_destroy(buffer);
    return 1;

nomsg:
    octstr_destroy(buffer);
    return 0;
}


int at2_hexchar(int hexc)
{
    hexc = toupper(hexc) - 48;
    return (hexc > 9) ? hexc - 7 : hexc;
}


Msg *at2_pdu_decode(Octstr *data, PrivAT2data *privdata)
{
    int type;
    Msg *msg = NULL;

    /* Get the PDU type */
    type = octstr_get_char(data, 1) & 3;

    switch (type) {

        case AT_DELIVER_SM:
            msg = at2_pdu_decode_deliver_sm(data, privdata);
            break;

            /* Add other message types here: */

    }

    return msg;
}


Msg *at2_pdu_decode_deliver_sm(Octstr *data, PrivAT2data *privdata)
{
    int len, pos, i, ntype;
    int udhi, dcs, udhlen, pid;
    Octstr *origin = NULL;
    Octstr *udh = NULL;
    Octstr *text = NULL, *tmpstr;
    Octstr *pdu = NULL;
    Msg *message = NULL;
    struct universaltime mtime; /* time structure */
    long stime; /* time in seconds */
    int timezone; /* timezone in 15 minutes jumps from GMT */

    /* 
     * Note: some parts of the PDU are not decoded because they are
     * not needed for the Msg type. 
     */

    /* convert the pdu to binary format for ease of processing */
    pdu = at2_convertpdu(data);

    /* UDH Indicator */
    udhi = (octstr_get_char(pdu, 0) & 64) >> 6;

    /* originating address */
    len = octstr_get_char(pdu, 1);
    ntype = octstr_get_char(pdu, 2);

    pos = 3;
    if ((ntype & 0xD0) == 0xD0) {
        /* Alphanumeric sender */
        origin = octstr_create("");
        tmpstr = octstr_copy(pdu, 3, len);
        at2_decode7bituncompressed(tmpstr, (((len - 1) * 4 - 3) / 7) + 1, origin, 0);
        octstr_destroy(tmpstr);
        debug("bb.smsc.at2", 0, "AT2[%s]: Alphanumeric sender <%s>", 
              octstr_get_cstr(privdata->name), octstr_get_cstr(origin));
        pos += (len + 1) / 2;
    } else {
        origin = octstr_create("");
        if ((ntype & 0x90) == 0x90) {
            /* International number */
            octstr_append_char(origin, '+');
        }
        for (i = 0; i < len; i += 2, pos++) {
            octstr_append_char(origin, (octstr_get_char(pdu, pos) & 15) + 48);
            if (i + 1 < len)
                octstr_append_char(origin, (octstr_get_char(pdu, pos) >> 4) + 48);
        }
        debug("bb.smsc.at2", 0, "AT2[%s]: Numeric sender %s <%s>", 
              octstr_get_cstr(privdata->name), ((ntype & 0x90) == 0x90 ? "(international)" : ""), 
              octstr_get_cstr(origin));
    }

    /* PID */
    pid = octstr_get_char(pdu, pos);
    pos++;

    /* DCS */
    dcs = octstr_get_char(pdu, pos);
    pos++;

    /* get the timestamp */
    mtime.year = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;
    mtime.year += (mtime.year < 70 ? 2000 : 1900);
    mtime.month = swap_nibbles(octstr_get_char(pdu, pos)) ;
    pos++;
    mtime.day = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;
    mtime.hour = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;
    mtime.minute = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;
    mtime.second = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;

    /* 
     * time zone: 
     *
     * time zone is "swapped nibble", with the MSB as the sign (1 is negative).  
     */
    timezone = swap_nibbles(octstr_get_char(pdu, pos));
    pos++;
    timezone = ((timezone >> 7) ? -1 : 1) * (timezone & 127);
    /* 
     * Ok, that was the time zone as read from the PDU. Now how to interpert it? 
     * All the handsets I tested send the timestamp of their local time and the 
     * timezone as GMT+0. I assume that the timestamp is the handset's local time, 
     * so we need to apply the timezone in reverse to get GM time: 
     */

    /* 
     * time in PDU is handset's local time and timezone is handset's time zone 
     * difference from GMT 
     */
    mtime.hour -= timezone / 4;
    mtime.minute -= 15 * (timezone % 4);

    stime = date_convert_universal(&mtime);

    /* get data length */
    len = octstr_get_char(pdu, pos);
    pos++;

    /* if there is a UDH */
    udhlen = 0;
    if (udhi) {
        udhlen = octstr_get_char(pdu, pos);
        pos++;
        udh = octstr_copy(pdu, pos, udhlen);
        pos += udhlen;
        len -= udhlen + 1;
    }

    /* build the message */
    message = msg_create(sms);
    if (!dcs_to_fields(&message, dcs)) {
        /* XXX Should reject this message? */
        debug("bb.smsc.at2", 0, "AT2[%s]: Invalid DCS",
              octstr_get_cstr(privdata->name));
        dcs_to_fields(&message, 0);
    }

    message->sms.pid = pid;

    /* deal with the user data -- 7 or 8 bit encoded */
    tmpstr = octstr_copy(pdu, pos, len);
    if (message->sms.coding == DC_8BIT || message->sms.coding == DC_UCS2) {
        text = octstr_duplicate(tmpstr);
    } else {
        int offset = 0;
        text = octstr_create("");
        if (udhi && message->sms.coding == DC_7BIT) {
            int nbits;
            nbits = (udhlen + 1) * 8;
            /* fill bits for UDH to septet boundary */
            offset = (((nbits / 7) + 1) * 7 - nbits) % 7;     
        }
        at2_decode7bituncompressed(tmpstr, len, text, offset);
    }

    message->sms.sender = origin;
    if (octstr_len(privdata->my_number)) {
        message->sms.receiver = octstr_duplicate(privdata->my_number);
    } else {
        /* Put a dummy address in the receiver for now (SMSC requires one) */
        message->sms.receiver = octstr_create_from_data("1234", 4);
    }
    if (udhi) {
        message->sms.udhdata = udh;
    }
    message->sms.msgdata = text;
    message->sms.time = stime;

    /* cleanup */
    octstr_destroy(pdu);
    octstr_destroy(tmpstr);

    return message;
}


Octstr *at2_convertpdu(Octstr *pdutext)
{
    Octstr *pdu;
    int i;
    int len = octstr_len(pdutext);

    pdu = octstr_create("");
    for (i = 0; i < len; i += 2) {
        octstr_append_char(pdu, at2_hexchar(octstr_get_char(pdutext, i)) * 16
                           + at2_hexchar(octstr_get_char(pdutext, i + 1)));
    }
    return pdu;
}


int at2_rmask[8] = { 0, 1, 3, 7, 15, 31, 63, 127 };
int at2_lmask[8] = { 0, 128, 192, 224, 240, 248, 252, 254 };

void at2_decode7bituncompressed(Octstr *input, int len, Octstr *decoded, int offset)
{
    unsigned char septet, octet, prevoctet;
    int i;
    int r = 1;
    int c = 7;
    int pos = 0;

    /* Shift the buffer offset bits to the left */
    if (offset > 0) {
        unsigned char *ip;
        for (i = 0, ip = octstr_get_cstr(input); i < octstr_len(input); i++) {
            if (i == octstr_len(input) - 1)
                *ip = *ip >> offset;
            else
                *ip = (*ip >> offset) | (*(ip + 1) << (8 - offset));
            ip++;
        }
    }
    octet = octstr_get_char(input, pos);
    prevoctet = 0;
    for (i = 0; i < len; i++) {
        septet = ((octet & at2_rmask[c]) << (r - 1)) + prevoctet;
        octstr_append_char(decoded, septet);

        prevoctet = (octet & at2_lmask[r]) >> c;

        /* When r=7 we have a full character in prevoctet */
        if ((r == 7) && (i < len - 1)) {
            i++;
            octstr_append_char(decoded, prevoctet);
            prevoctet = 0;
        }

        r = (r > 6) ? 1 : r + 1;
        c = (c < 2) ? 7 : c - 1;

        pos++;
        octet = octstr_get_char(input, pos);
    }
    charset_gsm_to_latin1(decoded);
}


void at2_send_messages(PrivAT2data *privdata)
{
    Msg *msg;

    do {
        if (privdata->modem->enable_mms && 
			list_len(privdata->outgoing_queue) > 1)
            at2_send_modem_command(privdata, "AT+CMMS=2", 0, 0);

        if ((msg = list_extract_first(privdata->outgoing_queue)))
            at2_send_one_message(privdata, msg);
    } while (msg);
}


void at2_send_one_message(PrivAT2data *privdata, Msg *msg)
{
    unsigned char command[500], pdu[500];
    int ret = -1;
    char sc[3];
    int retries = RETRY_SEND;

    if (octstr_len(privdata->my_number)) {
        octstr_destroy(msg->sms.sender);
        msg->sms.sender = octstr_duplicate(privdata->my_number);
    }

    /* 
     * The standard says you should be prepending the PDU with 00 to indicate 
     * to use the default SC. Some older modems dont expect this so it can be 
     * disabled 
     * NB: This extra padding is not counted in the CMGS byte count 
     */
    sc[0] = '\0';

    if (!privdata->modem->no_smsc)
        strcpy(sc, "00");

    if (msg_type(msg) == sms) {
        at2_pdu_encode(msg, &pdu[0], privdata);

        ret = -99;
        retries = RETRY_SEND;
        while ((ret != 0) && (retries-- > 0)) {
            /* 
             * send the initial command and then wait for > 
             */
            sprintf(command, "AT+CMGS=%d", strlen(pdu) / 2);
            
            ret = at2_send_modem_command(privdata, command, 5, 1);
            debug("bb.at", 0, "AT2[%s]: send command status: %d",
                  octstr_get_cstr(privdata->name), ret);

            if (ret != 1) /* > only! */
                continue;
            /* 
             * ok the > has been see now so we can send the PDU now and a 
             * control Z but no CR or LF 
             */
            sprintf(command, "%s%s", sc, pdu);
            at2_write(privdata, command);
            at2_write_ctrlz(privdata);
            
            /* wait 20 secs for modem command */
            ret = at2_wait_modem_command(privdata, 20, 0, NULL);
            debug("bb.at", 0, "AT2[%s]: send command status: %d",
                  octstr_get_cstr(privdata->name), ret);
            
            if (ret != 0) /* OK only */
                continue;
            counter_increase(privdata->conn->sent);
            bb_smscconn_sent(privdata->conn, msg);
        }

        if (ret != 0) {
            /*
             * no need to do counter_increase(privdata->conn->failed) here, 
             * since bb_smscconn_send_failed() will inc the counter on 
             * SMSCCONN_FAILED_MALFORMED
             */
            bb_smscconn_send_failed(privdata->conn, msg, SMSCCONN_FAILED_MALFORMED);
        }
    }
}


int at2_pdu_encode(Msg *msg, unsigned char *pdu, PrivAT2data *privdata)
{
    int pos = 0, i, len, setvalidity = 0;
    int ntype = PNT_UNKNOWN; /* number type default */
    int nstartpos = 0; /* offset for the phone number */
    int dcs; /* data coding scheme (GSM 03.38) */

    /* 
     * The message is encoded directly in the text representation of
     * the hex values that will be sent to the modem.
     * Each octet is coded with two characters. 
     */

    /* 
     * message type SUBMIT
     *    01010001 = 0x51 indicating add. UDH, TP-VP(Rel) & MSG_SUBMIT
     * or 00010001 = 0x11 for just TP-VP(Rel) & MSG_SUBMIT 
     */

    pdu[pos] = octstr_len(msg->sms.udhdata) ? at2_numtext(5) : at2_numtext(1);
    pos++;
    pdu[pos] = at2_numtext(AT_SUBMIT_SM);
    pos++;

    /* message reference (0 for now) */
    pdu[pos] = at2_numtext(0);
    pos++;
    pdu[pos] = at2_numtext(0);
    pos++;

    /* destination address */
    octstr_strip_blanks(msg->sms.receiver); /* strip blanks before length calculation */
    len = octstr_len(msg->sms.receiver);

    /* 
     * Check for international numbers
     * number starting with '+' or '00' are international,
     * others are national. 
     */
    if (strncmp(octstr_get_cstr(msg->sms.receiver), "+", 1) == 0) {
        debug("bb.smsc.at2", 0, "AT2[%s]: international starting with + (%s)",
              octstr_get_cstr(privdata->name), octstr_get_cstr(msg->sms.receiver));
        nstartpos++;
        ntype = PNT_INTER; /* international */
    } else if (strncmp(octstr_get_cstr(msg->sms.receiver), "00", 2) == 0) {
        debug("bb.smsc.at2", 0, "AT2[%s]: international starting with 00 (%s)",
              octstr_get_cstr(privdata->name), octstr_get_cstr(msg->sms.receiver));
        nstartpos += 2;
        ntype = PNT_INTER; /* international */
    }

    /* address length */
    pdu[pos] = at2_numtext(((len - nstartpos) & 240) >> 4);
    pos++;
    pdu[pos] = at2_numtext((len - nstartpos) & 15);
    pos++;

    /* Type of number */
    pdu[pos] = at2_numtext(8 + ntype);
    pos++;
    /* numbering plan: ISDN/Telephone numbering plan */
    pdu[pos] = at2_numtext(1);
    pos++;

    /* 
     * make sure there is no blank in the phone number and encode
     * an even number of digits 
     */
    octstr_strip_blanks(msg->sms.receiver);
    for (i = nstartpos; i < len; i += 2) {
        if (i + 1 < len) {
            pdu[pos] = octstr_get_char(msg->sms.receiver, i + 1);
        } else {
            pdu[pos] = at2_numtext (15);
        }
        pos++;
        pdu[pos] = octstr_get_char(msg->sms.receiver, i);
        pos++;
    }

    /* 
     * protocol identifier 
     * 0x00 implicit 
     */
    pdu[pos] = at2_numtext((msg->sms.pid & 240) >> 4);
    pos++;
    pdu[pos] = at2_numtext(msg->sms.pid & 15);
    pos++;

    /* data coding scheme */
    dcs = fields_to_dcs(msg,
                        (msg->sms.alt_dcs ? 2 - msg->sms.alt_dcs : privdata->alt_dcs));

    pdu[pos] = at2_numtext(dcs >> 4);
    pos++;
    pdu[pos] = at2_numtext(dcs % 16);
    pos++;

    /* 
     * Validity-Period (TP-VP)
     * see GSM 03.40 section 9.2.3.12
     * defaults to 24 hours = 167 if not set 
     */
    if ( msg->sms.validity) {
        if (msg->sms.validity > 635040)
            setvalidity = 255;
        if (msg->sms.validity >= 50400 && msg->sms.validity <= 635040)
            setvalidity = (msg->sms.validity - 1) / 7 / 24 / 60 + 192 + 1;
        if (msg->sms.validity > 43200 && msg->sms.validity < 50400)
            setvalidity = 197;
        if (msg->sms.validity >= 2880 && msg->sms.validity <= 43200)
            setvalidity = (msg->sms.validity - 1) / 24 / 60 + 166 + 1;
        if (msg->sms.validity > 1440 && msg->sms.validity < 2880)
            setvalidity = 168;
        if (msg->sms.validity >= 750 && msg->sms.validity <= 1440)
            setvalidity = (msg->sms.validity - 720 - 1) / 30 + 143 + 1;
        if (msg->sms.validity > 720 && msg->sms.validity < 750)
            setvalidity = 144;
        if (msg->sms.validity >= 5 && msg->sms.validity <= 720)
            setvalidity = (msg->sms.validity - 1) / 5 - 1 + 1;
        if (msg->sms.validity < 5)
            setvalidity = 0;
    } else
        setvalidity = (privdata->validityperiod != NULL ? 
            atoi(octstr_get_cstr(privdata->validityperiod)) : 167);

    if (setvalidity >= 0 && setvalidity <= 143)
        debug("bb.smsc.at2", 0, "AT2[%s]: TP-Validity-Period: %d minutes",
              octstr_get_cstr(privdata->name), (setvalidity + 1)*5);
    else if (setvalidity >= 144 && setvalidity <= 167)
        debug("bb.smsc.at2", 0, "AT2[%s]: TP-Validity-Period: %3.1f hours",
              octstr_get_cstr(privdata->name), ((float)(setvalidity - 143) / 2) + 12);
    else if (setvalidity >= 168 && setvalidity <= 196)
        debug("bb.smsc.at2", 0, "AT2[%s]: TP-Validity-Period: %d days",
              octstr_get_cstr(privdata->name), (setvalidity - 166));
    else
        debug("bb.smsc.at2", 0, "AT2[%s]: TP-Validity-Period: %d weeks",
              octstr_get_cstr(privdata->name), (setvalidity - 192));
    pdu[pos] = at2_numtext((setvalidity & 240) >> 4);
    pos++;
    pdu[pos] = at2_numtext(setvalidity & 15);
    pos++;

    /* user data length - include length of UDH if it exists */
    len = sms_msgdata_len(msg);

    if (octstr_len(msg->sms.udhdata)) {
        if (msg->sms.coding == DC_8BIT || msg->sms.coding == DC_UCS2) {
            len += octstr_len(msg->sms.udhdata);
        } else {
            /*
             * The reason we branch here is because UDH data length is determined
             * in septets if we are in GSM coding, otherwise it's in octets. Adding 6
             * will ensure that for an octet length of 0, we get septet length 0,
             * and for octet length 1 we get septet length 2. 
             */
            len += (((8 * octstr_len(msg->sms.udhdata)) + 6) / 7);
        }
    }

    pdu[pos] = at2_numtext((len & 240) >> 4);
    pos++;
    pdu[pos] = at2_numtext(len & 15);
    pos++;

    /* udh */
    if (octstr_len(msg->sms.udhdata)) {
        pos += at2_encode8bituncompressed(msg->sms.udhdata, &pdu[pos]);
    }

    /* 
     * user data 
     * if the data is too long, it is cut 
     */
    if (msg->sms.coding == DC_8BIT || msg->sms.coding == DC_UCS2) {
        pos += at2_encode8bituncompressed(msg->sms.msgdata, &pdu[pos]);
    } else {
        int offset = 0;

        if (octstr_len(msg->sms.udhdata)) { /* Have UDH */
            int nbits = octstr_len(msg->sms.udhdata) * 8; /* Includes UDH length byte */
            offset = (((nbits / 7) + 1) * 7 - nbits) % 7; /* Fill bits */
        }
        pos += at2_encode7bituncompressed(msg->sms.msgdata, &pdu[pos], offset);
    }
    pdu[pos] = 0;

    return 0;
}


int at2_encode7bituncompressed(Octstr *input, unsigned char *encoded, int offset)
{

    unsigned char prevoctet, tmpenc;
    int i;
    int c = 1;
    int r = 7;
    int pos = 0;
    int len;
    unsigned char enc7bit[256];
    int j, encpos = 0;
    int ermask[8] = { 0, 1, 3, 7, 15, 31, 63, 127 };
    int elmask[8] = { 0, 64, 96, 112, 120, 124, 126, 127 };

    charset_latin1_to_gsm(input);
    len = octstr_len(input);

    /* 
     * prevoctet is set to the first character and we'll start the loop
     * at the following char. 
     */
    prevoctet = octstr_get_char(input , 0);
    for (i = 1; i < octstr_len(input); i++) {
        /* 
         * a byte is encoded with what is left of the previous character
         * and filled with as much as possible of the current one. 
         */
        tmpenc = prevoctet + ((octstr_get_char(input, i) & ermask[c]) << r);
        enc7bit[encpos] = tmpenc;
        encpos++;
        c = (c > 6) ? 1 : c + 1;
        r = (r < 2) ? 7 : r - 1;

        /* 
         * prevoctet becomes the part of the current octet that hasn't
         * been copied to 'encoded' or the next char if the current has
         * been completely copied already. 
         */
        prevoctet = (octstr_get_char(input, i) & elmask[r]) >> (c - 1);
        if (r == 7) {
            i++;
            prevoctet = octstr_get_char(input, i);
        }
    }

    /* 
     * if the length of the message is a multiple of 8 then we
     * are finished. Otherwise prevoctet still contains part of a 
     * character so we add it. 
     */
    if ((len / 8)*8 != len) {
        enc7bit[encpos] = prevoctet;
        encpos++;
    }

    /* Now shift the buffer by the offset */
    if (offset > 0) {
        unsigned char nextdrop, lastdrop;

        nextdrop = lastdrop = 0;
        for (i = 0; i < encpos; i++) {
            nextdrop = enc7bit[i] >> (8 - offset); /* This drops off by shifting */
            if (i == 0)
                enc7bit[i] = enc7bit[i] << offset; /* This drops off by shifting */
            else
                enc7bit[i] = (enc7bit[i] << offset) | lastdrop;
            lastdrop = nextdrop;
        }

        if (offset > ((len*7) % 8)) {
            enc7bit [i] = nextdrop;
            i++;
        }
    } else
        i = encpos;

    for (j = 0; j < i; j++) {
        encoded[pos] = at2_numtext((enc7bit [j] & 240) >> 4);
        pos++;
        encoded[pos] = at2_numtext(enc7bit [j] & 15);
        pos++;
    }
    return pos;

}


int at2_encode8bituncompressed(Octstr *input, unsigned char *encoded)
{
    int len, i;

    len = octstr_len(input);

    for (i = 0; i < len; i++) {
        /* each character is encoded in its hex representation (2 chars) */
        encoded[i*2] = at2_numtext((octstr_get_char(input, i) & 240) >> 4);
        encoded[i*2 + 1] = at2_numtext(octstr_get_char(input, i) & 15);
    }
    return len*2;
}


int at2_numtext(int num)
{
    return (num > 9) ? (num + 55) : (num + 48);
}


int at2_detect_speed(PrivAT2data *privdata)
{
    int i;
#ifdef	B57600
    int autospeeds[] = { 57600, 38400, 19200, 9600 };
#else
    int autospeeds[] = { 38400, 19200, 9600 };
#endif

    debug("bb.smsc.at2", 0, "AT2[%s]: detecting modem speed. ", 
          octstr_get_cstr(privdata->name));

    for (i = 0; i < (sizeof(autospeeds) / sizeof(int)); i++) {
	if(at2_test_speed(privdata, autospeeds[i]) == 0) {
	    privdata->speed = autospeeds[i];
	    break;
	}
    }
    if (privdata->speed == 0) {
        info(0, "AT2[%s]: cannot detect speed", octstr_get_cstr(privdata->name));
        return -1;
    }
    info(0, "AT2[%s]: detect speed is %ld", octstr_get_cstr(privdata->name), privdata->speed);
    return 0;
}

int at2_test_speed(PrivAT2data *privdata, long speed) {

    int res;

    if (at2_open_device1(privdata) == -1)
	return -1;

    at2_set_speed(privdata, speed);
    /* send a return so the modem can detect the speed */
    res = at2_send_modem_command(privdata, "", 1, 0); 
	    
    res = at2_send_modem_command(privdata, "AT", 0, 0);

    if (res != 0)
	{
		res = at2_send_modem_command(privdata, "AT", 0, 0);
	}
    if (res != 0)
	{
		res = at2_send_modem_command(privdata, "AT", 0, 0);
	}
    at2_close_device(privdata);

    return res;
}


int at2_detect_modem_type(PrivAT2data *privdata)
{
    int res;
    ModemDef *modem;
    int i;

    debug("bb.smsc.at2", 0, "AT2[%s]: detecting modem type", octstr_get_cstr(privdata->name));

    if (at2_open_device1(privdata) == -1)
        return -1;

    at2_set_speed(privdata, privdata->speed);
    /* send a return so the modem can detect the speed */
    res = at2_send_modem_command(privdata, "", 1, 0); 
    res = at2_send_modem_command(privdata, "AT", 0, 0);

    if (at2_send_modem_command(privdata, "AT&F", 0, 0) == -1)
        return -1;
    if (at2_send_modem_command(privdata, "ATE0", 0, 0) == -1)
        return -1;

    at2_flush_buffer(privdata);

    if (at2_send_modem_command(privdata, "ATI", 0, 0) == -1)
        return -1;

    /* we try to detect the modem automatically */
    i = 1;
    while ((modem = at2_read_modems(privdata, privdata->configfile, NULL, i++)) != NULL) {

        if (octstr_len(modem->detect_string) == 0) {
            at2_destroy_modem(modem);
            continue;
        }

        /* 
        debug("bb.smsc.at2",0,"AT2[%s]: searching for %s", octstr_get_cstr(privdata->name), 
              octstr_get_cstr(modem->name)); 
        */

        if (octstr_search(privdata->lines, modem->detect_string, 0) != -1) {
            if (octstr_len(modem->detect_string2) == 0) {
                debug("bb.smsc.at2", 0, "AT2[%s]: found string <%s>, using modem definition <%s>", 
                      octstr_get_cstr(privdata->name), octstr_get_cstr(modem->detect_string), 
                      octstr_get_cstr(modem->name));
                privdata->modem = modem;
                break;
            } else {
                if (octstr_search(privdata->lines, modem->detect_string2, 0) != -1) {
                    debug("bb.smsc.at2", 0, "AT2[%s]: found string <%s> plus <%s>, using modem "
                          "definition <%s>", octstr_get_cstr(privdata->name), 
                          octstr_get_cstr(modem->detect_string), 
                          octstr_get_cstr(modem->detect_string2), 
                          octstr_get_cstr(modem->name));
                    privdata->modem = modem;
                    break;
                }
            }
        }
    }

    if (privdata->modem == NULL) {
        debug("bb.smsc.at2", 0, "AT2[%s]: Cannot detect modem, using generic", 
              octstr_get_cstr(privdata->name));
        if ((modem = at2_read_modems(privdata, privdata->configfile, octstr_imm("generic"), 0)) == NULL) {
            panic(0, "AT2[%s]: Cannot detect modem and generic not found", 
                  octstr_get_cstr(privdata->name));
        } else {
            privdata->modem = modem;
        }
    }

    /* lets see if it supports GSM SMS 2+ mode */
    res = at2_send_modem_command(privdata, "AT+CSMS=?", 0, 0);
    if (res != 0)
        /* if it doesnt even understand the command, I'm sure it won't support it */
        privdata->phase2plus = 0; 
    else {
        /* we have to take a part a string like +CSMS: (0,1,128) */
        Octstr *ts;
        int i;
        List *vals;

        ts = privdata->lines;
        privdata->lines = NULL;

        i = octstr_search_char(ts, '(', 0);
        if (i > 0) {
            octstr_delete(ts, 0, i + 1);
        }
        i = octstr_search_char(ts, ')', 0);
        if (i > 0) {
            octstr_truncate(ts, i);
        }
        vals = octstr_split(ts, octstr_imm(","));
        octstr_destroy(ts);
        ts = list_search(vals, octstr_imm("1"), (void*) octstr_item_match);
        if (ts)
            privdata->phase2plus = 1;
        list_destroy(vals, (void*) at2_octstr_destroy);
    }
    if (privdata->phase2plus)
        info(0, "AT2[%s]: Phase 2+ is supported", octstr_get_cstr(privdata->name));
    at2_close_device(privdata);
    return 0;
}


ModemDef *at2_read_modems(PrivAT2data *privdata, Octstr *file, Octstr *id, int idnumber)
{

    Cfg *cfg;
    List *grplist;
    CfgGroup *grp;
    Octstr *p;
    ModemDef *modem;
    int i = 1;

    /* 
     * Use id and idnumber=0 or id=NULL and idnumber > 0 
     */
    if (octstr_len(id) == 0 && idnumber == 0)
        return NULL;

    if (idnumber == 0)
        debug("bb.smsc.at2", 0, "AT2[%s]: Reading modem definitions from <%s>", 
              octstr_get_cstr(privdata->name), octstr_get_cstr(file));
    cfg = cfg_create(file);

    if (cfg_read(cfg) == -1)
        panic(0, "Cannot read modem definition file");

    grplist = cfg_get_multi_group(cfg, octstr_imm("modems"));
    if (idnumber == 0)
        debug("bb.smsc.at2", 0, "AT2[%s]: Found <%ld> modems in config", 
              octstr_get_cstr(privdata->name), list_len(grplist));

    if (grplist == NULL)
        panic(0, "Where are the modem definitions ?!?!");

    grp = NULL;
    while (grplist && (grp = list_extract_first(grplist)) != NULL) {
        p = cfg_get(grp, octstr_imm("id"));
        if (p == NULL) {
            info(0, "Modems group without id, bad");
            continue;
        }
        /* Check by id */
        if (octstr_len(id) != 0 && octstr_compare(p, id) == 0) {
            O_DESTROY(p);
            break;
        }
        /* Check by idnumber */
        if (octstr_len(id) == 0 && idnumber == i) {
            O_DESTROY(p);
            break;
        }
        O_DESTROY(p);
        i++;
        grp = NULL;
    }
    if (grplist != NULL)
        list_destroy(grplist, NULL);

    if (grp != NULL) {
        modem = gw_malloc(sizeof(ModemDef));

        modem->id = cfg_get(grp, octstr_imm("id"));

        modem->name = cfg_get(grp, octstr_imm("name"));
        if (modem->name == NULL)
            modem->name = octstr_duplicate(modem->id);

        modem->detect_string = cfg_get(grp, octstr_imm("detect-string"));
        modem->detect_string2 = cfg_get(grp, octstr_imm("detect-string2"));

        modem->init_string = cfg_get(grp, octstr_imm("init-string"));
        if (modem->init_string == NULL)
            modem->init_string = octstr_create("AT+CNMI=1,2,0,0,0");

        modem->speed = 9600;
        cfg_get_integer(&modem->speed, grp, octstr_imm("speed"));

        cfg_get_bool(&modem->need_sleep, grp, octstr_imm("need-sleep"));

        modem->enable_hwhs = cfg_get(grp, octstr_imm("enable-hwhs"));
        if (modem->enable_hwhs == NULL)
            modem->enable_hwhs = octstr_create("AT+IFC=2,2");

        cfg_get_bool(&modem->no_pin, grp, octstr_imm("no-pin"));

        cfg_get_bool(&modem->no_smsc, grp, octstr_imm("no-smsc"));

        modem->sendline_sleep = 100;
        cfg_get_integer(&modem->sendline_sleep, grp, octstr_imm("sendline-sleep"));

        modem->keepalive_cmd = cfg_get(grp, octstr_imm("keepalive-cmd"));
        if (modem->keepalive_cmd == NULL)
            modem->keepalive_cmd = octstr_create("AT");

        modem->message_storage = cfg_get(grp, octstr_imm("message-storage"));

        cfg_get_bool(&modem->enable_mms, grp, octstr_imm("enable-mms"));

        /*	
        if (modem->message_storage == NULL)
            modem->message_storage = octstr_create("SM");
        */

        cfg_get_bool(&modem->broken, grp, octstr_imm("broken"));

        cfg_destroy(cfg);
        return modem;

    } else {
        cfg_destroy(cfg);
        return NULL;
    }
}


void at2_destroy_modem(ModemDef *modem)
{
    if (modem != NULL) {
        O_DESTROY(modem->id);
        O_DESTROY(modem->name);
        O_DESTROY(modem->detect_string);
        O_DESTROY(modem->detect_string2);
        O_DESTROY(modem->init_string);
        O_DESTROY(modem->enable_hwhs);
        O_DESTROY(modem->keepalive_cmd);
        O_DESTROY(modem->message_storage);
        gw_free(modem);
    }
}


int swap_nibbles(char byte)
{
    return ( ( byte & 15 ) * 10 ) + ( byte >> 4 );
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.