SMPP(3pm)

πŸ“¦
version 1.19 Β· 2 versions
  • 1.19 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 1.19 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

✨ SMPP(3pm) ✨

User Contributed Perl Documentation

okay bestie, welcome to the SMPP(3pm) page! This is Net::SMPP, and it's giving pure Perl implementation of SMPP 3.4 over TCP. Let's get into it πŸ’…


πŸŽ€ NAME πŸŽ€

Net::SMPP β€” pure Perl implementation of SMPP 3.4 over TCP.


πŸ¦‹ SYNOPSIS πŸ¦‹

         use Net::SMPP;
         $smpp = Net::SMPP->new_transceiver($host, port=>$port,
                               system_id => 'yourusername',
                               password  => 'secret',
                               ) or die;

🌸 DESCRIPTION 🌸

So Net::SMPP implements the Short Message Peer to Peer protocol. It's frequently used to pass short messages between mobile operators implementing short message service (SMS). This is applicable to both european GSM and american CDMA/TDMA systems. πŸ’–

Heads up though: this documentation is not meant to be a complete reference to the SMPP protocol. Use the SMPP specification documents (see the references section) to get exact operation and parameter names and their meaning. You may also need to grab site-specific documentation about the remote end and any protocol extensions it supports or demands before you start a project. This document follows the convention of spelling parameter names exactly as they appear in the SMPP v3.4 documentation. SMPP v4.0 support also follows the respective documentation, except where v4.0 usage is in conflict with v3.4 usage β€” in that case the latter prevails (in practise I believe no such conflicts remain in the madule at present). For a complete list of error code and optional parameter enumerations, the reader is encouraged to consult the source code or the SMPP speciofications.

Despite its name, the SMPP protocol defines a client (ESME) and a server (often called SMSC in the mobile operator world). The client usually initiates the TCP connection and does bind to log in. After binding, a series of request response pairs, called PDUs (protocol data units), is exchanged. A request can be initiated by either end (hence "peer-to-peer"?) and the other end responds. Requests are numbered with a sequence number and each response has a corresponding sequence number. This allows several requests to be pending at the same time. Conceptually this is similar to IMAP or LDAP message IDs. Usually the $smpp object maintains the sequence numbers by itself and the programmer need not concern himself with their exact values, but should a need to override them arise, the seq argument can be supplied to any request or response method.

Normally this module operates in synchronous mode, meaning that a method that sends a request will also block until it gets the corresponding response. Internal command used for waiting for response is:

           $resp_pdu = $smpp->wait_pdu($cmd_id, $seq);

If, while waiting for a particular response, other PDUs are received, they are either handled by handlers (set up by constructor) or discarded. Both command code and sequence number must match. Typically a handler for enquire command is set up while all other commands are silently dropped. This practise may not be very suitable for transceiver mode of operation and certainly is not suitable for implementing a SMSC. 😭

Synchronous operation makes it impossible to interleave SMPP operations, so it should be regarded as a simplified programming model for simple tasks. Anyone requiring more advanced control has to use the asynchronous mode and take up the burden of understanding and implementing more of the message flow logic in his own application.

In synchronous mode, request PDU methods return a Net::SMPP::PDU object representing the response, if all went well protocolwise, or undef if there was a protocol level error. If undef was returned, the reason for the failure can be extracted from ${*$smpp}{smpperror} and ${*$smpp}{smpperrorcode} (actual codes are undocumented at the moment, but are guaranteed not to change) variables and the global variable $!. These variables are meaningless if anything else than undef was returned. The response itself may be an error response if there was an application level error in the remote end. In this case the application level error can be determined from the $pdu->{status} field. Some responses also have optional parameters that further clarify the failure β€” see documentation for each operation.

If a protocol level error happens, probably the only safe action is to destroy the connection object (e.g. undef $smpp). If an application level error happens, then depending on how the remote end has been implemented it may be possible to continue operation. 🫢

Module can also be used asynchronously by specifying async=>1 to the constructor. In this mode command methods return immediately with the sequence number of the PDU and user should poll for any responses using:

           $pdu = $smpp->wait_pdu($cmd_id, $seq);

Typically wait_pdu() is used to wait for a response, but if wait_pdu() is used to wait for a command, the caller should generate appropriate response.

If caller wants to receive next available PDU, he can call:

           $pdu = $smpp->read_pdu();

