Main Page | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

smsc_smasi.c

Go to the documentation of this file.
00001 /* ==================================================================== 
00002  * The Kannel Software License, Version 1.0 
00003  * 
00004  * Copyright (c) 2001-2008 Kannel Group  
00005  * Copyright (c) 1998-2001 WapIT Ltd.   
00006  * All rights reserved. 
00007  * 
00008  * Redistribution and use in source and binary forms, with or without 
00009  * modification, are permitted provided that the following conditions 
00010  * are met: 
00011  * 
00012  * 1. Redistributions of source code must retain the above copyright 
00013  *    notice, this list of conditions and the following disclaimer. 
00014  * 
00015  * 2. Redistributions in binary form must reproduce the above copyright 
00016  *    notice, this list of conditions and the following disclaimer in 
00017  *    the documentation and/or other materials provided with the 
00018  *    distribution. 
00019  * 
00020  * 3. The end-user documentation included with the redistribution, 
00021  *    if any, must include the following acknowledgment: 
00022  *       "This product includes software developed by the 
00023  *        Kannel Group (http://www.kannel.org/)." 
00024  *    Alternately, this acknowledgment may appear in the software itself, 
00025  *    if and wherever such third-party acknowledgments normally appear. 
00026  * 
00027  * 4. The names "Kannel" and "Kannel Group" must not be used to 
00028  *    endorse or promote products derived from this software without 
00029  *    prior written permission. For written permission, please  
00030  *    contact org@kannel.org. 
00031  * 
00032  * 5. Products derived from this software may not be called "Kannel", 
00033  *    nor may "Kannel" appear in their name, without prior written 
00034  *    permission of the Kannel Group. 
00035  * 
00036  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
00037  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
00038  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
00039  * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
00040  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
00041  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
00042  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
00043  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
00044  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
00045  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
00046  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
00047  * ==================================================================== 
00048  * 
00049  * This software consists of voluntary contributions made by many 
00050  * individuals on behalf of the Kannel Group.  For more information on  
00051  * the Kannel Group, please see <http://www.kannel.org/>. 
00052  * 
00053  * Portions of this software are based upon software originally written at  
00054  * WapIT Ltd., Helsinki, Finland for the Kannel project.  
00055  */ 
00056 
00057 /*
00058  * Implementation of a SM/ASI SMSC module.
00059  *
00060  * Stipe Tolj <stolj@wapme.de>
00061  *
00062  * This module connects to a CriticalPath InVoke SMS Center which
00063  * uses the SM/ASI protocol. 
00064  * The module is heavily based on the SMPP module design.
00065  *
00066  * TODO:
00067  * 1. alt_dcs is not used. Instead, msg->sms.mclass is used as the SMASI
00068  *    Class.
00069  * 2. Numbers are not handled correctly, I guess. SMASI allows only(?)
00070  *    international numbers without leading double zero. How to ensure
00071  *    this?
00072  * 3. Handling of npi and ton correct?
00073  * 4. SubmitMulti PDUs not supported.
00074  * 5. Replace PDUs not supported.
00075  * 6. Status PDUs not supported.
00076  * 7. Cancel PDUs not supported.
00077  * 8. UserRes PDUs not supported.
00078  * 9. Smsc PDUs not supported.
00079  * 10. EnquireLink PDUs not supported.
00080  */
00081 
00082 #include "gwlib/gwlib.h"
00083 #include "msg.h"
00084 #include "smsc_p.h"
00085 #include "smasi_pdu.h"
00086 #include "smscconn_p.h"
00087 #include "bb_smscconn_cb.h"
00088 #include "sms.h"
00089 #include "dlr.h"
00090 
00091 #define DEBUG 1
00092 
00093 #ifndef DEBUG
00094 static void dump_pdu(const char *msg, Octstr *id, SMASI_PDU *pdu) { }
00095 #else
00096 static void dump_pdu(const char *msg, Octstr *id, SMASI_PDU *pdu) 
00097 {
00098     debug("bb.sms.smasi", 0, "SMASI[%s]: %s", octstr_get_cstr(id), msg);
00099     smasi_pdu_dump(pdu);
00100 }
00101 #endif
00102 
00103 
00104 /************************************************************************/
00105 /* DEFAULT SETTINGS                                                     */
00106 /************************************************************************/
00107 
00108 #define SMASI_DEFAULT_PORT          21500
00109 #define SMASI_DEFAULT_PRIORITY      0
00110 #define MAX_PENDING_SUBMITS         10
00111 #define SMASI_THROTTLING_SLEEP_TIME 15
00112 #define SMASI_ENQUIRE_LINK_INTERVAL  30.0 
00113 
00114 
00115 /************************************************************************/
00116 /* OVERRIDE SETTINGS                                                    */
00117 /************************************************************************/
00118 
00119 /* Set these to -1 if no override desired. Values carried in message will
00120  * be used then. Or the defaults - if message has no values.
00121  * 
00122  * Otherwise these values will be forced!
00123  */
00124 
00125 #define SMASI_OVERRIDE_SOURCE_TON    1
00126 #define SMASI_OVERRIDE_SOURCE_NPI    -1
00127 #define SMASI_OVERRIDE_DEST_TON      -1
00128 #define SMASI_OVERRIDE_DEST_NPI      -1
00129 
00130 
00131 /************************************************************************/
00132 /* SMASI STRUCTURE AND RELATED FUNCTIONS                                */
00133 /************************************************************************/
00134 
00135 typedef struct {
00136     SMSCConn * conn;                 /* connection to the bearerbox */
00137     int thread_handle;               /* handle for the SMASI thread */
00138     List *msgs_to_send;
00139     Dict *sent_msgs;                 /* hash table for send, but yet not confirmed */
00140     List *received_msgs;             /* list of received, but yet not processed */
00141     Counter *message_id_counter;     /* sequence number */
00142     Octstr *host;                    /* host or IP of the SMASI server */
00143     long port;                       /* port to connect to */
00144     Octstr *username;     
00145     Octstr * password;
00146     Octstr * my_number;
00147     long source_addr_ton;
00148     long source_addr_npi;
00149     long dest_addr_ton;
00150     long dest_addr_npi;
00151     long priority;
00152     time_t throttling_err_time;
00153     int quitting;
00154     long enquire_link_interval;
00155     int logged_off;
00156 } SMASI;
00157 
00158 
00159 static SMASI *smasi_create(SMSCConn *conn) 
00160 {
00161 
00162     SMASI *smasi = gw_malloc(sizeof(SMASI));
00163 
00164     smasi->conn = conn;
00165 
00166     smasi->thread_handle = -1;
00167     smasi->msgs_to_send = gwlist_create();
00168     smasi->sent_msgs = dict_create(16, NULL);
00169     smasi->received_msgs = gwlist_create();
00170     smasi->message_id_counter = counter_create();
00171     smasi->host = NULL;
00172     smasi->username = NULL;
00173     smasi->password = NULL;
00174     smasi->source_addr_ton = -1;
00175     smasi->source_addr_npi = -1;
00176     smasi->dest_addr_ton = -1;
00177     smasi->dest_addr_npi = -1;
00178     smasi->my_number = NULL;
00179     smasi->port = 21500;
00180     smasi->quitting = 0;
00181     smasi->logged_off = 0;
00182     smasi->priority = 0;
00183     smasi->throttling_err_time = 0;
00184     smasi->enquire_link_interval = 30;
00185 
00186     gwlist_add_producer(smasi->msgs_to_send);
00187 
00188     return smasi;
00189 } 
00190 
00191 
00192 static void smasi_destroy(SMASI *smasi) 
00193 {
00194     if (smasi == NULL) return;
00195 
00196     gwlist_destroy(smasi->msgs_to_send, msg_destroy_item);
00197     dict_destroy(smasi->sent_msgs);
00198     gwlist_destroy(smasi->received_msgs, msg_destroy_item);
00199     counter_destroy(smasi->message_id_counter);
00200     octstr_destroy(smasi->host);
00201     octstr_destroy(smasi->username);
00202     octstr_destroy(smasi->password);
00203     gw_free(smasi);
00204 } 
00205 
00206 
00207 
00208 /************************************************************************/
00209 /* DATA ENCODING                                                        */
00210 /************************************************************************/
00211 
00212 /* These values will be initialized on module startup. They contain the
00213  * ASCII representation of the chars that need to be escaped in the message
00214  * body before transmission. Example: "," (comma) will be represented by
00215  * the octet string ":2c".
00216  */
00217 
00218 static Octstr *colon = NULL;
00219 static Octstr *assign = NULL;
00220 static Octstr *comma = NULL;
00221 static Octstr *cr = NULL;
00222 static Octstr *lf = NULL;
00223 
00224 
00225 /*
00226  * Escapes outgoing message body data by replacing occurrences of "special"
00227  * chars inside the octet string.
00228  */
00229 static void escape_data(Octstr *data) 
00230 {
00231     long pos = 0;
00232 
00233     /* This one uses a different approach than the encode and decode
00234      * functions. Because it is assumed, that only a fraction of the
00235      * contained chars have to be escaped.
00236      */
00237     while (pos < octstr_len(data)) {
00238         Octstr * escaped = NULL;
00239         int check = octstr_get_char(data, pos);
00240 
00241         if (check == ':') escaped = colon;
00242         else if (check == '=') escaped = assign;
00243         else if (check == ',') escaped = comma;
00244         else if (check == '\n') escaped = cr;
00245         else if (check == '\r') escaped = lf;
00246 
00247         if (escaped != NULL) {
00248             /* If the current char has to be escaped, delete the char from
00249              * the source string, replace it with the escape sequence, and
00250              * advance position until after the inserted sequence.
00251              */
00252             octstr_delete(data, pos, 1);
00253             octstr_insert(data, escaped, pos);
00254             pos += octstr_len(escaped);
00255         } else {
00256             /* If not escaped, simply skip the current char. */
00257             pos++;
00258         } 
00259     } 
00260 } 
00261 
00262 
00263 /*
00264  * Unescapes incoming message body data by replacing occurrences of escaped
00265  * chars with their original character representation.
00266  */
00267 static void unescape_data(Octstr *data) 
00268 {
00269     long pos = 0;
00270 
00271     /* Again, an inplace transformation is used. Because, again, it is
00272      * assumed that only a fraction of chars has to be unescaped.
00273      */
00274     while (pos < octstr_len(data)) {
00275         int check = octstr_get_char(data, pos);
00276 
00277         if (check == ':') {
00278             char byte = 0;
00279             int msb = octstr_get_char(data, pos + 1);
00280             int lsb = octstr_get_char(data, pos + 2);
00281  
00282             if (msb == '0') msb = 0;
00283             else if (msb >= '1' && msb <= '9') msb -= '1' + 1;
00284             else msb -= 'a' + 10;
00285 
00286             if (lsb == '0') lsb = 0;
00287             else if (lsb >= '1' && lsb <= '9') lsb -= '1' + 1;
00288             else lsb -= 'a' + 10;
00289 
00290             byte = msb << 4 | lsb;
00291 
00292             /* Do inplace unescaping. */
00293             octstr_delete(data, pos, 3);
00294             octstr_insert_data(data, pos, &byte, 1);
00295         } 
00296         pos++;
00297     } 
00298 }
00299 
00300 
00301 /*
00302  * Will replace a binary data octet string (inplace) with a SMASI conform
00303  * ASCII representation of the data.
00304  */
00305 static void encode_binary_data(Octstr *data) 
00306 {
00307     Octstr *result = octstr_create("");
00308     long pos = 0;
00309 
00310     while (pos < octstr_len(data)) {
00311         int encode = octstr_get_char(data, pos);
00312         int msb = (encode & 0xf0) >> 4;
00313         int lsb = (encode & 0x0f) >> 0;
00314 
00315         if (msb == 0) msb = '0';
00316         else if (msb < 10) msb = '1' + msb - 1;
00317         else msb = 'a' + msb - 10;
00318 
00319         if (lsb == 0) lsb = '0';
00320         else if (lsb < 10) lsb = '1' + lsb - 1;
00321         else lsb = 'a' + lsb - 10;
00322 
00323         octstr_append_char(result, ':');
00324         octstr_append_char(result, msb);
00325         octstr_append_char(result, lsb);
00326 
00327         pos++;
00328     } 
00329     /* Replace binary data octet string with ASCII representation. */
00330     octstr_delete(data, 0, octstr_len(data));
00331     octstr_append(data, result);
00332     octstr_destroy(result);
00333 }
00334 
00335 
00336 /*
00337  * Re-escape SMASI ASCII representation of binary data with the
00338  * original binary data octet string.
00339  * XXX this may be done by the internal parser routines too.
00340  */
00341 static void decode_binary_data(Octstr *data) 
00342 {
00343     long pos = 0;
00344 
00345     while (pos < octstr_len(data)) {
00346         int check = octstr_get_char(data, pos);
00347 
00348         if (check == ':') {
00349             Octstr *byte;
00350             int msb = octstr_get_char(data, pos + 1);
00351             int lsb = octstr_get_char(data, pos + 2);
00352 
00353             if (msb != -1 && lsb != -1) {
00354                 byte = octstr_create("");
00355                 octstr_append_char(byte, msb);
00356                 octstr_append_char(byte, lsb);
00357 
00358                 if (octstr_hex_to_binary(byte) != -1) {
00359                     /* Do inplace unescaping. */
00360                     octstr_delete(data, pos, 3);
00361                     octstr_insert(data, byte, pos);
00362                 } else {
00363                     error(0, "Malformed binary encoded data.");
00364                 }
00365 
00366                 octstr_destroy(byte);
00367             }
00368         } 
00369         pos++;
00370     } 
00371 }
00372 
00373 
00374 /************************************************************************/
00375 /* MESSAGE PROCESSING                                                   */
00376 /************************************************************************/
00377 
00378 static Octstr *get_ton_npi_value(int override, int message) 
00379 {
00380     if (override != -1) {
00381         debug("bb.sms.smasi", 0, "SMASI: Manually forced ton or npi to `%d'", 
00382               override);
00383         return (octstr_format("%ld", override));
00384     } else {
00385         return (octstr_format("%ld", message));
00386     }
00387 }
00388 
00389 
00390 /*
00391  * Gets the value to be used as source_addr_ton. Will use override values
00392  * if configured. Will use values from message otherwise. Or fall back to
00393  * defaults if nothing given.
00394  */
00395 static Octstr *get_source_addr_ton(SMASI *smasi, Msg *msg) 
00396 {
00397     return get_ton_npi_value(smasi->source_addr_ton, 
00398                              GSM_ADDR_TON_INTERNATIONAL);
00399 }
00400 
00401 
00402 /*
00403  * Gets the value to be used as source_addr_npi. Will use override values
00404  * if configured. Will use values from message otherwise. Or fall back to
00405  * defaults if nothing given.
00406  */
00407 static Octstr *get_source_addr_npi(SMASI *smasi, Msg *msg) 
00408 {
00409     return get_ton_npi_value(smasi->source_addr_npi, 
00410                              GSM_ADDR_NPI_E164);
00411 }
00412 
00413 
00414 /*
00415  * Gets the value to be used as dest_addr_ton. Will use override values
00416  * if configured. Will use values from message otherwise. Or fall back to
00417  * defaults if nothing given.
00418  */
00419 static Octstr *get_dest_addr_ton(SMASI *smasi, Msg *msg) 
00420 {
00421     return get_ton_npi_value(smasi->dest_addr_ton, 
00422                              GSM_ADDR_TON_INTERNATIONAL);
00423 }
00424 
00425 
00426 /*
00427  * Gets the value to be used as dest_addr_npi. Will use override values
00428  * if configured. Will use values from message otherwise. Or fall back to
00429  * defaults if nothing given.
00430  */
00431 static Octstr *get_dest_addr_npi(SMASI *smasi, Msg *msg) 
00432 {
00433     return get_ton_npi_value(smasi->dest_addr_npi, 
00434                              GSM_ADDR_NPI_E164);
00435 }
00436 
00437 
00438 /*
00439  * Determine the originator (sender number) type based on the number. Will
00440  * change the originator number if necessary.
00441  */
00442 static Octstr *get_originator_type(SMASI *smasi, Octstr *originator) 
00443 {
00444     /* International or alphanumeric sender? */
00445     if (octstr_get_char(originator, 0) == '+') {
00446         if (!octstr_check_range(originator, 1, 256, gw_isdigit)) {
00447             return octstr_format("%ld", GSM_ADDR_TON_ALPHANUMERIC);
00448         } else {
00449            /* Numeric sender address with + in front: The + has to be
00450             * removed from this international number.
00451             */
00452            octstr_delete(originator, 0, 1);
00453            return octstr_format("%ld", GSM_ADDR_TON_INTERNATIONAL);
00454         }
00455     } else if (!octstr_check_range(originator, 0, 256, gw_isdigit)) {
00456        return octstr_format("%ld", GSM_ADDR_TON_ALPHANUMERIC);
00457     }
00458 
00459     /* Return the default value. */
00460     return octstr_format("%ld", GSM_ADDR_TON_INTERNATIONAL);
00461 }
00462 
00463 
00464 /*
00465  * Creates a SubmitReq PDU from an outgoing message.
00466  */
00467 static SMASI_PDU *msg_to_pdu(SMASI *smasi, Msg *msg) 
00468 {
00469     SMASI_PDU *pdu = smasi_pdu_create(SubmitReq);
00470 
00471     pdu->u.SubmitReq.Destination = octstr_duplicate(msg->sms.receiver);
00472     pdu->u.SubmitReq.Body = octstr_duplicate(msg->sms.msgdata);
00473     pdu->u.SubmitReq.Originator = octstr_duplicate(msg->sms.sender);
00474 
00475     pdu->u.SubmitReq.OriginatorType = 
00476         get_originator_type(smasi, pdu->u.SubmitReq.Originator);
00477 
00478     pdu->u.SubmitReq.Sequence = 
00479         octstr_format("%ld", counter_increase(smasi->message_id_counter));
00480 
00481 
00482     /* If its a international number starting with +, lets remove the +. */
00483     if (octstr_get_char(pdu->u.SubmitReq.Destination, 0) == '+')
00484         octstr_delete(pdu->u.SubmitReq.Destination, 0, 1);
00485 
00486     /* Do ton and npi override - if configured. Use values from message
00487      * otherwise.
00488      */
00489     pdu->u.SubmitReq.OriginatorType = get_source_addr_ton(smasi, msg);
00490     pdu->u.SubmitReq.OriginatorPlan = get_source_addr_npi(smasi, msg);
00491     pdu->u.SubmitReq.DestinationType = get_dest_addr_ton(smasi, msg);
00492     pdu->u.SubmitReq.DestinationPlan = get_dest_addr_npi(smasi, msg);
00493        
00494     /* Set priority. */
00495     if (smasi->priority >= 0 && smasi->priority <= 3) {
00496         pdu->u.SubmitReq.MqPriority = octstr_format("%ld", smasi->priority);
00497     } else {
00498         pdu->u.SubmitReq.MqPriority = octstr_format("%ld", 0);
00499     }
00500 
00501     /* Set encoding. */
00502     if (msg->sms.coding != DC_UNDEF) {
00503         if (msg->sms.coding == DC_7BIT)
00504             pdu->u.SubmitReq.MsEncoding = octstr_create("7bit");
00505         else if (msg->sms.coding == DC_8BIT)
00506             pdu->u.SubmitReq.MsEncoding = octstr_create("8bit");
00507         else if (msg->sms.coding == DC_UCS2)
00508             pdu->u.SubmitReq.MsEncoding = octstr_create("16bit");
00509 
00510         /* Everything else will default to 7bit. */
00511     }
00512 
00513     /* Set messaging class - if within defined parameter range. */
00514     if (msg->sms.mclass != MC_UNDEF)
00515         pdu->u.SubmitReq.Class = octstr_format("%ld", msg->sms.mclass);
00516 
00517     /* Set Protocol ID. */
00518     pdu->u.SubmitReq.ProtocolID = octstr_format("%ld", 
00519     (msg->sms.pid == SMS_PARAM_UNDEFINED ? 0 : msg->sms.pid));
00520 
00521     /* Check if SMS is binary. */
00522     if (msg->sms.udhdata && octstr_len(msg->sms.udhdata) > 0) {
00523         
00524         pdu->u.SubmitReq.UserDataHeader =
00525           octstr_duplicate(msg->sms.udhdata);
00526 
00527         pdu->u.SubmitReq.BodyEncoding =
00528           octstr_create("Data");
00529 
00530         if (pdu->u.SubmitReq.MsEncoding)
00531           octstr_destroy(pdu->u.SubmitReq.MsEncoding);
00532 
00533         pdu->u.SubmitReq.MsEncoding =
00534           octstr_create("transparent");
00535 
00536         /* Encode data. */
00537         encode_binary_data(pdu->u.SubmitReq.UserDataHeader);
00538         encode_binary_data(pdu->u.SubmitReq.Body);
00539     } else {
00540 
00541         /* Otherwise do data escaping. */
00542         escape_data(pdu->u.SubmitReq.Body);
00543     } 
00544 
00545     return pdu;
00546 } 
00547 
00548 
00549 /*
00550  * Create a message structure from an incoming DeliverReq PDU.
00551  */
00552 static Msg *pdu_to_msg(SMASI_PDU *pdu) 
00553 {
00554     Msg *msg = NULL;
00555 
00556     gw_assert(pdu->type == DeliverReq);
00557     gw_assert(pdu->u.DeliverReq.Originator);
00558     gw_assert(pdu->u.DeliverReq.Destination);
00559     gw_assert(pdu->u.DeliverReq.Body);
00560 
00561     msg = msg_create(sms);
00562 
00563     msg->sms.sender = octstr_duplicate(pdu->u.DeliverReq.Originator);
00564     msg->sms.receiver = octstr_duplicate(pdu->u.DeliverReq.Destination);
00565     msg->sms.msgdata = octstr_duplicate(pdu->u.DeliverReq.Body);
00566  
00567     /* Read priority. */
00568     if (pdu->u.DeliverReq.ProtocolId)
00569         if (octstr_parse_long(&msg->sms.pid, 
00570                               pdu->u.DeliverReq.ProtocolId, 0, 10) == -1)
00571             msg->sms.pid = SMS_PARAM_UNDEFINED;
00572 
00573     /* Read Coding. */
00574     if (pdu->u.DeliverReq.MsEncoding) {
00575 
00576         /* Use specified coding. */
00577         if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "7bit") == 0)
00578             msg->sms.coding = DC_7BIT;
00579         else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "8bit") == 0)
00580             msg->sms.coding = DC_8BIT;
00581         else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "UCS2") == 0)
00582             msg->sms.coding = DC_UCS2;
00583         else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "transparent") == 0)
00584             msg->sms.coding = DC_8BIT;
00585     } else {
00586 
00587         /* Determine specified coding according to udhdata presence. */
00588         if (pdu->u.DeliverReq.UserDataHeader)
00589             msg->sms.coding = DC_8BIT;
00590         else
00591             msg->sms.coding = DC_7BIT;
00592     }
00593 
00594     /* Unescape (non-binary) or decode (binary) data. */
00595     if (msg->sms.coding == DC_8BIT) {
00596 
00597         decode_binary_data(msg->sms.msgdata);
00598         if (pdu->u.DeliverReq.UserDataHeader &&
00599             octstr_len(pdu->u.DeliverReq.UserDataHeader) > 0) {
00600             msg->sms.udhdata = octstr_duplicate(pdu->u.DeliverReq.UserDataHeader);
00601             decode_binary_data(msg->sms.udhdata);
00602         }
00603 
00604     } else {
00605         unescape_data(msg->sms.msgdata);
00606     } 
00607 
00608     /* Read message class. */
00609     if (pdu->u.DeliverReq.Class &&
00610         octstr_parse_long(&msg->sms.mclass, 
00611                           pdu->u.DeliverReq.Class, 0, 10) == -1)
00612             msg->sms.mclass = MC_UNDEF;    /* Set to unspecified. */
00613 
00614     /* Read protocol ID. */
00615     if (pdu->u.DeliverReq.ProtocolId &&
00616         octstr_parse_long(&msg->sms.pid, 
00617                           pdu->u.DeliverReq.ProtocolId, 0, 10) == -1)
00618             msg->sms.pid = SMS_PARAM_UNDEFINED;
00619 
00620     return msg;
00621 }
00622  
00623 
00624 /************************************************************************/
00625 /* PDU HANDLING                                                         */
00626 /************************************************************************/
00627 
00628 static void send_logoff(SMASI *smasi, Connection *conn) 
00629 {
00630     SMASI_PDU *pdu = NULL;
00631     Octstr *os = NULL;
00632 
00633     counter_increase(smasi->message_id_counter);
00634 
00635     pdu = smasi_pdu_create(LogoffReq);
00636     pdu->u.LogoffReq.Reason = octstr_create("Client shutting down");
00637     dump_pdu("Sending !LogoffReq:", smasi->conn->id, pdu); 
00638 
00639     os = smasi_pdu_pack(pdu);
00640     conn_write(conn, os);
00641     octstr_destroy(os);
00642     smasi_pdu_destroy(pdu);
00643 }
00644 
00645  
00646 static void send_enquire_link(SMASI *smasi, Connection *conn, long *last_sent) 
00647 { 
00648     SMASI_PDU *pdu = NULL; 
00649     Octstr *os = NULL; 
00650  
00651     if (date_universal_now() - *last_sent < smasi->enquire_link_interval)
00652         return; 
00653     *last_sent = date_universal_now(); 
00654  
00655     pdu = smasi_pdu_create(EnquireLinkReq);
00656     dump_pdu("Sending EnquireLinkReq:", smasi->conn->id, pdu); 
00657     os = smasi_pdu_pack(pdu); 
00658     if (os)
00659        conn_write(conn, os); /* Write errors checked by caller. */ 
00660     octstr_destroy(os); 
00661     smasi_pdu_destroy(pdu); 
00662 } 
00663 
00664 
00665 static int send_pdu(Connection *conn, Octstr *id, SMASI_PDU *pdu)
00666 {
00667     Octstr * os = NULL;
00668     int ret = 0;
00669 
00670     dump_pdu("Sending PDU:", id, pdu); 
00671     os = smasi_pdu_pack(pdu);
00672     if (os) ret = conn_write(conn, os);
00673     else ret = -1;
00674 
00675     octstr_destroy(os);
00676     return ret;
00677 }
00678  
00679 
00680 /*
00681  * Try to read a SMASI PDU from a connection. Return -1 for error (caller
00682  * should close the connection), 0 for no PDU ready yet, or 1 for PDU read
00683  * and unpacked. Return a pointer to the PDU in `*pdu'.
00684  */
00685 static int read_pdu(SMASI *smasi, Connection *conn, SMASI_PDU **pdu) 
00686 {
00687     Octstr *os;
00688     
00689     os = smasi_pdu_read(conn);
00690     if (os == NULL) {
00691         if (conn_eof(conn) || conn_error(conn)) 
00692             return -1;
00693         return 0;
00694     }
00695 
00696     *pdu = smasi_pdu_unpack(os);
00697     if (*pdu == NULL) {
00698         error(0, "SMASI[%s]: PDU unpacking failed.",
00699               octstr_get_cstr(smasi->conn->id));
00700         debug("bb.sms.smasi", 0, "SMASI[%s]: Failed PDU follows.",
00701               octstr_get_cstr(smasi->conn->id));
00702         octstr_dump(os, 0);
00703         octstr_destroy(os);
00704         return -1;
00705     }
00706     octstr_destroy(os);
00707     return 1;
00708 }
00709 
00710 
00711 static void handle_pdu(SMASI *smasi, Connection *conn, 
00712                        SMASI_PDU *pdu, long *pending_submits) 
00713 {
00714     SMASI_PDU *resp = NULL;
00715     Msg *msg = NULL;
00716     long reason;
00717 
00718     switch (pdu->type) {
00719 
00720         case DeliverReq:
00721             msg = pdu_to_msg(pdu);
00722 
00723             msg_dump(msg, 0);
00724 
00725             if (smasi->my_number && octstr_len(smasi->my_number)) {
00726                 octstr_destroy(msg->sms.receiver);
00727                 msg->sms.receiver = octstr_duplicate(smasi->my_number);
00728             }
00729 
00730             time(&msg->sms.time);
00731             msg->sms.smsc_id = octstr_duplicate(smasi->conn->id);
00732             bb_smscconn_receive(smasi->conn, msg);
00733             resp = smasi_pdu_create(DeliverConf);
00734 
00735             if (pdu->u.DeliverReq.Sequence)
00736                 resp->u.DeliverConf.Sequence =
00737                   octstr_duplicate(pdu->u.DeliverReq.Sequence);
00738 
00739             if (pdu->u.DeliverReq.MsgReference)
00740                 resp->u.DeliverConf.MsgReference =
00741                   octstr_duplicate(pdu->u.DeliverReq.MsgReference);
00742             break;
00743 
00744         case SubmitConf:
00745             if (pdu->u.SubmitConf.Sequence) {
00746                 msg = dict_remove(smasi->sent_msgs, 
00747                                   pdu->u.SubmitConf.Sequence);
00748             } else {
00749                 msg = NULL;
00750             }
00751 
00752             if (msg == NULL) {
00753                 warning(0, "SMASI[%s]: SMSC sent SubmitConf for unknown message.",
00754                         octstr_get_cstr(smasi->conn->id));
00755             } else {
00756                 debug("bb.sms.smasi",0,
00757                       "SMSC[%s]: SMSC confirmed msg seq <%s> ref <%s>",
00758                        octstr_get_cstr(smasi->conn->id),
00759                        octstr_get_cstr(pdu->u.SubmitConf.Sequence),
00760                        octstr_get_cstr(pdu->u.SubmitConf.MsgReference));
00761 
00762                 bb_smscconn_sent(smasi->conn, msg, NULL);
00763 
00764                 --(*pending_submits);
00765             }
00766             break;
00767 
00768         case SubmitRej:
00769             if (pdu->u.SubmitRej.Sequence) {
00770                 msg = dict_remove(smasi->sent_msgs, 
00771                                   pdu->u.SubmitRej.Sequence);
00772             } else {
00773                 msg = NULL;
00774             }
00775 
00776             error(0, "SMASI[%s]: SMSC returned error code %s for "
00777                   "message ref <%s>", octstr_get_cstr(smasi->conn->id),
00778                   octstr_get_cstr(pdu->u.SubmitRej.RejectCode),
00779                   octstr_get_cstr(pdu->u.SubmitRej.MsgReference));
00780 
00781             if (msg == NULL) {
00782                warning(0, "SMASI[%s]: SMSC sent SubmitRej for unknown message.",
00783                        octstr_get_cstr(smasi->conn->id));
00784             } else {
00785                 reason = SMSCCONN_FAILED_REJECTED;
00786                 bb_smscconn_send_failed(smasi->conn, msg, reason,
00787                     octstr_create("REJECTED"));
00788                 --(*pending_submits);
00789             }
00790             break;
00791 
00792         case LogonConf:
00793             *pending_submits = 0;
00794 
00795             smasi->conn->status = SMSCCONN_ACTIVE;
00796             smasi->conn->connect_time = time(NULL);
00797 
00798             bb_smscconn_connected(smasi->conn);
00799 
00800             info(0, "SMASI[%s]: connection to SMSC established.",
00801                  octstr_get_cstr(smasi->conn->id));
00802             break;
00803 
00804         case LogonRej:
00805             if (octstr_len(pdu->u.LogonRej.Reason) > 0) {
00806                 error(0, "SMASI[%s]: SMSC rejected login with reason <%s>",
00807                       octstr_get_cstr(smasi->conn->id),
00808                       octstr_get_cstr(pdu->u.LogonRej.Reason));
00809             } else {
00810                 error(0, "SMASI[%s]: SMSC rejected login without reason",
00811                       octstr_get_cstr(smasi->conn->id));
00812             }
00813             break;
00814 
00815         case LogoffConf:
00816             info(0, "SMASI[%s]: SMSC confirmed logoff.",
00817                  octstr_get_cstr(smasi->conn->id));
00818             smasi->logged_off = 1;
00819             break;
00820 
00821         default:
00822             warning(0, "SMASI[%s]: Unknown PDU type <%s>, ignored.",
00823                     octstr_get_cstr(smasi->conn->id), pdu->type_name);
00824             break;
00825     }
00826 
00827     if (resp != NULL) {
00828         send_pdu(conn, smasi->conn->id, resp);
00829         smasi_pdu_destroy(resp);
00830     }
00831 }
00832 
00833 
00834 /************************************************************************/
00835 /* SMASI CONNECTION HANDLING                                            */
00836 /************************************************************************/
00837 
00838 /*
00839  * Open transmission connection to SMS center. Return NULL for error,
00840  * open connection for OK. Caller must set smasi->conn->status correctly
00841  * before calling this.
00842  */
00843 static Connection *open_connection(SMASI *smasi) 
00844 {
00845     Connection *conn = conn_open_tcp_with_port(smasi->host, smasi->port, 0, smasi->conn->our_host);
00846 
00847     if (conn == NULL) {
00848         error(0, "SMASI[%s]: Couldn't connect to server.",
00849               octstr_get_cstr(smasi->conn->id));
00850         return NULL;
00851     } else {
00852         SMASI_PDU *logon = smasi_pdu_create(LogonReq);
00853 
00854         logon->u.LogonReq.Name = octstr_duplicate(smasi->username);
00855         logon->u.LogonReq.Password = octstr_duplicate(smasi->password);
00856 
00857         counter_increase(smasi->message_id_counter);
00858 
00859         send_pdu(conn, smasi->conn->id, logon);
00860 
00861         smasi_pdu_destroy(logon);
00862     }
00863 
00864     return conn;
00865 } 
00866 
00867 
00868 static void send_messages(SMASI *smasi, Connection *conn, 
00869                           long *pending_submits) 
00870 {
00871     double delay = 0;
00872 
00873     if (*pending_submits == -1) return;
00874 
00875     if (smasi->conn->throughput > 0) {
00876         delay = 1.0 / smasi->conn->throughput;
00877     }
00878 
00879     while (*pending_submits < MAX_PENDING_SUBMITS) {
00880         SMASI_PDU *pdu = NULL;
00881         /* Get next message, quit if none to be sent. */
00882         Msg *msg = gwlist_extract_first(smasi->msgs_to_send);
00883 
00884         if (msg == NULL) break;
00885 
00886         /* Send PDU, record it as waiting for ack from SMSC. */
00887         pdu = msg_to_pdu(smasi, msg);
00888 
00889         if (pdu->u.SubmitReq.Sequence)
00890             dict_put(smasi->sent_msgs, pdu->u.SubmitReq.Sequence, msg);
00891 
00892         send_pdu(conn, smasi->conn->id, pdu);
00893 
00894         smasi_pdu_destroy(pdu);
00895 
00896         /* obey throughput speed limit, if any */
00897         if (smasi->conn->throughput > 0)
00898             gwthread_sleep(delay);
00899 
00900         ++(*pending_submits);
00901     }
00902 }
00903 
00904 
00905 /*
00906  * This is the main function for the background thread for doing I/O on
00907  * one SMASI connection (the one for transmitting or receiving messages).
00908  * It makes the initial connection to the SMASI server and re-connects
00909  * if there are I/O errors or other errors that require it.
00910  */
00911 static void smasi_thread(void *arg) 
00912 {
00913     long pending_submits;
00914     long len;
00915     SMASI_PDU *pdu;
00916     SMASI *smasi;
00917     int logoff_already_sent = 0;
00918     int ret;
00919     Connection *conn;
00920     long last_enquire_sent; 
00921     double timeout; 
00922 
00923     smasi = arg;
00924 
00925     /* Make sure we log into our own log-file if defined */
00926     log_thread_to(smasi->conn->log_idx);
00927     
00928     while (!smasi->quitting) {
00929 
00930         conn = open_connection(smasi);
00931         if (conn == NULL) {
00932             error(0, "SMASI[%s]: Could not connect to SMSC center " \
00933                   "(retrying in %ld seconds).",
00934                   octstr_get_cstr(smasi->conn->id), smasi->conn->reconnect_delay);
00935 
00936             gwthread_sleep(smasi->conn->reconnect_delay);
00937             smasi->conn->status = SMSCCONN_RECONNECTING;
00938             continue;
00939         }
00940 
00941         last_enquire_sent = date_universal_now(); 
00942         pending_submits = -1;
00943         len = 0;
00944 
00945         for (;;) {
00946             timeout = last_enquire_sent + smasi->enquire_link_interval
00947                         - date_universal_now(); 
00948 
00949             /* wait for activity */
00950             if (conn_wait(conn, timeout) == -1) {
00951                 error(0, "SMASI[%s]: I/O error or other error. Re-connecting.",
00952                       octstr_get_cstr(smasi->conn->id));
00953                 break;
00954             }
00955 
00956             /* Send logoff request if module is shutting down. */
00957             if (smasi->quitting && !logoff_already_sent) {
00958                 send_logoff(smasi, conn);
00959                 logoff_already_sent = 1;
00960             } 
00961 
00962             /* send an enquire link */
00963             send_enquire_link(smasi, conn, &last_enquire_sent); 
00964 
00965             /* Receive incoming PDUs. */
00966             while ((ret = read_pdu(smasi, conn, &pdu)) == 1) {
00967                 /* Deal with the PDU we just got */ 
00968                 dump_pdu("Got PDU:", smasi->conn->id, pdu);
00969 
00970                 /* Process the received PDU. */
00971                 handle_pdu(smasi, conn, pdu, &pending_submits);
00972 
00973                 smasi_pdu_destroy(pdu);
00974 
00975                 /* Bail out if logoff confirmed. */
00976                 if (smasi->logged_off) break;
00977 
00978                 /* Make sure we send even if we read a lot. */
00979                 if ((!smasi->throttling_err_time ||
00980                     ((time(NULL) - smasi->throttling_err_time) >
00981                      SMASI_THROTTLING_SLEEP_TIME
00982                       && !(smasi->throttling_err_time = 0))))
00983                     send_messages(smasi, conn, &pending_submits);
00984             } 
00985 
00986             /* Check if connection broken. */
00987             if (ret == -1) {
00988                 error(0, "SMASI[%s]: I/O error or other error. Re-connecting.",
00989                       octstr_get_cstr(smasi->conn->id));
00990                 break;
00991             } 
00992 
00993             /* Bail out if logoff confirmed. */
00994             if (smasi->logged_off) break;
00995 
00996             if ((!smasi->throttling_err_time ||
00997                 ((time(NULL) - smasi->throttling_err_time) >
00998                  SMASI_THROTTLING_SLEEP_TIME
00999                   && !(smasi->throttling_err_time = 0))))
01000                 send_messages(smasi, conn, &pending_submits);
01001 
01002         } 
01003 
01004         conn_destroy(conn);
01005         conn = NULL;
01006     } 
01007 } 
01008 
01009 
01010 /************************************************************************/
01011 /* SMSCCONN INTERFACE                                                   */
01012 /************************************************************************/
01013 
01014 static long queued_cb(SMSCConn *conn) 
01015 {
01016     SMASI *smasi = conn->data;
01017 
01018     conn->load = (smasi ? (conn->status != SMSCCONN_DEAD ? 
01019                     gwlist_len(smasi->msgs_to_send) : 0) : 0);
01020 
01021     return conn->load;
01022 } 
01023 
01024 
01025 static int send_msg_cb(SMSCConn *conn, Msg *msg) 
01026 {
01027     SMASI *smasi = conn->data;
01028 
01029     gwlist_produce(smasi->msgs_to_send, msg_duplicate(msg));
01030     gwthread_wakeup(smasi->thread_handle);
01031 
01032     return 0;
01033 }
01034 
01035 
01036 static int shutdown_cb(SMSCConn *conn, int finish_sending) 
01037 {
01038     SMASI *smasi = NULL;
01039 
01040     debug("bb.sms.smasi", 0, "Shutting down SMSCConn %s (%s)",
01041           octstr_get_cstr(conn->name), finish_sending ? "slow" : "instant");
01042 
01043     conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
01044 
01045     smasi = conn->data;
01046     smasi->quitting = 1;
01047     gwthread_wakeup(smasi->thread_handle);
01048     gwthread_join(smasi->thread_handle);
01049     smasi_destroy(smasi);
01050 
01051     debug("bb.sms.smasi", 0, "SMSCConn %s shut down.",
01052           octstr_get_cstr(conn->name));
01053     conn->status = SMSCCONN_DEAD;
01054     bb_smscconn_killed();
01055 
01056     /* Clean up. */
01057     octstr_destroy(colon);
01058     octstr_destroy(assign);
01059     octstr_destroy(comma);
01060     octstr_destroy(cr);
01061     octstr_destroy(lf);
01062 
01063     return 0;
01064 }
01065 
01066 
01067 /*
01068  * Configures the SMASI structure according to the configuration.
01069  *
01070  * @return 0 on complete success. -1 if failed due to missing or invalid
01071  * configuration entry.
01072  */
01073 static int init_configuration(SMASI *smasi, CfgGroup *config) 
01074 {
01075     /* Read mandatory entries. */
01076     smasi->host = cfg_get(config, octstr_imm("host"));
01077     smasi->username = cfg_get(config, octstr_imm("smsc-username"));
01078     smasi->password = cfg_get(config, octstr_imm("smsc-password"));
01079 
01080     /* Check configuration. */
01081     if (smasi->host == NULL) {
01082         error(0,"SMASI: Configuration file doesn't specify host");
01083         return -1;
01084     }
01085     if (smasi->username == NULL) {
01086         error(0, "SMASI: Configuration file doesn't specify username.");
01087         return -1;
01088     }
01089     if (smasi->password == NULL) {
01090         error(0, "SMASI: Configuration file doesn't specify password.");
01091         return -1;
01092     }
01093 
01094     /* Read optional entries. Set default values if not set. */
01095     smasi->my_number = cfg_get(config, octstr_imm("my-number"));
01096     if (cfg_get_integer(&smasi->port, config, octstr_imm("port")) == -1)
01097         smasi->port = SMASI_DEFAULT_PORT;
01098     if (cfg_get_integer(&smasi->source_addr_ton, config,
01099       octstr_imm("source-addr-ton")) == -1)
01100         smasi->source_addr_ton = SMASI_OVERRIDE_SOURCE_TON;
01101     if (cfg_get_integer(&smasi->source_addr_npi, config,
01102       octstr_imm("source-addr-npi")) == -1)
01103         smasi->source_addr_npi = SMASI_OVERRIDE_SOURCE_NPI;
01104     if (cfg_get_integer(&smasi->dest_addr_ton, config,
01105       octstr_imm("dest-addr-ton")) == -1)
01106         smasi->dest_addr_ton = SMASI_OVERRIDE_DEST_TON;
01107     if (cfg_get_integer(&smasi->dest_addr_npi, config,
01108       octstr_imm("dest-addr-npi")) == -1)
01109         smasi->dest_addr_npi = SMASI_OVERRIDE_DEST_NPI;
01110     if (cfg_get_integer(&smasi->priority, config,
01111       octstr_imm("priority")) == -1)
01112         smasi->priority = SMASI_DEFAULT_PRIORITY;
01113     if (cfg_get_integer(&smasi->enquire_link_interval, config,
01114       octstr_imm("enquire-link-interval")) == -1)
01115         smasi->enquire_link_interval = SMASI_ENQUIRE_LINK_INTERVAL;
01116    
01117     /* Configure SMSC connection. */
01118     smasi->conn->data = smasi;
01119     smasi->conn->name = octstr_format("SMASI:%S:%d:%S",
01120         smasi->host, smasi->port, smasi->username);
01121 
01122     smasi->conn->id = cfg_get(config, octstr_imm("smsc-id"));
01123 
01124     if (smasi->conn->id == NULL)
01125       smasi->conn->id = octstr_duplicate(smasi->conn->name);
01126 
01127     return 0;
01128 } 
01129 
01130 
01131 int smsc_smasi_create(SMSCConn *conn, CfgGroup *config) 
01132 {
01133     SMASI *smasi = NULL;
01134 
01135     /* Initialize data encoding subsystem. */
01136     colon = octstr_create(":3a");
01137     assign = octstr_create(":3d");
01138     comma = octstr_create(":2c");
01139     cr = octstr_create(":0a");
01140     lf = octstr_create(":0d");
01141 
01142     /* Create main SMASI structure and initialize it with configuration
01143      * settings.
01144      */
01145     smasi = smasi_create(conn);
01146 
01147     if (init_configuration(smasi, config) != 0)
01148         panic(0, "SMASI SMSC module configuration invalid.");
01149 
01150     conn->status = SMSCCONN_CONNECTING;
01151 
01152     /* Port is always set to a configured value or defaults to 21500.
01153      * Therefore, threads are always started.
01154      */
01155     smasi->thread_handle = gwthread_create(smasi_thread, smasi);
01156 
01157     if (smasi->thread_handle == -1) {
01158         error(0, "SMASI[%s]: Couldn't start SMASI thread.",
01159               octstr_get_cstr(smasi->conn->id));
01160         smasi_destroy(conn->data);
01161         return -1;
01162     } 
01163 
01164     /* Setup control function pointers. */
01165     conn->shutdown = shutdown_cb;
01166     conn->queued = queued_cb;
01167     conn->send_msg = send_msg_cb;
01168 
01169     return 0;
01170 }
01171 
See file LICENSE for details about the license agreement for using, modifying, copying or deriving work from this software.