diff --git a/.gitignore b/.gitignore index 7f275add..a53e58e1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ target/ .project .settings/ +*.iml +.idea/ +*.ipr +*.iws +/out/ +.idea_modules/ diff --git a/.travis.yml b/.travis.yml index f8c1fdde..c16d2f86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: java jdk: - - openjdk6 + - openjdk7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 970b9a49..fd42ee28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Cloudhopper by Twitter cloudhopper-smpp ---------------- ## 6.0.0-netty4-beta-2 - - ch-commons-util version bumped from 6.0.1 to 6.0.2 to fix race condition bug + - ch-commons-util version bumped from 6.0.1 to 6.0.2 to fix race condition bug in WindowFuture: https://github.com/twitter/cloudhopper-smpp/issues/61 @@ -13,6 +13,16 @@ cloudhopper-smpp on Trustin's review. - Netty dependency changed to 4.0.25.Final. + +## 5.0.9 - 2015-11-24 + - Add support for missing pdus and tags + - Allow unbound channel to respond to enquire_link PDU + - Corrected name of DataCoding SmppConstant to IA5 (not GSM) + - Updated value of DATA_CODING_GSM constant and made it deprecated + - Add ESM_CLASS Message Mode constants + - DefaultSmppServer should use IO executor passed to its constructor + - Fix-up comments on the SMPP error code constants + ## 5.0.8 - 2015-04-17 - Fixed issue where rawErrorCode not set on DeliveryReceipt (khaing211) - Support for host address in SmppServerConfiguration (pgoergler) diff --git a/pom.xml b/pom.xml index 7b821441..8d716a1e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.restcomm.smpp ch-smpp jar - 6.0.0-netty4-beta-3-SNAPSHOT + 6.0.0-netty4-beta-4-SNAPSHOT ch-smpp Efficient, scalable, and flexible Java implementation of the Short Messaging Peer to Peer Protocol (SMPP) https://github.com/Restcomm/cloudhopper-smpp @@ -24,11 +24,13 @@ + 1.6 + 1.6 com.cloudhopper.smpp 7.0.6 7.0.6 7.0.6 - 4.0.28.Final + 4.1.16.Final 1.1.3 4.12 1.7.25 @@ -86,4 +88,4 @@ - + \ No newline at end of file diff --git a/src/etc/SMPP_v5_0.pdf b/src/etc/SMPP_v5_0.pdf new file mode 100644 index 00000000..07fbf625 Binary files /dev/null and b/src/etc/SMPP_v5_0.pdf differ diff --git a/src/main/java/com/cloudhopper/smpp/SmppConstants.java b/src/main/java/com/cloudhopper/smpp/SmppConstants.java index be45f4fc..d29502cd 100644 --- a/src/main/java/com/cloudhopper/smpp/SmppConstants.java +++ b/src/main/java/com/cloudhopper/smpp/SmppConstants.java @@ -46,6 +46,7 @@ public class SmppConstants { public static final byte VERSION_3_3 = 0x33; public static final byte VERSION_3_4 = 0x34; + public static final byte VERSION_5_0 = 0x50; public static final int DEFAULT_WINDOW_SIZE = 1; public static final long DEFAULT_WINDOW_WAIT_TIMEOUT = 60000; @@ -79,8 +80,12 @@ public class SmppConstants { public static final int CMD_ID_OUTBIND = 0x0000000B; public static final int CMD_ID_ENQUIRE_LINK = 0x00000015; public static final int CMD_ID_SUBMIT_MULTI = 0x00000021; + public static final int CMD_ID_ALERT_NOTIFICATION = 0x00000102; public static final int CMD_ID_DATA_SM = 0x00000103; - + public static final int CMD_ID_BROADCAST_SM = 0x00000111; + public static final int CMD_ID_QUERY_BROADCAST_SM = 0x00000112; + public static final int CMD_ID_CANCEL_BROADCAST_SM = 0x00000113; + // // SMPP Command ID (Responses) // @@ -97,10 +102,14 @@ public class SmppConstants { public static final int CMD_ID_ENQUIRE_LINK_RESP = 0x80000015; public static final int CMD_ID_SUBMIT_MULTI_RESP = 0x80000021; public static final int CMD_ID_DATA_SM_RESP = 0x80000103; + public static final int CMD_ID_BROADCAST_SM_RESP = 0x80000111; + public static final int CMD_ID_QUERY_BROADCAST_SM_RESP = 0x80000112; + public static final int CMD_ID_CANCEL_BROADCAST_SM_RESP = 0x80000113; // // Optional TLV Tags // + public static final short TAG_SOURCE_TELEMATICS_ID = 0x0010; public static final short TAG_PAYLOAD_TYPE = 0x0019; public static final short TAG_PRIVACY_INDICATOR = 0x0201; public static final short TAG_USER_MESSAGE_REFERENCE = 0x0204; @@ -135,6 +144,8 @@ public class SmppConstants { public static final short TAG_MORE_MSGS_TO_FOLLOW = 0x0426; // Message State public static final short TAG_MSG_STATE = 0x0427; + // Congestion State + public static final short TAG_CONGESTION_STATE = 0x0428; // Callback Number Presentation Indicator public static final short TAG_CALLBACK_NUM_PRES_IND = 0x0302; // Callback Number Alphanumeric Tag @@ -151,6 +162,38 @@ public class SmppConstants { public static final short TAG_ITS_SESSION_INFO = 0x1383; // USSD Service Op public static final short TAG_USSD_SERVICE_OP = 0x0501; + // Broadcast Channel Indicator + public static final short TAG_BROADCAST_CHANNEL_INDICATOR = 0x0600; + // Broadcast Content Type + public static final short TAG_BROADCAST_CONTENT_TYPE = 0x0601; + // Broadcast Content Type Info + public static final short TAG_BROADCAST_CONTENT_TYPE_INFO = 0x0602; + // Broadcast Message Class + public static final short TAG_BROADCAST_MESSAGE_CLASS = 0x0603; + // Broadcast Rep Num + public static final short TAG_BROADCAST_REP_NUM = 0x0604; + // Broadcast Frequency Interval + public static final short TAG_BROADCAST_FREQUENCY_INTERVAL = 0x0605; + // Broadcast Area Identifier + public static final short TAG_BROADCAST_AREA_IDENTIFIER = 0x0606; + // Broadcast Error Status + public static final short TAG_BROADCAST_ERROR_STATUS = 0x0607; + // Broadcast Area Success + public static final short TAG_BROADCAST_AREA_SUCCESS = 0x0608; + // Broadcast End Time + public static final short TAG_BROADCAST_END_TIME = 0x0609; + // Broadcast Service Group + public static final short TAG_BROADCAST_SERVICE_GROUP = 0x060A; + // Source Network Id + public static final short TAG_SOURCE_NETWORK_ID = 0x060D; + // Dest Network Id + public static final short TAG_DEST_NETWORK_ID = 0x060E; + // Source Node Id + public static final short TAG_SOURCE_NODE_ID = 0x060F; + // Dest Node Id + public static final short TAG_DEST_NODE_ID = 0x0610; + // Billing Identification + public static final short TAG_BILLING_IDENTIFICATION = 0x060B; // Originating MSC Address public static final short TAG_ORIG_MSC_ADDR = (short)0x8081; // Destination MSC Address @@ -177,12 +220,20 @@ public class SmppConstants { public static final short TAG_ADD_STATUS_INFO = 0x001D; // Receipted Message ID public static final short TAG_RECEIPTED_MSG_ID = 0x001E; - + // MS Message Wait Facilities + public static final short TAG_MS_MSG_WAIT_FACILITIES = 0x0030; /** ESM Class */ + /** Message Mode (bits 1-0) */ + public static final byte ESM_CLASS_MM_MASK = 0x03; // BIN 00000011 + public static final byte ESM_CLASS_MM_DEFAULT = 0x00; // BIN 00000000 + public static final byte ESM_CLASS_MM_DATAGRAM = 0x01; // BIN 00000001 + public static final byte ESM_CLASS_MM_TRANSACTION = 0x02; // BIN 00000010 + public static final byte ESM_CLASS_MM_STORE_FORWARD = 0x03; // BIN 00000011 + /** Message Type (bits 5-2) */ - public static final byte ESM_CLASS_MT_MASK = (byte)0x1C; // BIN: 11100 + public static final byte ESM_CLASS_MT_MASK = (byte)0x1C; // BIN: 11100 public static final byte ESM_CLASS_MT_SMSC_DELIVERY_RECEIPT = (byte)0x04; // BIN: 100, Recv Msg contains SMSC delivery receipt public static final byte ESM_CLASS_MT_ESME_DELIVERY_RECEIPT = (byte)0x08; // BIN: 1000, Send/Recv Msg contains ESME delivery acknowledgement public static final byte ESM_CLASS_MT_MANUAL_USER_ACK = (byte)0x10; // BIN: 10000, Send/Recv Msg contains manual/user acknowledgment @@ -200,6 +251,7 @@ public class SmppConstants { public static final byte REGISTERED_DELIVERY_SMSC_RECEIPT_NOT_REQUESTED = 0x00; public static final byte REGISTERED_DELIVERY_SMSC_RECEIPT_REQUESTED = 0x01; public static final byte REGISTERED_DELIVERY_SMSC_RECEIPT_ON_FAILURE = 0x02; + public static final byte REGISTERED_DELIVERY_SMSC_RECEIPT_ON_SUCCESS = 0x03; // SME originated acknowledgement (bits 3 & 2) public static final byte REGISTERED_DELIVERY_SME_ACK_MASK = 0x0c; @@ -211,9 +263,9 @@ public class SmppConstants { // Intermediate notification (bit 4) // NOTE: SMPP 3.4 specs originally wrote (bit 5) but their matrix actually used bit 4 // the confirmed value is bit 4, not 5. - public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_MASK = 0x10; - public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_NOT_REQUESTED = 0x0; - public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_REQUESTED = 0x10; + public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_MASK = 0x10; + public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_NOT_REQUESTED = 0x00; + public static final byte REGISTERED_DELIVERY_INTERMEDIATE_NOTIFICATION_REQUESTED = 0x10; // Replace if Present flag @@ -275,7 +327,15 @@ public class SmppConstants { // SMPP Data Coding // public static final byte DATA_CODING_DEFAULT = (byte)0x00; // SMSC Default Alphabet - public static final byte DATA_CODING_GSM = (byte)0x01; // IA5 (CCITT T.50)/ASCII (ANSI X3.4) + public static final byte DATA_CODING_IA5 = (byte)0x01; // IA5 (CCITT T.50)/ASCII (ANSI X3.4) + + /** + * @deprecated May be removed in a future version + * Please use IA5 for DCS 0x01 or DEFAULT for DCS 0x00 + */ + @Deprecated + public static final byte DATA_CODING_GSM = (byte)0x01; + public static final byte DATA_CODING_8BITA = (byte)0x02; // Octet unspecified (8-bit binary) defined for TDMA and/ or CDMA but not defined for GSM public static final byte DATA_CODING_LATIN1 = (byte)0x03; // Latin 1 (ISO-8859-1) public static final byte DATA_CODING_8BIT = (byte)0x04; // Octet unspecified (8-bit binary) ALL TECHNOLOGIES @@ -302,153 +362,160 @@ public class SmppConstants { /** Command Length is invalid */ public static final int STATUS_INVCMDLEN = 0x00000002; - - public static final int STATUS_INVCMDID = 0x00000003; - // Invalid Command ID - public static final int STATUS_INVBNDSTS = 0x00000004; + // Invalid Command ID + public static final int STATUS_INVCMDID = 0x00000003; // Incorrect BIND Status for given command - - public static final int STATUS_ALYBND = 0x00000005; + public static final int STATUS_INVBNDSTS = 0x00000004; // ESME Already in Bound State - - public static final int STATUS_INVPRTFLG = 0x00000006; + public static final int STATUS_ALYBND = 0x00000005; // Invalid Priority Flag - - public static final int STATUS_INVREGDLVFLG = 0x00000007; + public static final int STATUS_INVPRTFLG = 0x00000006; // Invalid Registered Delivery Flag + public static final int STATUS_INVREGDLVFLG = 0x00000007; + // System Error + public static final int STATUS_SYSERR = 0x00000008; - public static final int STATUS_SYSERR = 0x00000008; // System Error - - // Reserved = 0x00000009 Reserved + // 0x00000009 Reserved - public static final int STATUS_INVSRCADR = 0x0000000A; // Invalid Source Address - - public static final int STATUS_INVDSTADR = 0x0000000B; + public static final int STATUS_INVSRCADR = 0x0000000A; // Invalid Dest Addr - - public static final int STATUS_INVMSGID = 0x0000000C; + public static final int STATUS_INVDSTADR = 0x0000000B; // Message ID is invalid - - public static final int STATUS_BINDFAIL = 0x0000000D; // Bind Failed - - public static final int STATUS_INVPASWD = 0x0000000E; // Invalid Password - - public static final int STATUS_INVSYSID = 0x0000000F; + public static final int STATUS_INVMSGID = 0x0000000C; + // Bind Failed + public static final int STATUS_BINDFAIL = 0x0000000D; + // Invalid Password + public static final int STATUS_INVPASWD = 0x0000000E; // Invalid System ID + public static final int STATUS_INVSYSID = 0x0000000F; - // Reserved = 0x00000010 Reserved + // 0x00000010 Reserved - public static final int STATUS_CANCELFAIL = 0x00000011; // Cancel SM Failed + public static final int STATUS_CANCELFAIL = 0x00000011; - // Reserved = 0x00000012 Reserved - public static final int STATUS_REPLACEFAIL = 0x00000013; + // 0x00000012 Reserved // Replace SM Failed - public static final int STATUS_MSGQFUL = 0x00000014; - + public static final int STATUS_REPLACEFAIL = 0x00000013; // Message Queue Full + public static final int STATUS_MSGQFUL = 0x00000014; + // Invalid Service Type public static final int STATUS_INVSERTYP = 0x00000015; - // Invalid Service Type - // Reserved = 0x00000016-0x00000032 - public static final int STATUS_INVNUMDESTS = 0x00000033; + // 0x00000016-0x00000032 Reserved // Invalid number of destinations + public static final int STATUS_INVNUMDESTS = 0x00000033; + // Invalid Distribution List name public static final int STATUS_INVDLNAME = 0x00000034; - // Invalid Distribution List name - // Reserved = 0x00000035-0x0000003F - public static final int STATUS_INVDESTFLAG = 0x00000040; + // 0x00000035-0x0000003F Reserved // Destination flag is invalid (submit_multi) - // Reserved = 0x00000041 Reserved - public static final int STATUS_INVSUBREP = 0x00000042; + public static final int STATUS_INVDESTFLAG = 0x00000040; - // Invalid ‘submit with replace’ request - // (i.e. submit_sm with replace_if_present_flag set) - public static final int STATUS_INVESMCLASS = 0x00000043; + // 0x00000041 Reserved + // Invalid ‘submit with replace’ request + // (i.e. submit_sm with replace_if_present_flag set) + public static final int STATUS_INVSUBREP = 0x00000042; // Invalid esm_class field data - public static final int STATUS_CNTSUBDL = 0x00000044; - + public static final int STATUS_INVESMCLASS = 0x00000043; // Cannot Submit to Distribution List + public static final int STATUS_CNTSUBDL = 0x00000044; + // submit_sm or submit_multi failed public static final int STATUS_SUBMITFAIL = 0x00000045; - // submit_sm or submit_multi failed - // Reserved = 0x00000046-0x00000047 Reserved - public static final int STATUS_INVSRCTON = 0x00000048; + // 0x00000046-0x00000047 Reserved // Invalid Source address TON - public static final int STATUS_INVSRCNPI = 0x00000049; - + public static final int STATUS_INVSRCTON = 0x00000048; // Invalid Source address NPI - public static final int STATUS_INVDSTTON = 0x00000050; - + public static final int STATUS_INVSRCNPI = 0x00000049; // Invalid Destination address TON + public static final int STATUS_INVDSTTON = 0x00000050; + // Invalid Destination address NPI public static final int STATUS_INVDSTNPI = 0x00000051; - // Invalid Destination address NPI - // Reserved = 0x00000052 Reserved - public static final int STATUS_INVSYSTYP = 0x00000053; + // 0x00000052 Reserved // Invalid system_type field - public static final int STATUS_INVREPFLAG = 0x00000054; - + public static final int STATUS_INVSYSTYP = 0x00000053; // Invalid replace_if_present flag + public static final int STATUS_INVREPFLAG = 0x00000054; + // Invalid number of messages public static final int STATUS_INVNUMMSGS = 0x00000055; - // Invalid number of messages - // Reserved = 0x00000056-0x00000057 Reserved + // 0x00000056-0x00000057 Reserved + + // Throttling error (ESME has exceeded allowed message limits) public static final int STATUS_THROTTLED = 0x00000058; - // Throttling error (ESME has exceeded allowed message - // limits) - // Reserved = 0x00000059-0x00000060 Reserved - public static final int STATUS_INVSCHED = 0x00000061; + // 0x00000059-0x00000060 Reserved // Invalid Scheduled Delivery Time - public static final int STATUS_INVEXPIRY = 0x00000062; - + public static final int STATUS_INVSCHED = 0x00000061; // Invalid message validity period (Expiry time) - public static final int STATUS_INVDFTMSGID = 0x00000063; - + public static final int STATUS_INVEXPIRY = 0x00000062; // Predefined Message Invalid or Not Found - public static final int STATUS_X_T_APPN = 0x00000064; - + public static final int STATUS_INVDFTMSGID = 0x00000063; // ESME Receiver Temporary App Error Code - public static final int STATUS_X_P_APPN = 0x00000065; - + public static final int STATUS_X_T_APPN = 0x00000064; // ESME Receiver Permanent App Error Code - public static final int STATUS_X_R_APPN = 0x00000066; - + public static final int STATUS_X_P_APPN = 0x00000065; // ESME Receiver Reject Message Error Code + public static final int STATUS_X_R_APPN = 0x00000066; + // query_sm request failed public static final int STATUS_QUERYFAIL = 0x00000067; - // query_sm request failed - // Reserved = 0x00000068-0x000000BF Reserved - public static final int STATUS_INVOPTPARSTREAM = 0x000000C0; + // 0x00000068-0x000000BF Reserved // Error in the optional part of the PDU Body. - public static final int STATUS_OPTPARNOTALLWD = 0x000000C1; - + public static final int STATUS_INVOPTPARSTREAM = 0x000000C0; // Optional Parameter not allowed - public static final int STATUS_INVPARLEN = 0x000000C2; - + public static final int STATUS_OPTPARNOTALLWD = 0x000000C1; // Invalid Parameter Length. - public static final int STATUS_MISSINGOPTPARAM = 0x000000C3; - + public static final int STATUS_INVPARLEN = 0x000000C2; // Expected Optional Parameter missing + public static final int STATUS_MISSINGOPTPARAM = 0x000000C3; + // Invalid Optional Parameter Value public static final int STATUS_INVOPTPARAMVAL = 0x000000C4; - // Invalid Optional Parameter Value - // Reserved = 0x000000C5-0x000000FD Reserved - public static final int STATUS_DELIVERYFAILURE = 0x000000FE; + // 0x000000C5-0x000000FD Reserved // Delivery Failure (used for data_sm_resp) - public static final int STATUS_UNKNOWNERR = 0x000000FF; // Unknown Error + public static final int STATUS_DELIVERYFAILURE = 0x000000FE; + // Unknown Error + public static final int STATUS_UNKNOWNERR = 0x000000FF; + // ESME Not authorised to use specified service_type + public static final int STATUS_SERTYPUNAUTH = 0x00000100; + // ESME Prohibited from using specified operation + public static final int STATUS_PROHIBITED = 0x00000101; + // Specified service_type is unavailable + public static final int STATUS_SERTYPUNAVAIL = 0x00000102; + // Specified service_type is denied + public static final int STATUS_SERTYPDENIED = 0x00000103; + // Invalid Data Coding Scheme + public static final int STATUS_INVDCS = 0x00000104; + // Source Address Sub unit is Invalid + public static final int STATUS_INVSRCADDRSUBUNIT = 0x00000105; + // Destination Address Sub unit is Invalid + public static final int STATUS_INVDSTADDRSUBUNIT = 0x00000106; + // Broadcast Frequency Interval is invalid + public static final int STATUS_INVBCASTFREQINT = 0x00000107; + // Broadcast Alias Name is invalid + public static final int STATUS_INVBCASTALIAS_NAME = 0x00000108; + // Broadcast Area Format is invalid + public static final int STATUS_INVBCASTAREAFMT = 0x00000109; + // Numberof Broadcast Areas is invalid + public static final int STATUS_INVNUMBCAST_AREAS = 0x0000010A; + // Broadcast Content Type is invalid + public static final int STATUS_INVBCASTCNTTYPE = 0x0000010B; + // Broadcast Message Class is invalid + public static final int STATUS_INVBCASTMSGCLASS = 0x0000010C; public static final Map STATUS_MESSAGE_MAP; public static final Map TAG_NAME_MAP; @@ -503,8 +570,22 @@ public class SmppConstants { STATUS_MESSAGE_MAP.put(STATUS_INVOPTPARAMVAL, "Optional parameter value invalid"); STATUS_MESSAGE_MAP.put(STATUS_DELIVERYFAILURE, "Deliver SM failed"); STATUS_MESSAGE_MAP.put(STATUS_UNKNOWNERR, "Unknown error"); + STATUS_MESSAGE_MAP.put(STATUS_SERTYPUNAUTH, "Not authorised to use specified service_type"); + STATUS_MESSAGE_MAP.put(STATUS_PROHIBITED, "Prohibited from using specified operation"); + STATUS_MESSAGE_MAP.put(STATUS_SERTYPUNAVAIL, "Specified service_type is unavailable"); + STATUS_MESSAGE_MAP.put(STATUS_SERTYPDENIED, "Specified service_type is denied"); + STATUS_MESSAGE_MAP.put(STATUS_INVDCS, "Invalid Data Coding Scheme"); + STATUS_MESSAGE_MAP.put(STATUS_INVSRCADDRSUBUNIT, "Source Address Sub unit is Invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVDSTADDRSUBUNIT, "Destination Address Sub unit is Invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVBCASTFREQINT, "Broadcast Frequency Interval is invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVBCASTALIAS_NAME, "Broadcast Alias Name is invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVBCASTAREAFMT, "Broadcast Area Format is invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVNUMBCAST_AREAS, "Number of Broadcast Areas is invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVBCASTCNTTYPE, "Broadcast Content Type is invalid"); + STATUS_MESSAGE_MAP.put(STATUS_INVBCASTMSGCLASS, "Broadcast Message Class is invalid"); TAG_NAME_MAP = new HashMap(); + TAG_NAME_MAP.put(TAG_SOURCE_TELEMATICS_ID, "source_telematics_id"); TAG_NAME_MAP.put(TAG_PAYLOAD_TYPE, "payload_type"); TAG_NAME_MAP.put(TAG_PRIVACY_INDICATOR, "privacy_indicator"); TAG_NAME_MAP.put(TAG_USER_MESSAGE_REFERENCE, "user_message_reference"); @@ -529,6 +610,7 @@ public class SmppConstants { TAG_NAME_MAP.put(TAG_DELIVERY_FAILURE_REASON, "delivery_failure_reason"); TAG_NAME_MAP.put(TAG_MORE_MSGS_TO_FOLLOW, "more_msgs_to_follow"); TAG_NAME_MAP.put(TAG_MSG_STATE, "message_state"); + TAG_NAME_MAP.put(TAG_CONGESTION_STATE, "congestion_state"); TAG_NAME_MAP.put(TAG_CALLBACK_NUM_PRES_IND, "callback_num_pres_ind"); TAG_NAME_MAP.put(TAG_CALLBACK_NUM_ATAG, "callback_num_atag"); TAG_NAME_MAP.put(TAG_NUM_MSGS, "num_msgs_in_mailbox"); @@ -537,6 +619,22 @@ public class SmppConstants { TAG_NAME_MAP.put(TAG_ITS_REPLY_TYPE, "its_reply_type"); TAG_NAME_MAP.put(TAG_ITS_SESSION_INFO, "its_session_info"); TAG_NAME_MAP.put(TAG_USSD_SERVICE_OP, "ussd_service_op"); + TAG_NAME_MAP.put(TAG_BROADCAST_CHANNEL_INDICATOR, "broadcast_channel_indicator"); + TAG_NAME_MAP.put(TAG_BROADCAST_CONTENT_TYPE, "broadcast_content_type"); + TAG_NAME_MAP.put(TAG_BROADCAST_CONTENT_TYPE_INFO, "broadcast_content_type_info"); + TAG_NAME_MAP.put(TAG_BROADCAST_MESSAGE_CLASS, "broadcast_message_class"); + TAG_NAME_MAP.put(TAG_BROADCAST_REP_NUM, "broadcast_rep_num"); + TAG_NAME_MAP.put(TAG_BROADCAST_FREQUENCY_INTERVAL, "broadcast_frequency_interval"); + TAG_NAME_MAP.put(TAG_BROADCAST_AREA_IDENTIFIER, "broadcast_area_identifier"); + TAG_NAME_MAP.put(TAG_BROADCAST_ERROR_STATUS, "broadcast_error_status"); + TAG_NAME_MAP.put(TAG_BROADCAST_AREA_SUCCESS, "broadcast_area_success"); + TAG_NAME_MAP.put(TAG_BROADCAST_END_TIME, "broadcast_end_time"); + TAG_NAME_MAP.put(TAG_BROADCAST_SERVICE_GROUP, "broadcast_service_group"); + TAG_NAME_MAP.put(TAG_SOURCE_NETWORK_ID, "source_network_id"); + TAG_NAME_MAP.put(TAG_DEST_NETWORK_ID, "dest_network_id"); + TAG_NAME_MAP.put(TAG_SOURCE_NODE_ID, "source_node_id"); + TAG_NAME_MAP.put(TAG_DEST_NODE_ID, "dest_node_id"); + TAG_NAME_MAP.put(TAG_BILLING_IDENTIFICATION, "billing_identification"); TAG_NAME_MAP.put(TAG_ORIG_MSC_ADDR, "orig_msc_addr"); TAG_NAME_MAP.put(TAG_DEST_MSC_ADDR, "dest_msc_addr"); TAG_NAME_MAP.put(TAG_DEST_ADDR_SUBUNIT, "dest_addr_subunit"); @@ -550,6 +648,7 @@ public class SmppConstants { TAG_NAME_MAP.put(TAG_QOS_TIME_TO_LIVE, "qos_time_to_live"); TAG_NAME_MAP.put(TAG_ADD_STATUS_INFO, "additional_status_info"); TAG_NAME_MAP.put(TAG_RECEIPTED_MSG_ID, "receipted_message_id"); + TAG_NAME_MAP.put(TAG_MS_MSG_WAIT_FACILITIES, "ms_msg_wait_facilities"); } } diff --git a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppClient.java b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppClient.java index 10f4df3a..e53a3b4c 100644 --- a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppClient.java +++ b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppClient.java @@ -34,32 +34,22 @@ import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ConnectTimeoutException; import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.oio.OioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.channel.socket.oio.OioSocketChannel; -import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; -import java.net.InetSocketAddress; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngine; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -231,7 +221,8 @@ protected void doBind(DefaultSmppSession session, SmppSessionConfiguration confi protected DefaultSmppSession doOpen(SmppSessionConfiguration config, SmppSessionHandler sessionHandler) throws SmppTimeoutException, SmppChannelException, InterruptedException { // create and connect a channel to the remote host - this.clientChannel = createConnectedChannel(config.getHost(), config.getPort(), config.getConnectTimeout()); + this.clientChannel = createConnectedChannel(config.getHost(), config.getPort(), config.getConnectTimeout(), + config.getClientBindHost(), config.getClientBindPort()); // tie this new opened channel with a new session return createSession(clientChannel, config, sessionHandler); } @@ -294,23 +285,32 @@ protected DefaultSmppSession createSession(Channel channel, SmppSessionConfigura } protected Channel createConnectedChannel(String host, int port, long connectTimeoutMillis) throws SmppTimeoutException, SmppChannelException, InterruptedException { + return createConnectedChannel(host, port, connectTimeoutMillis, null, 0); + } + + protected Channel createConnectedChannel( + String host, int port, long connectTimeoutMillis, String clientLocalHost, int clientLocalPort + ) throws SmppTimeoutException, SmppChannelException, InterruptedException { // a socket address used to "bind" to the remote system InetSocketAddress socketAddr = new InetSocketAddress(host, port); // set the timeout this.clientBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int)connectTimeoutMillis); + // a socket address used to "bind" in the local system (e.g. for using specific IP if current host has many) + InetSocketAddress localAddr = clientLocalHost == null ? null : + new InetSocketAddress(clientLocalHost, clientLocalPort); // attempt to connect to the remote system - ChannelFuture connectFuture = this.clientBootstrap.connect(socketAddr); + ChannelFuture connectFuture = this.clientBootstrap.connect(socketAddr, localAddr); /* It turns out that under certain unknown circumstances the connect waits forever: https://github.com/twitter/cloudhopper-smpp/issues/117 * That's why the future is canceled 1 second after the specified timeout. * This is a workaround and hopefully not needed after the switch to netty 4. */ - logger.debug("Waiting for client connection to {}", socketAddr); + logger.debug("Waiting for client connection to {} from {}", socketAddr, localAddr); if (!connectFuture.await(connectTimeoutMillis + 1000)) { logger.error("connectFuture did not finish in expected time! Try to cancel the connectFuture"); - boolean isCanceled = connectFuture.cancel(); + boolean isCanceled = connectFuture.cancel(true); logger.error("connectFuture: isCanceled {} isDone {} isSuccess {}", isCanceled, connectFuture.isDone(), connectFuture.isSuccess()); throw new SmppChannelConnectTimeoutException("Could not connect to the server within timeout"); } diff --git a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppServer.java b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppServer.java index e9bfff2f..03a3ce48 100644 --- a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppServer.java +++ b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppServer.java @@ -69,7 +69,7 @@ public class DefaultSmppServer implements SmppServer, DefaultSmppServerMXBean { private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; private ServerBootstrap serverBootstrap; - private Channel serverChannel; + private Channel serverChannel; // shared instance of a timer background thread to close unbound channels private final Timer bindTimer; // shared instance of a session id generator (an atomic long) @@ -242,8 +242,8 @@ public void start() throws SmppChannelException { // this.serverBootstrap.bind(...).sync(); // ChannelFuture.sync() is a very useful operation. It waits until the future is // fulfilled and then rethrow the exception if the operation has failed. - // Not sure if we can do this, as we actually need to retry on timeout. - + // Not sure if we can do this, as we actually need to retry on timeout. + if (timeout) throw new SmppChannelException("Can't bind to port " + configuration.getPort() + " after " + configuration.getBindTimeout() + " milliseconds"); diff --git a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppSession.java b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppSession.java index e205b8db..f3458d7f 100644 --- a/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppSession.java +++ b/src/main/java/com/cloudhopper/smpp/impl/DefaultSmppSession.java @@ -520,7 +520,7 @@ public WindowFuture sendRequestPdu(PduRequest pd // check if the write was a success if (!channelFuture.isSuccess()) { // the write failed, make sure to throw an exception - throw new SmppChannelException(channelFuture.cause() != null ? channelFuture.getCause().getMessage() + throw new SmppChannelException(channelFuture.cause() != null ? channelFuture.cause().getMessage() : "ChannelFuture failed without cause.", channelFuture.cause()); } diff --git a/src/main/java/com/cloudhopper/smpp/impl/UnboundSmppSession.java b/src/main/java/com/cloudhopper/smpp/impl/UnboundSmppSession.java index 43e85290..58f88f86 100644 --- a/src/main/java/com/cloudhopper/smpp/impl/UnboundSmppSession.java +++ b/src/main/java/com/cloudhopper/smpp/impl/UnboundSmppSession.java @@ -69,59 +69,65 @@ public void firePduReceived(Pdu pdu) { // always log the PDU received on an unbound session logger.info("received PDU: {}", pdu); - // only bind requests are permitted - if (!(pdu instanceof BaseBind)) { - logger.warn("Only bind requests are permitted on new connections, closing connection [{}]", channelName); - - // FIXME: we could create a response with an error and THEN close the connection... + // only bind and enquire_link requests are permitted + if (pdu instanceof BaseBind) { + // delegate any bind request to the server handler + // variables we track for a successful bind request + BaseBind bindRequest = (BaseBind)pdu; + + // create a default session configuration based on this bind request + SmppSessionConfiguration sessionConfiguration = createSessionConfiguration(bindRequest); + + // assign a new identifier for this session + Long sessionId = server.nextSessionId(); + + try { + // delegate the bind request upstream to server handler + this.server.bindRequested(sessionId, sessionConfiguration, bindRequest); + } catch (SmppProcessingException e) { + logger.warn("Bind request rejected or failed for connection [{}] with error [{}]", channelName, e.getMessage()); + // create a failed bind response and send back to connection + BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode()); + this.sendResponsePdu(bindResponse); + // cancel the timer task & close connection + closeChannelAndCancelTimer(); + return; + } - // cancel the timer task & close connection - closeChannelAndCancelTimer(); + // if we got there then 98% "bound" -- we just need to create the + // new session and tie everything together -- cancel the bind timer + this.bindTimeoutTask.cancel(); + + // prepare an "OK" bind response that the session will send back once flagged as 'serverReady' + BaseBindResp preparedBindResponse = server.createBindResponse(bindRequest, SmppConstants.STATUS_OK); + + try { + // create a new a new session and tie the bind response to it + server.createSession(sessionId, channel, sessionConfiguration, preparedBindResponse); + } catch (SmppProcessingException e) { + logger.warn("Bind request was approved, but createSession failed for connection [{}] with error [{}]", channelName, e.getMessage()); + // create a failed bind response and send back to connection + BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode()); + this.sendResponsePdu(bindResponse); + // cancel the timer task & close connection + closeChannelAndCancelTimer(); + return; + } + } else if (pdu instanceof EnquireLink) { + EnquireLinkResp response = ((EnquireLink) pdu).createResponse(); + logger.info("Responding to enquire_link with response [{}]", response); + this.sendResponsePdu(response); return; - } - - // delegate any bind request to the server handler - // variables we track for a successful bind request - BaseBind bindRequest = (BaseBind)pdu; + } else { + logger.warn("Only bind or enquire_link requests are permitted on new connections, closing connection [{}]", channelName); - // create a default session configuration based on this bind request - SmppSessionConfiguration sessionConfiguration = createSessionConfiguration(bindRequest); - - // assign a new identifier for this session - Long sessionId = server.nextSessionId(); + // FIXME: we could create a response with an error and THEN close the connection... - try { - // delegate the bind request upstream to server handler - this.server.bindRequested(sessionId, sessionConfiguration, bindRequest); - } catch (SmppProcessingException e) { - logger.warn("Bind request rejected or failed for connection [{}] with error [{}]", channelName, e.getMessage()); - // create a failed bind response and send back to connection - BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode()); - this.sendResponsePdu(bindResponse); // cancel the timer task & close connection closeChannelAndCancelTimer(); return; } - // if we got there then 98% "bound" -- we just need to create the - // new session and tie everything together -- cancel the bind timer - this.bindTimeoutTask.cancel(); - - // prepare an "OK" bind response that the session will send back once flagged as 'serverReady' - BaseBindResp preparedBindResponse = server.createBindResponse(bindRequest, SmppConstants.STATUS_OK); - - try { - // create a new a new session and tie the bind response to it - server.createSession(sessionId, channel, sessionConfiguration, preparedBindResponse); - } catch (SmppProcessingException e) { - logger.warn("Bind request was approved, but createSession failed for connection [{}] with error [{}]", channelName, e.getMessage()); - // create a failed bind response and send back to connection - BaseBindResp bindResponse = server.createBindResponse(bindRequest, e.getErrorCode()); - this.sendResponsePdu(bindResponse); - // cancel the timer task & close connection - closeChannelAndCancelTimer(); - return; - } } public void closeChannelAndCancelTimer() { diff --git a/src/main/java/com/cloudhopper/smpp/pdu/AlertNotification.java b/src/main/java/com/cloudhopper/smpp/pdu/AlertNotification.java new file mode 100644 index 00000000..094a5ed8 --- /dev/null +++ b/src/main/java/com/cloudhopper/smpp/pdu/AlertNotification.java @@ -0,0 +1,86 @@ +package com.cloudhopper.smpp.pdu; + +/* + * #%L + * ch-smpp + * %% + * Copyright (C) 2009 - 2015 Cloudhopper by Twitter + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.cloudhopper.commons.util.StringUtil; +import com.cloudhopper.smpp.SmppConstants; +import com.cloudhopper.smpp.type.Address; +import com.cloudhopper.smpp.type.RecoverablePduException; +import com.cloudhopper.smpp.type.UnrecoverablePduException; +import com.cloudhopper.smpp.util.ByteBufUtil; +import com.cloudhopper.smpp.util.PduUtil; +import io.netty.buffer.ByteBuf; + +public class AlertNotification extends Pdu { + + protected Address sourceAddress; + protected Address esmeAddress; + + public AlertNotification(){ + super( SmppConstants.CMD_ID_ALERT_NOTIFICATION, "alert_notification", true ); + } + + public Address getSourceAddress() { + return this.sourceAddress; + } + + public void setSourceAddress(Address value) { + this.sourceAddress = value; + } + + public Address getEsmeAddress() { + return this.esmeAddress; + } + + public void setEsmeAddress(Address value) { + this.esmeAddress = value; + } + + @Override + protected int calculateByteSizeOfBody(){ + int bodyLength = 0; + bodyLength += PduUtil.calculateByteSizeOfAddress(this.sourceAddress); + bodyLength += PduUtil.calculateByteSizeOfAddress(this.esmeAddress); + return bodyLength; + } + + @Override + public void readBody( ByteBuf buffer ) throws UnrecoverablePduException, RecoverablePduException{ + this.sourceAddress = ByteBufUtil.readAddress(buffer); + this.esmeAddress = ByteBufUtil.readAddress(buffer); + } + + @Override + public void writeBody( ByteBuf buffer ) throws UnrecoverablePduException, RecoverablePduException{ + ByteBufUtil.writeAddress(buffer, this.sourceAddress); + ByteBufUtil.writeAddress(buffer, this.esmeAddress); + } + + @Override + protected void appendBodyToString( StringBuilder buffer ){ + buffer.append("( sourceAddr ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.sourceAddress)); + buffer.append("] esmeAddr ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.esmeAddress)); + buffer.append("])"); + } + +} diff --git a/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSm.java b/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSm.java new file mode 100644 index 00000000..d882191f --- /dev/null +++ b/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSm.java @@ -0,0 +1,181 @@ +package com.cloudhopper.smpp.pdu; + +/* + * #%L + * ch-smpp + * %% + * Copyright (C) 2009 - 2015 Cloudhopper by Twitter + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.cloudhopper.commons.util.HexUtil; +import com.cloudhopper.commons.util.StringUtil; +import com.cloudhopper.smpp.SmppConstants; +import com.cloudhopper.smpp.type.Address; +import com.cloudhopper.smpp.type.RecoverablePduException; +import com.cloudhopper.smpp.type.SmppInvalidArgumentException; +import com.cloudhopper.smpp.type.UnrecoverablePduException; +import com.cloudhopper.smpp.util.ByteBufUtil; +import com.cloudhopper.smpp.util.PduUtil; +import io.netty.buffer.ByteBuf; + +public class ReplaceSm extends PduRequest { + + private String messageId; + private Address sourceAddress; + private String scheduleDeliveryTime; + private String validityPeriod; + private byte registeredDelivery; + private byte defaultMsgId; + private byte[] shortMessage; + + public ReplaceSm() { + super(SmppConstants.CMD_ID_REPLACE_SM, "replace_sm"); + } + + @Override + public ReplaceSmResp createResponse() { + ReplaceSmResp resp = new ReplaceSmResp(); + resp.setSequenceNumber(this.getSequenceNumber()); + return resp; + } + + @Override + public Class getResponseClass() { + return ReplaceSmResp.class; + } + + + public String getMessageId(){ + return messageId; + } + + public void setMessageId(String messageId){ + this.messageId = messageId; + } + + public int getShortMessageLength() { + return (this.shortMessage == null ? 0 : this.shortMessage.length); + } + + public byte[] getShortMessage() { + return this.shortMessage; + } + + public void setShortMessage(byte[] value) throws SmppInvalidArgumentException { + if (value != null && value.length > 255) { + throw new SmppInvalidArgumentException("A short message in a PDU can only be a max of 255 bytes [actual=" + value.length + "]; use optional parameter message_payload as an alternative"); + } + this.shortMessage = value; + } + + public byte getDefaultMsgId() { + return this.defaultMsgId; + } + + public void setDefaultMsgId(byte value) { + this.defaultMsgId = value; + } + + public byte getRegisteredDelivery() { + return this.registeredDelivery; + } + + public void setRegisteredDelivery(byte value) { + this.registeredDelivery = value; + } + + public String getValidityPeriod() { + return this.validityPeriod; + } + + public void setValidityPeriod(String value) { + this.validityPeriod = value; + } + + public String getScheduleDeliveryTime() { + return this.scheduleDeliveryTime; + } + + public void setScheduleDeliveryTime(String value) { + this.scheduleDeliveryTime = value; + } + + public Address getSourceAddress() { + return this.sourceAddress; + } + + public void setSourceAddress(Address value) { + this.sourceAddress = value; + } + + @Override + public void readBody(ByteBuf buffer) throws UnrecoverablePduException, RecoverablePduException { + this.messageId = ByteBufUtil.readNullTerminatedString(buffer); + this.sourceAddress = ByteBufUtil.readAddress(buffer); + this.scheduleDeliveryTime = ByteBufUtil.readNullTerminatedString(buffer); + this.validityPeriod = ByteBufUtil.readNullTerminatedString(buffer); + this.registeredDelivery = buffer.readByte(); + this.defaultMsgId = buffer.readByte(); + // this is always an unsigned version of the short message length + short shortMessageLength = buffer.readUnsignedByte(); + this.shortMessage = new byte[shortMessageLength]; + buffer.readBytes(this.shortMessage); + } + + @Override + public int calculateByteSizeOfBody() { + int bodyLength = 0; + bodyLength += PduUtil.calculateByteSizeOfNullTerminatedString(this.messageId); + bodyLength += PduUtil.calculateByteSizeOfAddress(this.sourceAddress); + bodyLength += PduUtil.calculateByteSizeOfNullTerminatedString(this.scheduleDeliveryTime); + bodyLength += PduUtil.calculateByteSizeOfNullTerminatedString(this.validityPeriod); + bodyLength += 3; // regDelivery, defaultMsgId, messageLength bytes + bodyLength += getShortMessageLength(); + return bodyLength; + } + + @Override + public void writeBody(ByteBuf buffer) throws UnrecoverablePduException, RecoverablePduException { + ByteBufUtil.writeNullTerminatedString(buffer, this.messageId); + ByteBufUtil.writeAddress(buffer, this.sourceAddress); + ByteBufUtil.writeNullTerminatedString(buffer, this.scheduleDeliveryTime); + ByteBufUtil.writeNullTerminatedString(buffer, this.validityPeriod); + buffer.writeByte(this.registeredDelivery); + buffer.writeByte(this.defaultMsgId); + buffer.writeByte((byte)getShortMessageLength()); + if (this.shortMessage != null) { + buffer.writeBytes(this.shortMessage); + } + } + + @Override + public void appendBodyToString(StringBuilder buffer) { + buffer.append("( messageId ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.messageId)); + buffer.append("] sourceAddr ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.sourceAddress)); + buffer.append("] scheduleDeliveryTime ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.scheduleDeliveryTime)); + buffer.append("] validityPeriod ["); + buffer.append(StringUtil.toStringWithNullAsEmpty(this.validityPeriod)); + buffer.append("] regDlvry [0x"); + buffer.append(HexUtil.toHexString(this.registeredDelivery)); + buffer.append("] message ["); + HexUtil.appendHexString(buffer, this.shortMessage); + buffer.append("])"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSmResp.java b/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSmResp.java new file mode 100644 index 00000000..9646460e --- /dev/null +++ b/src/main/java/com/cloudhopper/smpp/pdu/ReplaceSmResp.java @@ -0,0 +1,53 @@ +package com.cloudhopper.smpp.pdu; + +/* + * #%L + * ch-smpp + * %% + * Copyright (C) 2009 - 2015 Cloudhopper by Twitter + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.cloudhopper.smpp.SmppConstants; +import com.cloudhopper.smpp.type.RecoverablePduException; +import com.cloudhopper.smpp.type.UnrecoverablePduException; +import io.netty.buffer.ByteBuf; + +public class ReplaceSmResp extends PduResponse { + + public ReplaceSmResp() { + super(SmppConstants.CMD_ID_REPLACE_SM_RESP, "replace_sm_resp"); + } + + @Override + public void readBody(ByteBuf buffer) throws UnrecoverablePduException, RecoverablePduException { + // nothing + } + + @Override + public int calculateByteSizeOfBody() { + return 0; + } + + @Override + public void writeBody(ByteBuf buffer) throws UnrecoverablePduException, RecoverablePduException { + // do nothing + } + + @Override + public void appendBodyToString(StringBuilder buffer) { + } + +} \ No newline at end of file diff --git a/src/main/java/com/cloudhopper/smpp/transcoder/DefaultPduTranscoder.java b/src/main/java/com/cloudhopper/smpp/transcoder/DefaultPduTranscoder.java index 6023b0ad..ae4f0d99 100644 --- a/src/main/java/com/cloudhopper/smpp/transcoder/DefaultPduTranscoder.java +++ b/src/main/java/com/cloudhopper/smpp/transcoder/DefaultPduTranscoder.java @@ -156,6 +156,8 @@ protected Pdu doDecode(int commandLength, ByteBuf buffer) throws UnrecoverablePd pdu = new CancelSm(); } else if (commandId == SmppConstants.CMD_ID_QUERY_SM) { pdu = new QuerySm(); + } else if (commandId == SmppConstants.CMD_ID_REPLACE_SM) { + pdu = new ReplaceSm(); } else if (commandId == SmppConstants.CMD_ID_BIND_TRANSCEIVER) { pdu = new BindTransceiver(); } else if (commandId == SmppConstants.CMD_ID_BIND_TRANSMITTER) { @@ -164,6 +166,8 @@ protected Pdu doDecode(int commandLength, ByteBuf buffer) throws UnrecoverablePd pdu = new BindReceiver(); } else if (commandId == SmppConstants.CMD_ID_UNBIND) { pdu = new Unbind(); + } else if (commandId == SmppConstants.CMD_ID_ALERT_NOTIFICATION) { + pdu = new AlertNotification(); } else if (commandId == SmppConstants.CMD_ID_SUBMIT_MULTI) { pdu = new SubmitMulti(); } else { @@ -180,6 +184,8 @@ protected Pdu doDecode(int commandLength, ByteBuf buffer) throws UnrecoverablePd pdu = new CancelSmResp(); } else if (commandId == SmppConstants.CMD_ID_QUERY_SM_RESP) { pdu = new QuerySmResp(); + } else if (commandId == SmppConstants.CMD_ID_REPLACE_SM_RESP) { + pdu = new ReplaceSmResp(); } else if (commandId == SmppConstants.CMD_ID_ENQUIRE_LINK_RESP) { pdu = new EnquireLinkResp(); } else if (commandId == SmppConstants.CMD_ID_BIND_TRANSCEIVER_RESP) { diff --git a/src/main/java/com/cloudhopper/smpp/type/SmppConnectionConfiguration.java b/src/main/java/com/cloudhopper/smpp/type/SmppConnectionConfiguration.java index d0fa0827..32272c3b 100644 --- a/src/main/java/com/cloudhopper/smpp/type/SmppConnectionConfiguration.java +++ b/src/main/java/com/cloudhopper/smpp/type/SmppConnectionConfiguration.java @@ -32,15 +32,24 @@ public class SmppConnectionConfiguration { private String host; private int port; private long connectTimeout; + private String clientBindHost; + private int clientBindPort; public SmppConnectionConfiguration() { this(null, 0, SmppConstants.DEFAULT_CONNECT_TIMEOUT); } public SmppConnectionConfiguration(String host, int port, long connectTimeout) { + this(host, port, connectTimeout, null, 0); + } + + public SmppConnectionConfiguration(String host, int port, long connectTimeout, + String clientBindHost, int clientBindPort) { this.host = host; this.port = port; this.connectTimeout = connectTimeout; + this.clientBindHost = clientBindHost; + this.clientBindPort = clientBindPort; } public void setHost(String value) { @@ -67,4 +76,19 @@ public long getConnectTimeout() { return this.connectTimeout; } + public String getClientBindHost() { + return clientBindHost; + } + + public void setClientBindHost(String clientBindHost) { + this.clientBindHost = clientBindHost; + } + + public int getClientBindPort() { + return clientBindPort; + } + + public void setClientBindPort(int clientBindPort) { + this.clientBindPort = clientBindPort; + } } diff --git a/src/test/java/com/cloudhopper/smpp/impl/DefaultSmppSessionTest.java b/src/test/java/com/cloudhopper/smpp/impl/DefaultSmppSessionTest.java index 3d3eb9cf..5257eda7 100644 --- a/src/test/java/com/cloudhopper/smpp/impl/DefaultSmppSessionTest.java +++ b/src/test/java/com/cloudhopper/smpp/impl/DefaultSmppSessionTest.java @@ -33,13 +33,13 @@ import com.cloudhopper.smpp.type.*; import com.cloudhopper.smpp.util.SmppSessionUtil; import io.netty.channel.Channel; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import io.netty.util.internal.ThreadLocalRandom; +import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.BindException; +import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; // my imports @@ -57,7 +57,9 @@ public class DefaultSmppSessionTest { static SmppSimulatorServer server; static DefaultSmppClient bootstrap; - + + SmppSession session; + @BeforeClass public static void startSimulator() { server = new SmppSimulatorServer(); @@ -73,6 +75,20 @@ public static void stopSimulator() { bootstrap.destroy(); } + @Before + public void before() throws Exception { + session = null; + } + + @After + public void after() throws Exception { + SmppSessionUtil.close(session); + session = null; + logger.info("cleaning up server"); + unregisterServerBindProcessor(); + clearAllServerSessions(); + } + public SmppSessionConfiguration createDefaultConfiguration() { SmppSessionConfiguration configuration = new SmppSessionConfiguration(); configuration.setWindowSize(1); @@ -106,14 +122,11 @@ public void bindToBadPortThrowsSmppChannelConnectException() throws Exception { // change this to a port we know a server isn't running on configuration.setPort(PORT+1); - DefaultSmppSession session = null; try { - session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); Assert.fail(); } catch (SmppChannelConnectException e) { // correct behavior - } finally { - SmppSessionUtil.close(session); } } @@ -123,15 +136,12 @@ public void bindToUnknownHostThrowsSmppChannelConnectException() throws Exceptio // change to a host that doesn't exist configuration.setHost("jfjdjdjdjdjjdjd"); - DefaultSmppSession session = null; try { - session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); Assert.fail(); } catch (SmppChannelConnectException e) { // correct behavior logger.info("Expected error message: {}", e.getMessage()); - } finally { - SmppSessionUtil.close(session); } } @@ -142,15 +152,12 @@ public void bindToFirewalledHostThrowsSmppChannelConnectTimeoutException() throw configuration.setHost("www.twitter.com"); configuration.setPort(81); - DefaultSmppSession session = null; try { - session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); Assert.fail(); } catch (SmppChannelConnectTimeoutException e) { // correct behavior logger.info("Expected error message: {}", e.getMessage()); - } finally { - SmppSessionUtil.close(session); } } @@ -159,16 +166,13 @@ public void bindConnectsButNoResponseThrowsSmppTimeoutException() throws Excepti SmppSessionConfiguration configuration = createDefaultConfiguration(); unregisterServerBindProcessor(); - DefaultSmppSession session = null; try { - session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); Assert.fail(); } catch (SmppTimeoutException e) { // correct behavior (underlying cause MUST be a response timeout) // Assert.assertNotNull(e.getCause()); // Assert.assertEquals(ResponseTimeoutException.class, e.getCause().getClass()); - } finally { - SmppSessionUtil.close(session); } } @@ -180,16 +184,13 @@ public void bindWithBadCredentialsThrowsSmppBindException() throws Exception { // set a bad system id configuration.setSystemId("BADID"); - DefaultSmppSession session = null; try { - session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); Assert.fail(); } catch (SmppBindException e) { // correct behavior Assert.assertNotNull(e.getBindResponse()); Assert.assertEquals(SmppConstants.STATUS_INVSYSID, e.getBindResponse().getCommandStatus()); - } finally { - SmppSessionUtil.close(session); } } @@ -198,12 +199,10 @@ public void bindOK() throws Exception { SmppSessionConfiguration configuration = createDefaultConfiguration(); registerServerBindProcessor(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); // verify the session stuff... Assert.assertEquals(true, session.isBound()); - - SmppSessionUtil.close(session); } @Test @@ -213,7 +212,7 @@ public void enquireLinkWithGenericNackResponse() throws Exception { clearAllServerSessions(); // bind and get the simulator session - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); // register a generic nack will come next simulator0.setPduProcessor(new SmppSimulatorPduProcessor() { @@ -224,15 +223,12 @@ public boolean process(SmppSimulatorSessionHandler session, Channel channel, Pdu } }); + try { - try { - session.enquireLink(new EnquireLink(), 1000); - Assert.fail(); - } catch (GenericNackException e) { - // correct behavior - } - } finally { - SmppSessionUtil.close(session); + session.enquireLink(new EnquireLink(), 1000); + Assert.fail(); + } catch (GenericNackException e) { + // correct behavior } } @@ -243,7 +239,7 @@ public void enquireLinkWithARequestWithSameSequenceNumber() throws Exception { clearAllServerSessions(); // bind and get the simulator session - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); // create an enquire link response back -- we should skip it and wait for a response instead simulator0.setPduProcessor(new SmppSimulatorPduProcessor() { @@ -257,16 +253,12 @@ public boolean process(SmppSimulatorSessionHandler session, Channel channel, Pdu }); try { - try { - session.enquireLink(new EnquireLink(), 100); - Assert.fail(); - } catch (SmppTimeoutException e) { - // correct behavior (underlying cause MUST be a response timeout) + session.enquireLink(new EnquireLink(), 100); + Assert.fail(); + } catch (SmppTimeoutException e) { + // correct behavior (underlying cause MUST be a response timeout) // Assert.assertNotNull(e.getCause()); // Assert.assertEquals(ResponseTimeoutException.class, e.getCause().getClass()); - } - } finally { - SmppSessionUtil.close(session); } } @@ -277,7 +269,7 @@ public void multipleEnquireLinks() throws Exception { clearAllServerSessions(); // bind and get the simulator session - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); // create an enquire link response back -- we should skip it and wait for a response instead simulator0.setPduProcessor(new SmppSimulatorPduProcessor() { @@ -288,15 +280,11 @@ public boolean process(SmppSimulatorSessionHandler session, Channel channel, Pdu } }); - try { - session.enquireLink(new EnquireLink(), 100); - session.enquireLink(new EnquireLink(), 100); - session.enquireLink(new EnquireLink(), 100); - session.enquireLink(new EnquireLink(), 100); - session.enquireLink(new EnquireLink(), 100); - } finally { - SmppSessionUtil.close(session); - } + session.enquireLink(new EnquireLink(), 100); + session.enquireLink(new EnquireLink(), 100); + session.enquireLink(new EnquireLink(), 100); + session.enquireLink(new EnquireLink(), 100); + session.enquireLink(new EnquireLink(), 100); } @Test @@ -309,72 +297,67 @@ public void windowSizeBlocksAsyncRequest() throws Exception { configuration.setWindowSize(3); // bind and get the simulator session - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration); + session = bootstrap.bind(configuration); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); // make sure the processor is null simulator0.setPduProcessor(null); + // create the requests and response we plan on sending + EnquireLink el0 = new EnquireLink(); + EnquireLink el1 = new EnquireLink(); + EnquireLink el2 = new EnquireLink(); + EnquireLink el3 = new EnquireLink(); + el0.setSequenceNumber(0x7000); + el1.setSequenceNumber(0x4541); + el2.setSequenceNumber(0x5414); + el3.setSequenceNumber(0x2414); + EnquireLinkResp el0Resp = el0.createResponse(); + EnquireLinkResp el1Resp = el1.createResponse(); + EnquireLinkResp el2Resp = el2.createResponse(); + EnquireLinkResp el3Resp = el3.createResponse(); + + // this request should be permitted (with window size = 2) + WindowFuture future0 = session.sendRequestPdu(el0, 3000, true); + WindowFuture future1 = session.sendRequestPdu(el1, 3000, true); + WindowFuture future2 = session.sendRequestPdu(el2, 3000, true); + + Assert.assertEquals(3, session.getSendWindow().getSize()); + try { -// try { - // create the requests and response we plan on sending - EnquireLink el0 = new EnquireLink(); - EnquireLink el1 = new EnquireLink(); - EnquireLink el2 = new EnquireLink(); - EnquireLink el3 = new EnquireLink(); - el0.setSequenceNumber(0x7000); - el1.setSequenceNumber(0x4541); - el2.setSequenceNumber(0x5414); - el3.setSequenceNumber(0x2414); - EnquireLinkResp el0Resp = el0.createResponse(); - EnquireLinkResp el1Resp = el1.createResponse(); - EnquireLinkResp el2Resp = el2.createResponse(); - EnquireLinkResp el3Resp = el3.createResponse(); - - // this request should be permitted (with window size = 2) - WindowFuture future0 = session.sendRequestPdu(el0, 3000, true); - WindowFuture future1 = session.sendRequestPdu(el1, 3000, true); - WindowFuture future2 = session.sendRequestPdu(el2, 3000, true); - - Assert.assertEquals(3, session.getSendWindow().getSize()); - - try { - // window size of 3 is now filled up, this one should timeout - session.sendRequestPdu(el3, 100, true); - Assert.fail(); - } catch (SmppTimeoutException e) { - Assert.assertNotNull(e.getCause()); - Assert.assertEquals(OfferTimeoutException.class, e.getCause().getClass()); - } - - Assert.assertEquals(3, session.getSendWindow().getSize()); - - // now the smsc will send a response back to the second request - simulator0.sendPdu(el1Resp); - - // wait for the response to make its way back in - future1.await(); - - // there should be 1 slot free now in the window - Assert.assertEquals(2, session.getSendWindow().getSize()); - - // this request should now succeed - WindowFuture future3 = session.sendRequestPdu(el3, 3000, true); - - // send back responses for everything that's missing - simulator0.sendPdu(el2Resp); - simulator0.sendPdu(el0Resp); - simulator0.sendPdu(el3Resp); - - // make sure they all finished - future0.await(); - future1.await(); - future2.await(); - future3.await(); - - Assert.assertEquals(0, session.getSendWindow().getSize()); - } finally { - SmppSessionUtil.close(session); + // window size of 3 is now filled up, this one should timeout + session.sendRequestPdu(el3, 100, true); + Assert.fail(); + } catch (SmppTimeoutException e) { + Assert.assertNotNull(e.getCause()); + Assert.assertEquals(OfferTimeoutException.class, e.getCause().getClass()); } + + Assert.assertEquals(3, session.getSendWindow().getSize()); + + // now the smsc will send a response back to the second request + simulator0.sendPdu(el1Resp); + + // wait for the response to make its way back in + future1.await(); + + // there should be 1 slot free now in the window + Assert.assertEquals(2, session.getSendWindow().getSize()); + + // this request should now succeed + WindowFuture future3 = session.sendRequestPdu(el3, 3000, true); + + // send back responses for everything that's missing + simulator0.sendPdu(el2Resp); + simulator0.sendPdu(el0Resp); + simulator0.sendPdu(el3Resp); + + // make sure they all finished + future0.await(); + future1.await(); + future2.await(); + future3.await(); + + Assert.assertEquals(0, session.getSendWindow().getSize()); } @@ -386,39 +369,35 @@ public void cumulationOfMultipleByteBuffersToParsePdu() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); - - // send 1 byte - logger.debug("Sending 1 byte"); - simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("00")).sync(); - - // nothing received yet - Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); - - // send 14 more bytes - logger.debug("Sending 14 more bytes"); - simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("00001000000015000000000a342e")).sync(); - - // send 1 more byte - logger.debug("Sending 1 more bytes"); - simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("e7")).sync(); - - // we should have received a PDU request, poll for it - PduRequest pdu0 = sessionHandler.getReceivedPduRequests().poll(2000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(pdu0); - Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(171192039, pdu0.getSequenceNumber()); - } finally { - SmppSessionUtil.close(session); - } + Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); + + // send 1 byte + logger.debug("Sending 1 byte"); + simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("00")).sync(); + + // nothing received yet + Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); + + // send 14 more bytes + logger.debug("Sending 14 more bytes"); + simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("00001000000015000000000a342e")).sync(); + + // send 1 more byte + logger.debug("Sending 1 more bytes"); + simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("e7")).sync(); + + // we should have received a PDU request, poll for it + PduRequest pdu0 = sessionHandler.getReceivedPduRequests().poll(2000, TimeUnit.MILLISECONDS); + Assert.assertNotNull(pdu0); + Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(171192039, pdu0.getSequenceNumber()); } @Test @@ -429,7 +408,7 @@ public void routePduResponseToWaitingThread() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(new SmppSimulatorPduProcessor() { @@ -440,24 +419,20 @@ public boolean process(SmppSimulatorSessionHandler session, Channel channel, Pdu } }); - try { - // this should entirely succeed - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); + // this should entirely succeed + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); - EnquireLinkResp el0Resp = session.enquireLink(el0, 200); + EnquireLinkResp el0Resp = session.enquireLink(el0, 200); - // check everything is correct afterwards - Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, el0Resp.getCommandId()); - Assert.assertEquals(0, el0Resp.getCommandStatus()); - Assert.assertEquals(16, el0Resp.getCommandLength()); - Assert.assertEquals(0x1000, el0Resp.getSequenceNumber()); - Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); - Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); - Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); - } finally { - SmppSessionUtil.close(session); - } + // check everything is correct afterwards + Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, el0Resp.getCommandId()); + Assert.assertEquals(0, el0Resp.getCommandStatus()); + Assert.assertEquals(16, el0Resp.getCommandLength()); + Assert.assertEquals(0x1000, el0Resp.getSequenceNumber()); + Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); + Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); + Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); } @@ -469,33 +444,29 @@ public void receiveExpectedPduResponseViaAnAsynchronousSend() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); - EnquireLinkResp el0Resp = el0.createResponse(); - - // send asynchronously (no response will be received yet) - session.sendRequestPdu(el0, 2000, false); - - // send the response back -- this should be routed as n ExpectedPduResponse.... - simulator0.sendPdu(el0Resp); - - // we should have received a PDU response - PduAsyncResponse asyncpdu0 = sessionHandler.getReceivedExpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); - PduResponse pdu0 = asyncpdu0.getResponse(); - Assert.assertNotNull("Unable to receive expected PDU response -- perhaps it was routed incorrectly?", pdu0); - Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); - } finally { - SmppSessionUtil.close(session); - } + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); + EnquireLinkResp el0Resp = el0.createResponse(); + + // send asynchronously (no response will be received yet) + session.sendRequestPdu(el0, 2000, false); + + // send the response back -- this should be routed as n ExpectedPduResponse.... + simulator0.sendPdu(el0Resp); + + // we should have received a PDU response + PduAsyncResponse asyncpdu0 = sessionHandler.getReceivedExpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); + PduResponse pdu0 = asyncpdu0.getResponse(); + Assert.assertNotNull("Unable to receive expected PDU response -- perhaps it was routed incorrectly?", pdu0); + Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); } @@ -507,24 +478,20 @@ public void impossiblePDULengthCausesUnrecoverablePduException() throws Exceptio // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - // send some bytes that should trigger a MAJOR issue during parsing - simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("F000001000000015000000000a342ee7")).sync(); + // send some bytes that should trigger a MAJOR issue during parsing + simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("F000001000000015000000000a342ee7")).sync(); - // we should have received an Unrecoverable exception, poll for it - Throwable t = sessionHandler.getThrowables().poll(2000, TimeUnit.MILLISECONDS); - logger.debug("polled for exception: {}", t.getMessage()); + // we should have received an Unrecoverable exception, poll for it + Throwable t = sessionHandler.getThrowables().poll(2000, TimeUnit.MILLISECONDS); + logger.debug("polled for exception: {}", t.getMessage()); - Assert.assertNotNull(t); - Assert.assertTrue(t.getMessage(), (t instanceof UnrecoverablePduException)); - } finally { - SmppSessionUtil.close(session); - } + Assert.assertNotNull(t); + Assert.assertTrue(t.getMessage(), (t instanceof UnrecoverablePduException)); } /** THIS TEST NO LONGER APPLIES, WE SUPPORT ALL 32-BITS NOW @@ -565,35 +532,30 @@ public void noTerminatingZeroCausesRecoverablePduException() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - // send some bytes that should trigger a MAJOR issue during parsing - simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("0000001180000004000000000a342ee139")).sync(); + // send some bytes that should trigger a MAJOR issue during parsing + simulator0.getChannel().writeAndFlush(BufferHelper.createBuffer("0000001180000004000000000a342ee139")).sync(); - // we should have received an Unrecoverable exception, poll for it - Throwable t = sessionHandler.getThrowables().poll(2000, TimeUnit.MILLISECONDS); - logger.debug("polled for exception: {}", t.getMessage()); + // we should have received an Unrecoverable exception, poll for it + Throwable t = sessionHandler.getThrowables().poll(2000, TimeUnit.MILLISECONDS); + logger.debug("polled for exception: {}", t.getMessage()); - Assert.assertNotNull(t); - Assert.assertTrue(t.getMessage(), (t instanceof TerminatingNullByteNotFoundException)); - - TerminatingNullByteNotFoundException ex = (TerminatingNullByteNotFoundException)t; - // unwrap it - SubmitSmResp pdu0 = (SubmitSmResp)ex.getPartialPdu(); - Assert.assertEquals(17, pdu0.getCommandLength()); - Assert.assertEquals(SmppConstants.CMD_ID_SUBMIT_SM_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(171192033, pdu0.getSequenceNumber()); - Assert.assertEquals(true, pdu0.isResponse()); - Assert.assertEquals(null, pdu0.getMessageId()); + Assert.assertNotNull(t); + Assert.assertTrue(t.getMessage(), (t instanceof TerminatingNullByteNotFoundException)); - } finally { - SmppSessionUtil.close(session); - } + TerminatingNullByteNotFoundException ex = (TerminatingNullByteNotFoundException)t; + // unwrap it + SubmitSmResp pdu0 = (SubmitSmResp)ex.getPartialPdu(); + Assert.assertEquals(17, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_SUBMIT_SM_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(171192033, pdu0.getSequenceNumber()); + Assert.assertEquals(true, pdu0.isResponse()); + Assert.assertEquals(null, pdu0.getMessageId()); } @@ -605,7 +567,7 @@ public void closeDoesNotTriggerUnexpectedlyClosedEvent() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); @@ -626,7 +588,7 @@ public void unbindWithNoResponseDoesNotTriggerUnexpectedlyClosedEvent() throws E // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); @@ -646,7 +608,7 @@ public void unbindTriggeringRemoteToCloseDoesNotTriggerUnexpectedlyClosedEvent() // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(new SmppSimulatorPduProcessor() { @@ -672,7 +634,7 @@ public void multipleClosesWorkOK() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); @@ -705,7 +667,7 @@ public void remoteCloseDoesTriggerUnexpectedlyClosedEvent() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); @@ -736,35 +698,31 @@ public void sendRequestAndGetResponseOKWithResponseTypeNotMatchingOriginalReques // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - // NOTE: send enquireLink but receive an unbind resp (with same sequence #) - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); - - UnbindResp ubResp = new UnbindResp(); - ubResp.setSequenceNumber(el0.getSequenceNumber()); - - // schedule this response on the simulator - simulator0.addPduToWriteOnNextPduReceived(ubResp); - - // send asynchronously (no response will be received yet) - PduResponse pdu0 = session.sendRequestAndGetResponse(el0, 2000); - logger.debug("{}", pdu0); - - // NOTE: this internal method is OK to return an unexpected response - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); - Assert.assertEquals(true, pdu0.isResponse()); - } finally { - SmppSessionUtil.close(session); - } + // NOTE: send enquireLink but receive an unbind resp (with same sequence #) + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); + + UnbindResp ubResp = new UnbindResp(); + ubResp.setSequenceNumber(el0.getSequenceNumber()); + + // schedule this response on the simulator + simulator0.addPduToWriteOnNextPduReceived(ubResp); + + // send asynchronously (no response will be received yet) + PduResponse pdu0 = ((DefaultSmppSession)session).sendRequestAndGetResponse(el0, 2000); + logger.debug("{}", pdu0); + + // NOTE: this internal method is OK to return an unexpected response + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); + Assert.assertEquals(true, pdu0.isResponse()); } @Test @@ -775,41 +733,37 @@ public void enquireLinkFailsWithResponseTypeNotMatchingOriginalRequest() throws // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - // NOTE: send enquireLink but receive an unbind resp (with same sequence #) - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); - - UnbindResp ubResp = new UnbindResp(); - ubResp.setSequenceNumber(el0.getSequenceNumber()); - - // schedule this response on the simulator - simulator0.addPduToWriteOnNextPduReceived(ubResp); - - PduResponse pdu0 = null; - try { - EnquireLinkResp el0Resp = session.enquireLink(el0, 200); - Assert.fail(); - } catch (UnexpectedPduResponseException e) { - // correct behavior - pdu0 = e.getResponsePdu(); - } + // NOTE: send enquireLink but receive an unbind resp (with same sequence #) + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); - logger.debug("unexpected pdu: {}", pdu0); + UnbindResp ubResp = new UnbindResp(); + ubResp.setSequenceNumber(el0.getSequenceNumber()); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); - Assert.assertEquals(true, pdu0.isResponse()); - } finally { - SmppSessionUtil.close(session); + // schedule this response on the simulator + simulator0.addPduToWriteOnNextPduReceived(ubResp); + + PduResponse pdu0 = null; + try { + EnquireLinkResp el0Resp = session.enquireLink(el0, 200); + Assert.fail(); + } catch (UnexpectedPduResponseException e) { + // correct behavior + pdu0 = e.getResponsePdu(); } + + logger.debug("unexpected pdu: {}", pdu0); + + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); + Assert.assertEquals(true, pdu0.isResponse()); } @Test @@ -820,53 +774,49 @@ public void asynchronousPduRequestWithResponseTypeNotMatchingOriginalRequest() t // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - // NOTE: send enquireLink but receive an unbind resp (with same sequence #) - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); + // NOTE: send enquireLink but receive an unbind resp (with same sequence #) + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); - UnbindResp ubResp = new UnbindResp(); - ubResp.setSequenceNumber(el0.getSequenceNumber()); + UnbindResp ubResp = new UnbindResp(); + ubResp.setSequenceNumber(el0.getSequenceNumber()); - // schedule this response on the simulator - simulator0.addPduToWriteOnNextPduReceived(ubResp); + // schedule this response on the simulator + simulator0.addPduToWriteOnNextPduReceived(ubResp); - // send this PDU asynchronously - session.sendRequestPdu(el0, 200, false); + // send this PDU asynchronously + session.sendRequestPdu(el0, 200, false); - // wait for an expected pdu response - PduAsyncResponse asyncpdu0 = sessionHandler.getReceivedExpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); - logger.debug("{}", asyncpdu0); + // wait for an expected pdu response + PduAsyncResponse asyncpdu0 = sessionHandler.getReceivedExpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); + logger.debug("{}", asyncpdu0); - PduResponse pdu0 = asyncpdu0.getResponse(); + PduResponse pdu0 = asyncpdu0.getResponse(); - /** - PduResponse pdu0 = null; - try { - EnquireLinkResp el0Resp = session.enquireLink(el0, 200); - Assert.fail(); - } catch (UnexpectedPduResponseException e) { - // correct behavior - pdu0 = e.getResponsePdu(); - } + /* + PduResponse pdu0 = null; + try { + EnquireLinkResp el0Resp = session.enquireLink(el0, 200); + Assert.fail(); + } catch (UnexpectedPduResponseException e) { + // correct behavior + pdu0 = e.getResponsePdu(); + } - logger.debug("unexpected pdu: {}", pdu0); + logger.debug("unexpected pdu: {}", pdu0); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); - Assert.assertEquals(true, pdu0.isResponse()); - */ - } finally { - SmppSessionUtil.close(session); - } + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_UNBIND_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); + Assert.assertEquals(true, pdu0.isResponse()); + */ } @@ -878,42 +828,38 @@ public void receiveUnexpectedPduResponseAfterSenderThreadTimeoutWaiting() throws // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1001); - EnquireLinkResp el0Resp = el0.createResponse(); - - // send a request and wait for a response that never shows up - WindowFuture future = session.sendRequestPdu(el0, 50, true); - Assert.assertFalse(future.await()); - // a call to cancel() is usually done in sendRequestPduAndGetResponse - // but for this test we'll manually need to call it here - future.cancel(); - - Assert.assertEquals(WindowFuture.CALLER_WAITING_TIMEOUT, future.getCallerStateHint()); - - // send a response now after the caller would have timed out - simulator0.sendPdu(el0Resp); - - // we should have received an unexpected PDU response - Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); - Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); - PduResponse pdu0 = sessionHandler.getReceivedUnexpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); - Assert.assertNotNull("Unable to receive unexpected PDU response -- perhaps it was routed incorrectly?", pdu0); - Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(0x1001, pdu0.getSequenceNumber()); - - Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); - } finally { - SmppSessionUtil.close(session); - } + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1001); + EnquireLinkResp el0Resp = el0.createResponse(); + + // send a request and wait for a response that never shows up + WindowFuture future = session.sendRequestPdu(el0, 50, true); + Assert.assertFalse(future.await()); + // a call to cancel() is usually done in sendRequestPduAndGetResponse + // but for this test we'll manually need to call it here + future.cancel(); + + Assert.assertEquals(WindowFuture.CALLER_WAITING_TIMEOUT, future.getCallerStateHint()); + + // send a response now after the caller would have timed out + simulator0.sendPdu(el0Resp); + + // we should have received an unexpected PDU response + Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); + Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); + PduResponse pdu0 = sessionHandler.getReceivedUnexpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); + Assert.assertNotNull("Unable to receive unexpected PDU response -- perhaps it was routed incorrectly?", pdu0); + Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(0x1001, pdu0.getSequenceNumber()); + + Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); } @Test @@ -924,33 +870,29 @@ public void receiveUnexpectedPduResponse() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); - try { - EnquireLink el0 = new EnquireLink(); - el0.setSequenceNumber(0x1000); - EnquireLinkResp el0Resp = el0.createResponse(); - - // send a response to a request that was NEVER sent - simulator0.sendPdu(el0Resp); - - // we should have received a PDU response - PduResponse pdu0 = sessionHandler.getReceivedUnexpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); - Assert.assertNotNull("Unable to receive unexpected PDU response -- perhaps it was routed incorrectly?", pdu0); - Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); - Assert.assertEquals(0, pdu0.getCommandStatus()); - Assert.assertEquals(16, pdu0.getCommandLength()); - Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); - - Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); - Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); - Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); - } finally { - SmppSessionUtil.close(session); - } + EnquireLink el0 = new EnquireLink(); + el0.setSequenceNumber(0x1000); + EnquireLinkResp el0Resp = el0.createResponse(); + + // send a response to a request that was NEVER sent + simulator0.sendPdu(el0Resp); + + // we should have received a PDU response + PduResponse pdu0 = sessionHandler.getReceivedUnexpectedPduResponses().poll(1000, TimeUnit.MILLISECONDS); + Assert.assertNotNull("Unable to receive unexpected PDU response -- perhaps it was routed incorrectly?", pdu0); + Assert.assertEquals(SmppConstants.CMD_ID_ENQUIRE_LINK_RESP, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(0x1000, pdu0.getSequenceNumber()); + + Assert.assertEquals(0, sessionHandler.getReceivedPduRequests().size()); + Assert.assertEquals(0, sessionHandler.getReceivedExpectedPduResponses().size()); + Assert.assertEquals(0, sessionHandler.getReceivedUnexpectedPduResponses().size()); } @@ -962,26 +904,22 @@ public void synchronousSendButNeverGetResponse() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); try { - try { - session.enquireLink(new EnquireLink(), 100); - // request should timeout - Assert.fail(); - } catch (SmppTimeoutException e) { - // correct behavior - } - - // with a "synchronous" type of send, after a timeout, the request - // should have been cancelled - Assert.assertEquals(0, session.getSendWindow().getSize()); - } finally { - SmppSessionUtil.close(session); + session.enquireLink(new EnquireLink(), 100); + // request should timeout + Assert.fail(); + } catch (SmppTimeoutException e) { + // correct behavior } + + // with a "synchronous" type of send, after a timeout, the request + // should have been cancelled + Assert.assertEquals(0, session.getSendWindow().getSize()); } @Test @@ -992,7 +930,7 @@ public void shutdown() throws Exception { // bind and get the simulator session PollableSmppSessionHandler sessionHandler = new PollableSmppSessionHandler(); - DefaultSmppSession session = (DefaultSmppSession)bootstrap.bind(configuration, sessionHandler); + session = bootstrap.bind(configuration, sessionHandler); SmppSimulatorSessionHandler simulator0 = server.pollNextSession(1000); simulator0.setPduProcessor(null); @@ -1008,4 +946,58 @@ public void shutdown() throws Exception { Assert.assertEquals(0, session.getSendWindow().getSize()); } + @Test + public void bindToSpecificHost() throws Exception { + final String expectedBindHost = "127.0.0.1"; + + SmppSessionConfiguration configuration = createDefaultConfiguration(); + configuration.setConnectTimeout(120000); + configuration.setClientBindHost(expectedBindHost); + registerServerBindProcessor(); + + session = bootstrap.bind(configuration); + + // verify the session stuff... + Assert.assertEquals(true, session.isBound()); + // verify that the local address we used to connect is the expected + DefaultSmppSession dss = (DefaultSmppSession) session; + InetSocketAddress localAddress = (InetSocketAddress) dss.getChannel().localAddress(); + + Assert.assertEquals(expectedBindHost, localAddress.getAddress().getHostAddress()); + } + + @Test + public void bindToSpecificLocalAddress() throws Exception { + final String expectedBindHost = "127.0.0.1"; + // try to use a random local port and pray that it hasn't been used earlier. + final int expectedBindPort = ThreadLocalRandom.current().nextInt(PORT+1, 65535); + + SmppSessionConfiguration configuration = createDefaultConfiguration(); + configuration.setConnectTimeout(120000); + configuration.setClientBindHost(expectedBindHost); + configuration.setClientBindPort(expectedBindPort); + registerServerBindProcessor(); + + try { + session = bootstrap.bind(configuration); + } catch (SmppChannelConnectException e) { + // if we have bound to the expectedLocalPort in a previous run (or test), then the TCP connection may + // very well be still open, in a TIME_WAIT status. In that case there's not much to do, other than + // ignoring this test + Assume.assumeFalse( + "Error trying to bind to local port. Address already in use. Probably it is still in TIME_WAIT?", + e.getCause() != null && + e.getCause().getCause() != null && + e.getCause().getCause() instanceof BindException); + throw e; + } + + // verify the session stuff... + Assert.assertEquals(true, session.isBound()); + // verify that the local address we used to connect is the expected + DefaultSmppSession dss = (DefaultSmppSession) session; + InetSocketAddress localAddress = (InetSocketAddress) dss.getChannel().localAddress(); + Assert.assertEquals(expectedBindHost, localAddress.getAddress().getHostAddress()); + Assert.assertEquals(expectedBindPort, localAddress.getPort()); + } } diff --git a/src/test/java/com/cloudhopper/smpp/ssl/SslSessionTest.java b/src/test/java/com/cloudhopper/smpp/ssl/SslSessionTest.java index f757cb7d..91d51d0c 100644 --- a/src/test/java/com/cloudhopper/smpp/ssl/SslSessionTest.java +++ b/src/test/java/com/cloudhopper/smpp/ssl/SslSessionTest.java @@ -113,8 +113,8 @@ private SmppSessionConfiguration createClientConfigurationNoSSL() { configuration.setType(SmppBindType.TRANSCEIVER); configuration.setHost("localhost"); configuration.setPort(PORT); - configuration.setConnectTimeout(200); - configuration.setBindTimeout(200); + configuration.setConnectTimeout(2000); + configuration.setBindTimeout(2000); configuration.setSystemId(SYSTEMID); configuration.setPassword(PASSWORD); configuration.getLoggingOptions().setLogBytes(true); @@ -238,7 +238,7 @@ public void bindOverSSL() throws Exception { try { // this should actually work SmppSession session0 = client0.bind(sessionConfig0); - Thread.sleep(200); + Thread.sleep(2000); Assert.assertEquals(1, serverHandler.sessions.size()); Assert.assertEquals(1, server0.getChannels().size()); @@ -250,7 +250,7 @@ public void bindOverSSL() throws Exception { Assert.assertEquals(SmppSession.Type.CLIENT, serverSession0.getRemoteType()); serverSession0.close(); - Thread.sleep(200); + Thread.sleep(2000); Assert.assertEquals(0, serverHandler.sessions.size()); Assert.assertEquals(0, server0.getChannels().size()); Assert.assertEquals(false, serverSession0.isBound()); @@ -270,7 +270,7 @@ public void enquireLinkOverSSL() throws Exception { try { SmppSession session0 = client0.bind(sessionConfig0); - Thread.sleep(100); + Thread.sleep(1000); // send encrypted enquire link; receive encrypted response. EnquireLinkResp enquireLinkResp = session0.enquireLink(new EnquireLink(), 1000); diff --git a/src/test/java/com/cloudhopper/smpp/transcoder/PduDecoderTest.java b/src/test/java/com/cloudhopper/smpp/transcoder/PduDecoderTest.java index 1a748461..e9f166b0 100644 --- a/src/test/java/com/cloudhopper/smpp/transcoder/PduDecoderTest.java +++ b/src/test/java/com/cloudhopper/smpp/transcoder/PduDecoderTest.java @@ -1392,4 +1392,61 @@ public void decodeQuerySmResp() throws Exception { Assert.assertEquals(0, buffer.readableBytes()); } + + @Test + public void decodeReplaceSm() throws Exception { + ByteBuf buffer = BufferHelper.createBuffer("00000050000000070000000000004FE86D73672D313233343500010135353532373130303030003135303230333034303530363730382B00303130323033303430353036303030520001020474657874"); + + ReplaceSm pdu0 = (ReplaceSm)transcoder.decode(buffer); + + Assert.assertEquals(80, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_REPLACE_SM, pdu0.getCommandId()); + Assert.assertEquals(0, pdu0.getCommandStatus()); + Assert.assertEquals(20456, pdu0.getSequenceNumber()); + Assert.assertEquals(true, pdu0.isRequest()); + Assert.assertEquals(0x01, pdu0.getSourceAddress().getTon()); + Assert.assertEquals(0x01, pdu0.getSourceAddress().getNpi()); + Assert.assertEquals("5552710000", pdu0.getSourceAddress().getAddress()); + Assert.assertEquals("150203040506708+", pdu0.getScheduleDeliveryTime()); + Assert.assertEquals("010203040506000R", pdu0.getValidityPeriod()); + Assert.assertEquals((byte)0x01, pdu0.getRegisteredDelivery()); + Assert.assertEquals((byte)0x02, pdu0.getDefaultMsgId()); + Assert.assertEquals("text", new String(pdu0.getShortMessage(), "ISO-8859-1")); + + Assert.assertEquals(null, pdu0.getOptionalParameters()); + + Assert.assertEquals(0, buffer.readableBytes()); + } + + @Test + public void decodeReplaceSmResp() throws Exception { + ByteBuf buffer = BufferHelper.createBuffer("00000010800000070000000200004FE8"); + + ReplaceSmResp pdu0 = (ReplaceSmResp)transcoder.decode(buffer); + + Assert.assertEquals(16, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_REPLACE_SM_RESP, pdu0.getCommandId()); + Assert.assertEquals(true, pdu0.isResponse()); + Assert.assertEquals(2, pdu0.getCommandStatus()); + Assert.assertEquals(20456, pdu0.getSequenceNumber()); + + Assert.assertEquals(0, buffer.readableBytes()); + } + + @Test + public void decodeAlertNotification() throws Exception { + ByteBuf buffer = BufferHelper.createBuffer("00000025000001020000000200004FE8010135353532373130303030000101343034303400"); + + AlertNotification pdu0 = (AlertNotification)transcoder.decode(buffer); + + Assert.assertEquals(37, pdu0.getCommandLength()); + Assert.assertEquals(SmppConstants.CMD_ID_ALERT_NOTIFICATION, pdu0.getCommandId()); + Assert.assertEquals(false, pdu0.isResponse()); + Assert.assertEquals(2, pdu0.getCommandStatus()); + Assert.assertEquals(20456, pdu0.getSequenceNumber()); + Assert.assertEquals("5552710000", pdu0.getSourceAddress().getAddress()); + Assert.assertEquals("40404", pdu0.getEsmeAddress().getAddress()); + + Assert.assertEquals(0, buffer.readableBytes()); + } } diff --git a/src/test/java/com/cloudhopper/smpp/transcoder/PduEncoderTest.java b/src/test/java/com/cloudhopper/smpp/transcoder/PduEncoderTest.java index d75b264a..2604e922 100644 --- a/src/test/java/com/cloudhopper/smpp/transcoder/PduEncoderTest.java +++ b/src/test/java/com/cloudhopper/smpp/transcoder/PduEncoderTest.java @@ -542,4 +542,56 @@ public void encodeQueryRespSm() throws Exception { // logger.debug("{}", HexUtil.toHexString(BufferHelper.createByteArray(buffer))); Assert.assertArrayEquals(HexUtil.toByteArray("00000019800000030000000000004FE8313233343500000600"), BufferHelper.createByteArray(buffer)); } + + + @Test + public void encodeReplaceSM() throws Exception { + ReplaceSm pdu0 = new ReplaceSm(); + + pdu0.setSequenceNumber(20456); + pdu0.setMessageId("msg-12345"); + pdu0.setSourceAddress(new Address((byte)0x01, (byte)0x01, "5552710000")); + pdu0.setScheduleDeliveryTime("150203040506708+"); + pdu0.setValidityPeriod("010203040506000R"); + pdu0.setRegisteredDelivery((byte)0x01); + pdu0.setDefaultMsgId((byte)0x02); + pdu0.setShortMessage("text".getBytes("ISO-8859-1")); + + ByteBuf buffer = transcoder.encode(pdu0); + + String expectedHex = "00000050000000070000000000004FE86D73672D313233343500010135353532373130303030003135303230333034303530363730382B00303130323033303430353036303030520001020474657874"; + String actualHex = HexUtil.toHexString(BufferHelper.createByteArray(buffer)).toUpperCase(); + + Assert.assertEquals(expectedHex, actualHex); + } + + @Test + public void encodeReplaceSmResp() throws Exception { + ReplaceSmResp pdu0 = new ReplaceSmResp(); + + pdu0.setSequenceNumber(20456); + pdu0.setCommandStatus( 2 ); + + ByteBuf buffer = transcoder.encode(pdu0); + + String expectedHex = "00000010800000070000000200004FE8"; + String actualHex = HexUtil.toHexString(BufferHelper.createByteArray(buffer)).toUpperCase(); + Assert.assertEquals(expectedHex, actualHex); + } + + @Test + public void encodeAlertNotification() throws Exception { + AlertNotification pdu0 = new AlertNotification(); + + pdu0.setSequenceNumber(20456); + pdu0.setCommandStatus( 2 ); + pdu0.setSourceAddress( new Address((byte)0x01, (byte)0x01, "5552710000") ); + pdu0.setEsmeAddress( new Address((byte)0x01, (byte)0x01, "40404") ); + + ByteBuf buffer = transcoder.encode(pdu0); + + String expectedHex = "00000025000001020000000200004FE8010135353532373130303030000101343034303400"; + String actualHex = HexUtil.toHexString(BufferHelper.createByteArray(buffer)).toUpperCase(); + Assert.assertEquals(expectedHex, actualHex); + } }