which will block until a PDU is received from the stream. The caller would then have to check if the PDU is a response or a request and take appropriate action. The smsc.pl example program supplied with this distribution demonstrates a possible framework for handling both requests and responses. ✨

If the caller does not want to block on wait_pdu() or read_pdu(), he must use select() to determine if the socket is readable (*** what if SSL layer gets inserted?). Even if the socket selects for reading, there may not be enough data to complete the PDU, so the call may still block. Currently there is no reliable mechanism for avoiding this. If this bothers you, you may consider allocating a separate process for each connection so that blocking does not matter, or you may set up some sort of timeout (see perlipc(1) man page) or you may rewrite this module and contribute patches. πŸ’€

Response methods always return the sequence number, irrespective of synchronous or asynchronous mode, or undef if an error happened.


πŸ‘‘ CONSTRUCTORS πŸ‘‘

new()

Do not call. Has special internal meaning during accepting connections from listening socket.

new_connect()

Create a new SMPP client object and open conncetion to SMSC host:

               $smpp = Net::SMPP->new_connect($host,
                  system_id => 'username',   # usually needed (default '')
                  password => 'secret',      # usually needed (default '')
                  system_type => '',         # default ok, often not needed
                  interface_version => 0x34, # default ok, almost never needed
                  addr_ton => 0x00,          # default ok, type of number unknwn
                  addr_npi => 0x00,          # default ok, number plan indicator
                  address_range => '',       # default ok, regex matching nmbrs
                  ) or die;

Usually this constructor is not called directly. Use new_transceiver(), new_transmitter(), and new_receiver() instead.

new_transceiver(), new_transmitter(), new_receiver()

These constructors first construct the object using new_connect() and then bind using given type of bind request. See bind family of methods, below. These constructors are usually used to implement ESME type functionality.

new_listen('localhost', port=>2251)

Create new SMPP server object and open socket to listen on given port. This constructor is usually used to implement a SMSC.


πŸ’Œ REQUEST PDU METHODS πŸ’Œ

Each request PDU method constructs a PDU from list of arguments supplied and sends it to the wire.

If async mode has been enabled (by specifying "async=>1" in the constructor or as an argument to the method), the methods return sequence number of the PDU just sent. This number can be later used to match up the response, like this:

           $seq = $smpp->query_sm(message_id => $msg_id) or die;
           ...
           $resp_pdu = $smpp->wait_pdu(Net::SMPP::CMD_query_sm_resp, $seq)
              or die;
           die "Response indicated error: " . $resp_pdu->explain_status()
              if $resp_pdu->status;

If async mode is not enabled (i.e. "async=>1" was not specified neither in constructor nor the method), the method will wait for the corresponding response and return Net::SMPP::PDU object representing that response. The application should check the outcome of the operation from the status field of the response PDU, like this:

           $resp_pdu = $smpp->query_sm(message_id => $msg_id) or die;
           die "Response indicated error: " . $resp_pdu->explain_status()
              if $resp_pdu->status;

All request PDU methods optionally take "seq=>123" argument that allows explicit specification of the sequence number. The default is to increment internally stored sequence number by one and use that.

Most PDUs have mandatory parameters and optional parameters. If mandatory parameter is not supplied, it is inherited from the smpp object. This means that the parameter can either be set as an argument to the constructor or it is inherited from built-in defaults in the innards of Net::SMPP (see "Default" table from line 217 onwards). Some mandatory parameters can not be defaulted β€” if they are missing a die results. In descriptions below, defaultable mandatory parameters are show with the default value and comment indicating that its defaultable.

Optional parameters can be supplied to all PDUs (although the SMPP spec does not allow optional parameters for some PDUs, the module does not check for this) by listing them in the order that they should be appended to the end of the PDU. Optional parameters can not be defaulted β€” if the parameter is not supplied, it simply is not included in the PDU. Optional parameters are not supported by previous versions of the SMPP protocol (up to and including 3.3). Applications wishing to be downwards compatible should not make use of optional parameters.

Standard optional parameters can be supplied by their name (see "param_tab" in the Net::SMPP source code, around line 345, for list of known optional parameters), but the programmer still needs to supply the value of the parameter in the expected format (one often has to use pack to construct the value). Consult SMPP specifications for the correct format.

It is possible to supply arbitrary unsupported optional parameters by simply supplying the parameter tag as a decimal number. Consult your site dependent documentation to figure out the correct tags and to determine the correct format for the value.

Okay so when optional parameters come back in response PDUs, they get decoded and made available under both the numeric tag and the symbolic tag, if we know it. πŸ’– For example, the delivery_failure_reson of data_sm_resp can be accessed both as $resp->{delivery_failure_reson} and as $resp->{1061}. The application needs to interpret the formatting of optional parameters itself. The module always assumes they're strings, while often they're actually interpreted as integers. 😭 So consult the SMPP specifications and site-dependent documentation for the correct format, and use unpack to get the numbers.

If unknown nonnumeric parameter tags are supplied, a warning is issued and the parameter is skipped. 🚫

In general, the Net::SMPP module does not enforce SMPP specifications. This means it will happily accept too-long or too-short values for mandatory or optional parameters. Also, the internal formatting of the parameter values is not checked in any way. The programmer should consult the SMPP specifications to learn the correct length and format of each mandatory and optional parameter. πŸ“š

Similarly, if the remote end returns incorrect PDUs and Net::SMPP is able to parse them (usually because the length fields match), then Net::SMPP will not perform any further checks. This means that some fields may be longer than allowed for in the specifications. πŸ’€

I opted to leave the checks out at this stage because I needed a flexible module that allowed me to explore even nonconformant SMSC implementations. If the lack of sanity checks bothers you, formulate such checks and submit me a patch. Ideally, one could at construction time supply an argument like strict=>1 to enable the sanity checks. ✨


alert_notification() (4.12.1, p.108) πŸ’–

Sent by SMSC to ESME when a particular mobile subscriber has become available. source_addr specifies which mobile subscriber. esme_addr specifies which ESME the message is destined to. Alert notifications can arise if the delivery pending flag had been set for the subscriber from a previous data_sm operation. πŸ“²

There is no response PDU. Nope, nada. πŸ™…β€β™€οΈ

               $smpp->alert_notification(
                                         source_addr_ton => 0x00, # default ok
                                         source_addr_npi => 0x00, # default ok
                                         source_addr => '',       # default ok
                                         esme_addr_ton => 0x00,   # default ok
                                         esme_addr_npi => 0x00,   # default ok
                                         esme_addr => $esme_addr, # mandatory
                                         ) or die;

bind_transceiver() (4.1.5, p.51), bind_transmitter() (4.1.1, p.46), bind_receiver() (4.1.3, p.48) 🫢

The bind family of methods is used to authenticate the client (ESME) to the server (SMSC). Usually bind happens as part of the corresponding constructor new_transceiver(), new_transmitter(), or new_receiver(), so these methods are rarely called directly. They take a plethora of options, which are largely the same as the options taken by the constructors and can safely be defaulted. πŸ”

               $smpp->bind_transceiver(
                  system_id => 'username',   # usually needed (default '')
                  password => 'secret',      # usually needed (default '')
                  system_type => '',         # default ok, often not needed
                  interface_version => 0x34, # default ok, almost never needed
                  addr_ton => 0x00,          # default ok, type of number unkwn
                  addr_npi => 0x00,          # default ok, number plan indic.
                  address_range => '',       # default ok, regex matching tels
                  ) or die;

Typically it would be called like:

               $resp_pdu = $smpp->bind_transceiver(system_id => 'username',
                                                   password => 'secret') or die;
               die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

or to inform SMSC that you can handle all Spanish numbers:

               $resp_pdu = $smpp->bind_transceiver(system_id => 'username',
                                                   password => 'secret',
                                                   address_range => '^\+?34')
                  or die;
               die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

cancel_sm() (4.9.1, p.98) πŸ—‘οΈ

Issued by ESME to cancel one or more short messages. Two principal modes of operation are:

  1. if message_id is supplied, other fields can be left at defaults. This mode deletes just one message.
  2. if message_id is not supplied (or is empty string), then the other fields must be supplied and all messages matching the criteria reflected by the other fields are deleted.
               $smpp->cancel_sm(
                                service_type => '',      # default ok
                                message_id => '', # default ok, but often given
                                source_addr_ton => 0x00, # default ok
                                source_addr_npi => 0x00, # default ok
                                source_addr => '',       # default ok
                                dest_addr_ton => 0x00,   # default ok
                                dest_addr_npi => 0x00,   # default ok
                                destination_addr => '',  # default ok
                              ) or die;

For example:

              $resp_pdu = $smpp->submit_sm(destination_addr => '+447799658372',
                                            short_message => 'test message')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;
              $msg_id = $resp_pdu->{message_id};

              $resp_pdu = $smpp->query_sm(message_id => $msg_id) or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;
              print "Message state is $resp_pdu->{message_state}\n";

              $resp_pdu = $smpp->replace_sm(message_id => $msg_id,
                                            short_message => 'another test')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

              $resp_pdu = $smpp->cancel_sm(message_id => $msg_id) or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

data_sm() (4.7.1, p.87) πŸ“¦

Newer alternative to submit_sm and deliver_sm. In addition to that, data_sm can be used to pass special messages such as SMSC Delivery Receipt, SME Delivery Acknowledgement, SME Manual/User Acknowledgement, Intermediate notification. ✨

Unlike submit_sm and deliver_sm, the short_message parameter is not mandatory. Never-the-less, the optional parameter message_payload must be supplied for things to work correctly. πŸ’‘

               $smpp->data_sm(
                              service_type => '',      # default ok
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              dest_addr_ton => 0x00,   # default ok
                              dest_addr_npi => 0x00,   # default ok
                              destination_addr => $tel,  # mandatory
                              esm_class => 0x00,       # default ok
                              registered_delivery => 0x00, #default ok
                              data_coding => 0x00,     # default ok
                              message_payload => 'test msg', # opt, but needed
                              ) or die;

For example:

              $resp_pdu = $smpp->data_sm(destination_addr => '+447799658372',
                                         message_payload => 'test message')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

deliver_sm() (4.6.1, p.79) πŸ“¨

Issued by SMSC to send a message to an ESME. Furthermore, SMSC can transfer the following special messages: 1. SMSC delivery receipt, 2. SME delivery acknowledgement, 3. SME Manual/User Acknowledgement, 4. Intermediate notification. These messages are sent in response to an SMS message whose registered_delivery parameter requested them. πŸ“¬

If message data is longer than 254 bytes, the optional parameter message_payload should be used to store the message and short_message should be set to empty string. N.B. although the protocol has a mechanism for sending fairly large messages, the underlying mobile network usually does not support very large messages. GSM supports only up to 160 characters, other systems 128 or even just 100 characters. πŸ“

               $smpp->deliver_sm(
                              service_type => '',      # default ok
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              dest_addr_ton => 0x00,   # default ok
                              dest_addr_npi => 0x00,   # default ok
                              destination_addr => $t,  # mandatory
                              esm_class => 0x00,       # default ok
                              protocol_id => 0x00,     # default ok on CDMA,TDMA
                                                       #   on GSM value needed
                              priority_flag => 0x00,   # default ok
                              schedule_delivery_time => '', # default ok
                              validity_period => '',        # default ok
                              registered_delivery => 0x00,  # default ok
                              replace_if_present_flag => 0x00, # default ok
                              data_coding => 0x00,     # default ok
                              sm_default_msg_id => 0x00,    # default ok
                              short_message => '',     # default ok, but
                                                       #   usually supplied
                              ) or die;

For example:

              $resp_pdu = $smpp->deliver_sm(destination_addr => '+447799658372',
                                            short_message => 'test message')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

Used by either ESME or SMSC to "ping" the other side. Takes no parameters. Just a little "u up?" vibe. πŸ˜‰

               $smpp->enquire_link() or die;

outbind() (4.1.7, p.54, 2.2.1, p.16)

Okay so bestie, outbind is the SMSC's way of signaling the ESME to originate a bind_receiver request to the SMSC. The system_id and password authenticate the SMSC to the ESME. The outbind is used when the SMSC initiates the TCP session and needs to trigger the ESME to perform a bind_receiver. It is not needed if the ESME initiates the TCP connection (e.g. sec 2.7.1, p.27). πŸ’…

There is literally not a response PDU for outbind β€” instead the ESME is expected to issue bind_receiver. Periodt.

               $smpp->outbind(
                              system_id => '',  # default ok, but usually given
                              password => '',   # default ok, but usually given
                              ) or die;

query_sm() (4.8.1, p.95)

Used by the ESME to query the status of a submitted short message. Both message_id and source_addr must match β€” if source_addr was defaulted to NULL during submit, it must be NULL here, too. Peep the example near cancel_sm, bestie. ✨

               $smpp->query_sm(
                              message_id => $msg_id,   # mandatory
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              ) or die;

replace_sm() (4.10.1, p.102)

Used by the ESME to replace a previously submitted short message, provided it is still pending delivery. Both message_id and source_addr must match β€” if source_addr was defaulted to NULL during submit, it must be NULL here, too. See example near cancel_sm. πŸŽ€

               $smpp->replace_sm(
                              message_id => $msg_id,   # mandatory
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              schedule_delivery_time => '', # default ok
                              validity_period => '',        # default ok
                              registered_delivery => 0x00,  # default ok
                              sm_default_msg_id => 0x00,    # default ok
                              short_message => '',     # default ok, but
                                                       #   usually supplied
                              ) or die;

submit_sm() (4.4.1, p.59)

Used by the ESME to submit a short message to the SMSC for onward transmission to the specified short message entity (SME). The submit_sm does not support the transaction message mode. No cap. 🚫

If message data is longer than 254 bytes, the optional parameter message_payload should be used to store the message and short_message should be set to empty string.

N.B. although the protocol has a mechanism for sending fairly large messages, the underlying mobile network usually does not support very large messages. GSM supports only up to 160 characters. πŸ’€
               $smpp->submit_sm(
                              service_type => '',      # default ok
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              dest_addr_ton => 0x00,   # default ok
                              dest_addr_npi => 0x00,   # default ok
                              destination_addr => $t,  # mandatory
                              esm_class => 0x00,       # default ok
                              protocol_id => 0x00,     # default ok on CDMA,TDMA
                                                       #   on GSM value needed
                              priority_flag => 0x00,   # default ok
                              schedule_delivery_time => '', # default ok
                              validity_period => '',        # default ok
                              registered_delivery => 0x00,  # default ok
                              replace_if_present_flag => 0x00, # default ok
                              data_coding => 0x00,     # default ok
                              sm_default_msg_id => 0x00,    # default ok
                              short_message => '',     # default ok, but
                                                       #   usually supplied
                              ) or die;

For example:

              $resp_pdu = $smpp->submit_sm(destination_addr => '+447799658372',
                                            short_message => 'test message')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

Or:

              $resp_pdu = $smpp->submit_sm(destination_addr => '+447799658372',
                                           short_message => '',
                                           message_payload => 'a'x500) or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

submit_multi() (4.5.1, p.69)

Used by the ESME to submit a short message to the SMSC for onward transmission to the specified short message entities (SMEs). This command is especially destined for multiple recipients. Group chat energy, fr fr. πŸ‘―β€β™€οΈ

If message data is longer than 254 bytes, the optional parameter message_payload should be used to store the message and short_message should be set to empty string.

N.B. although the protocol has a mechanism for sending fairly large messages, the underlying mobile network usually does not support very large messages. GSM supports only up to 160 characters. πŸ’€
               $smpp->submit_multi(
                              service_type => '',      # default ok
                              source_addr_ton => 0x00, # default ok
                              source_addr_npi => 0x00, # default ok
                              source_addr => '',       # default ok
                              dest_flag =>             # default ok
                                   [ MULTIDESTFLAG_SME_Address,
                                     MULTIDESTFLAG_dist_list, ... ],
                              dest_addr_ton =>         # default ok
                                   [ 0x00, 0x00, ... ],
                              dest_addr_npi =>         # default ok
                                   [ 0x00, 0x00, ... ],
                              destination_addr =>      # mandatory
                                   [ $t1, $t2, ... ],
                              esm_class => 0x00,       # default ok
                              protocol_id => 0x00,     # default ok on CDMA,TDMA
                                                       #   on GSM value needed
                              priority_flag => 0x00,   # default ok
                              schedule_delivery_time => '', # default ok
                              validity_period => '',        # default ok
                              registered_delivery => 0x00,  # default ok
                              replace_if_present_flag => 0x00, # default ok
                              data_coding => 0x00,     # default ok
                              sm_default_msg_id => 0x00,    # default ok
                              short_message => '',     # default ok, but
                                                       #   usually supplied
                              ) or die;

For example:

              $resp_pdu = $smpp->submit_multi(destination_addr =>
                                              [ '+447799658372', '+447799658373' ],
                                              short_message => 'test message')
                 or die;
              die "Response indicated error: " . $resp_pdu->explain_status()
                  if $resp_pdu->status;

The destinations are specified as an array reference. dest_flag, dest_addr_ton, and dest_addr_npi must have same cardinality as destination_addr if they are present. Default for dest_flag is MULTIDESTFLAG_SME_Address, i.e. normal phone number. πŸ’…


unbind() (4.2, p.56)

Used by the ESME to unregisters the ESME from the SMSC. Does not take any parameters. Bye bestie! πŸ‘‹

               $smpp->unbind() or die;

RESPONSE PDU METHODS

Response PDU methods are used to indicate the outcome of requested commands. Typically these methods would be used by someone implementing a server (SMSC). Main character server energy, iykyk. πŸ‘‘

Response PDUs do not have a separate asynchronous behaviour pattern.


bind_receiver_resp()

bind_transmitter_resp()

bind_transceiver_resp()

               $smpp->bind_transceiver_resp(
                                            system_id => '', # default ok
                                            ) or die;

cancel_sm_resp() (4.9.2, p.100)

               $smpp->cancel_sm_resp() or die;

data_sm_resp()

               $smpp->data_sm_resp(message_id => $msg_id) or die;

deliver_sm_resp()

               $smpp->deliver_sm_resp(message_id => $msg_id) or die;

               $smpp->enquire_link_resp() or die;

generic_nack() (4.3.1, p.57)

               $smpp->generic_nack() or die;

query_sm_resp() (4.6.2, p.96)

               $smpp->query_sm_resp(
                                    message_id => $msg_id,   # mandatory
                                    final_date => '',        # default ok
                                    message_state => $state, # mandatory
                                    error_code => 0x00,      # default ok
                              ) or die;

replace_sm_resp() (4.10.2, p.104)

               $smpp->replace_sm_resp() or die;

submit_sm_resp() (4.4.2, p.67)

               $smpp->submit_sm_resp(message_id => $msg_id) or die;

submit_multi_resp() (4.5.2, p.76)

               $smpp->submit_multi_resp(message_id => $msg_id
                                        dest_addr_ton => [], # default ok
                                        dest_addr_npi => [], # default ok
                                        destination_addr => [],  # mandatory
                                        error_status_code => [], # mandatory
                                        ) or die;

unbind_resp() (4.2.2, p.56)

               $smpp->unbind_resp() or die;

🌸 MESSAGE ENCODING AND LENGTH 🌸

ok bestie so here's the tea β˜• β€” basically every SMS tech out there has its own built-in length cap and nobody's exempt. GSM, for example, says the length is 140 bytes. And if you're using 7 bit encoding? That's literally where the famous 160 characters comes from. iconic.

But here's the thing: Net::SMPP does not enforce this limit in any way. Like, at all. If you build a message that's too long, that's on you, bestie πŸ’€. It is your problem. The module expects you, the application layer, to make sure you stay inside the limits.

Same energy for encoding: Net::SMPP also does not automatically perform the encoding, not even if you set the data_encoding parameter. Nope. The application layer is responsible for doing the encoding and for setting the data_encoding parameter to match what you actually did. No auto-pilot here 🧚

But to help you out with the usual 7 bit encoding, there are these functions β€” and no, they will not fire themselves, you have to call them explicitly:

pack_7bit()

unpack_7bit()

Example

$resp_pdu = $smpp->submit_sm(destination_addr => '+447799658372',
                             data_encoding => 0x00,
                             short_message => pack_7bit('test message'))
  or die;

Now, why is the module so chill about leaving encoding and length to the application layer? Two reasons, and they're honestly both good:

  1. A lot of the time the data is just getting copied straight through to another message or another protocol. So like... we genuinely do not care how it's encoded or how long it is. It was presumably valid at origin, right? πŸ’…
  2. This policy keeps underlying technology dependencies out of the module. Local deployments tend to be full of all manner of hacks that make this whole area very difficult to chart. So it gets left to the local application developer to figure out what is locally needed. periodt.

πŸ¦‹ OTHER METHODS πŸ¦‹

read_pdu()

Reads a PDU from the stream and analyzes it into a Net::SMPP::PDU object (but only if the PDU is of a known type). It blocks until a PDU is available. 😴

If you do not want it to block, do a select on the socket to make sure some data is available β€” though unfortunately some data may be available but not enough, so it can still block anyway. no cap, that's just how it is.

read_pdu() is very useful for implementing the main loop of an SMSC, where unknown PDUs must be received in random order and processed. main character behaviour honestly πŸ‘‘

$pdu = $smpp->read_pdu() or die;

wait_pdu()

Reads PDUs from the stream and handles or discards them until the matching PDU is found. Blocks until success. Typically wait_pdu() is used internally by request methods when they're operating in synchronous mode.

The PDUs to handle are specified by "${*$me}{handlers}-"{$command_id}>. The handlers table is initially populated to handle enquire_link PDUs automatically, but this can be altered using the handlers argument to the constructor. cute, right?

$pdu = $smpp->wait_pdu($cmd_id_to_wait, $seq_to_wait) or die;

set_version($vers)

Sets the protocol version of the object to either 0x40 or 0x34. It's important to use this method instead of altering the $smpp->{smpp_version} field directly, because there are several other fields that have to be set in tandem. so like, don't be a rebel about it πŸ™ˆ


✨ EXAMPLES ✨

Typical client:

use Net::SMPP;
$smpp = Net::SMPP->new_transceiver('smsc.foo.net', port=>2552) or die;
$resp_pdu = $smpp->submit_sm(destination_addr => '447799658372',
                             data => 'test message') or die;
***

Typical server, run from inetd:

***

See test.pl for good templates with all official parameters β€” but beware, the actual parameter values are fictitious, and so is the flow of the dialog. it's giving demo energy, not production energy πŸ’€


πŸŽ€ MULTIPART MESSAGE πŸŽ€

Reportedly (Zeus Panchenko) multipart messages can be gotten to work with this:

while (length ($msgtext)) {
  if ($multimsg_maxparts) {
    @udh_ar = map { sprintf "%x", $_ } $origref, $multimsg_maxparts, $multimsg_curpart;
    $udh = pack("hhhhhh",0x05, 0x00, 0x03 , @udh_ar);
    $resp_pdu = $smpp->submit_sm(destination_addr => $phone,
                         ...
                         short_message => $udh . $msgtext,
                       );
    ...
  }
}

🌈 VERSION 4.0 SUPPORT 🌈

#4#cut =head1 VERSION 4.0 SUPPORT β€” yeah bestie that's the raw source marker, we are keeping everything here, no crumbs left behind πŸ’…

So Net::SMPP was originally written for version 3.4 of the SMPP protocol. Since then, the author got hold of specs for an earlier protocol β€” version 4.0 (Logical, eh? pun intended 😭). In the author's understanding, the relevant differences are as follows. n.b. (ok) marks a difference that has already been implemented:

  1. A reserved (always 0x00000000) field in message header (v4 p. 21) (ok)
  2. Connection can not be opened in transceiver mode (this module will not enforce this restriction) (ok)
  3. Command versioning. Version 0x01 == V4 (v4 p. 22) (ok)
  4. Support for extended facilities has to be requested during bind (ok)
  5. bind_* PDUs have facilities_mask field (v4 p. 25) (ok)
  6. bind_*_resp PDUs have facilities_mask field (v4 p. 27) (ok)
  7. outbind lacks system ID field (v4 p.30, v3.4 p. 54) (ok)
  8. submit_sm lacks service_type and adds message_class (v4 p. 34, v3.4 p. 59) (ok)
  9. submit_sm: telematic_interworking == protocol_id (ok)
  10. submit_sm: starting from number of destinations and destination address the message format is substantially different. Actually the message format is somewhat similar to v3.4 submit_multi. (ok)
  11. submit_sm: validity period encoded as an integer relative offset (was absolute time as C string) (ok)
  12. submit_sm: replace_if_present flag missing (ok)
  13. submit_sm: sm_length field is 2 octets (was one) (ok)
  14. submit_sm_resp is completely different, but actually equal to v3.4 submit_multi_resp (v4 p. 37, v3.4 pp. 67,75) (ok)
  15. submit_sm vs submit_multi: lacks service_type, adds message_class (ok)
  16. submit_sm vs submit_multi: number_of_dests increased from 1 byte to 4 (ok)
  17. submit_sm vs submit_multi: esm_class lacking, adds messaging_mode and msg_reference (ok)
  18. submit_sm vs submit_multi: telematic_interworking == protocol_id (ok)
  19. submit_sm vs submit_multi: replace_if_present missing (ok)
  20. submit_sm vs submit_multi: sm_length is 2 bytes (was one) (ok)
  21. submit_sm vs submit_multi: lacks dest_flag and distribution_list_name (ok)
  22. deliver_sm: lacks service_type (ok)
  23. deliver_sm: lacks esm_class, adds msg_reference and message_class (ok)
  24. deliver_sm: telematic_interworking == protocol_id (ok)
  25. deliver_sm: priority_level == priority_flag (ok)
  26. deliver_sm: submit_time_stamp == schedule_delivery_time (ok)
  27. deliver_sm: lacks validity_period, registered_delivery, and replace_if_present_flag (ok)
  28. deliver_sm: lacks sm_default_msg_id (ok)
  29. deliver_sm: sm_length is now 2 bytes (was one) (ok)
  30. deliver_sm_resp: lacks message_id (v3.4 has the field, but its unused) (ok)
  31. New command: delivery_receipt (ok)
  32. New response: delivery_receipt_resp (ok)
  33. query_sm: dest_addr_* fields added (v4 p. 46, v3.4 p. 95) (ok)
  34. query_sm_resp: error_code renamed to network_error_code and increased in size from one to 4 bytes (ok)
  35. cancel_sm: service_type renamed to message_class, also type changed (ok)
  36. replace_sm: added dest_addr_* fields (ok)
  37. replace_sm: data type of validity_period changed (ok)
  38. replace_sm: added data_coding field (ok)
  39. replace_sm: sm_length field increased from one to two bytes (ok)
  40. In v3.4 command code 0x0009 means bind_transceiver, in v4.0 this very same code means delivery_receipt (bummer) (ok) πŸ’€
  41. In v3.4 enquire_link is 0x0015 where as in v4 it is 0x000a (ok)

To create a version 4 connection, you must specify smpp_version => 0x40 and you should not bind as transceiver, because that is not supported by the specification. 🚫

Since the v3.4 specification seems more mature, the author recommends that where attributes have been renamed between v4 and v3.4, you stick to using the v3.4 names. Compatibility code has been provided whenever possible. 🫢

#4#end

πŸ“ MISC. NOTES πŸ“

Unless you wrote your program to be multithreaded or multiprocess, everything will happen in one thread of execution. So if you get an unbind while you're off doing something else (like checking your spool directory), it just sits there in operating system level buffers until you actually call read_pdu(). Knowing about that unbind or not is of little use. You can write your program to assume the network traffic arrives only exactly when you call read_pdu(). wild, i know ✨

Speaking of unbind: it is normally handled by a dispatch table automatically if you use wait_pdu() to receive your traffic. But if you created your own dispatch table, you'll have to add it there yourself. And if you're calling read_pdu(), then you have to handle it yourself. Even if you are using the supplied table, you may want to double check β€” there could be a bug. πŸ™ˆ

One more thing: if your problem is knowing whether wait_pdu() or read_pdu() would block, then you have two possible solutions:

  1. use select(2) systemcall to determine for the socket is ready for reading
  2. structure your program as several processes (e.g. one for sending and one for receiving) so that you can afford to block

Neither of those two tricks is specific to this module, bestie. Consult any standard text book on TCP/IP network programming. πŸ“š


πŸ’€ ERRORS πŸ’€

Please consult the status_code table in the beginning of the source code, or SMPP specification section 5.1.3, table 5-2, pp.112-114.


πŸ‘‘ EXPORT πŸ‘‘

None by default. that's it, that's the whole section πŸ’…


🧚 TESTS / WHAT IS KNOWN TO WORK 🧚

Interoperates with itself. iconic. self-love 🫢


πŸ”₯ TO DO AND BUGS πŸ”₯

  • read_pdu() can block even if socket selects for reading.
  • The submit_multi command has not been implemented.

Sampo Kellomaki <sampo@symlabs.com>

Net::SMPP is copyright (c) 2001-2010 by Sampo Kellomaki, All rights reserved. Portions copyright (c) 2001-2005 by Symlabs, All rights reserved. You may use and distribute Net::SMPP under same terms as perl itself.

NET::SMPP COMES WITH ABSOLUTELY NO WARRANTY. 😭

🌟 PLUG 🌟

This work was sponsored by Symlabs, the LDAP and directory experts (www.symlabs.com). slay πŸ’œ

πŸ‘€ SEE ALSO πŸ‘€

okay bestie, here's your reading list, your link dump, your sources era πŸ’– go forth and cite:

lowkey obsessed with how many of these links are just... dead now πŸ’€ the internet really said "no thoughts, only rot" but we keep them here anyway because history πŸŽ€

and that's a wrap, bestie!! 🫢 this whole thing ships as perl v5.36.0, dated 2022-12-04, and it lives in the manual page known as SMPP(3pm) β€” go read it with man SMPP and slay πŸ’…βœ¨

πŸ“¦ where this page comes from