635 lines
22 KiB
PHP
635 lines
22 KiB
PHP
<?php
|
||
namespace TPS\Birzha\Classes;
|
||
/**
|
||
*ORIGINAL AUTHOR
|
||
* @package net
|
||
* Class for receiving or sending sms through SMPP protocol.
|
||
* @version 0.2
|
||
* @author paladin
|
||
* @since 04/25/2006
|
||
* @see http://www.smpp.org/doc/public/index.html - SMPP 3.4 protocol specification
|
||
*/
|
||
|
||
/**
|
||
* FORK BY xhit
|
||
* @package net
|
||
* Class for receiving or sending sms through SMPP protocol.
|
||
* @version fork 0.1
|
||
* @author xhit
|
||
* @since 02/01/2019
|
||
*/
|
||
|
||
/**
|
||
*This class is also a merge from smpp lib of onlinecity
|
||
* @see https://github.com/onlinecity/php-smpp
|
||
*/
|
||
|
||
class SMPP{
|
||
|
||
// Command ids - SMPP v3.4 - 5.1.2.1 page 110-111
|
||
const GENERIC_NACK = 0x80000000;
|
||
const BIND_RECEIVER = 0x00000001;
|
||
const BIND_RECEIVER_RESP = 0x80000001;
|
||
const BIND_TRANSMITTER = 0x00000002;
|
||
const BIND_TRANSMITTER_RESP = 0x80000002;
|
||
const QUERY_SM = 0x00000003;
|
||
const QUERY_SM_RESP = 0x80000003;
|
||
const SUBMIT_SM = 0x00000004;
|
||
const SUBMIT_SM_RESP = 0x80000004;
|
||
const DELIVER_SM = 0x00000005;
|
||
const DELIVER_SM_RESP = 0x80000005;
|
||
const UNBIND = 0x00000006;
|
||
const UNBIND_RESP = 0x80000006;
|
||
const REPLACE_SM = 0x00000007;
|
||
const REPLACE_SM_RESP = 0x80000007;
|
||
const CANCEL_SM = 0x00000008;
|
||
const CANCEL_SM_RESP = 0x80000008;
|
||
const BIND_TRANSCEIVER = 0x00000009;
|
||
const BIND_TRANSCEIVER_RESP = 0x80000009;
|
||
const OUTBIND = 0x0000000B;
|
||
const ENQUIRE_LINK = 0x00000015;
|
||
const ENQUIRE_LINK_RESP = 0x80000015;
|
||
|
||
// Command status - SMPP v3.4 - 5.1.3 page 112-114
|
||
const ESME_ROK = 0x00000000; // No Error
|
||
const ESME_RINVMSGLEN = 0x00000001; // Message Length is invalid
|
||
const ESME_RINVCMDLEN = 0x00000002; // Command Length is invalid
|
||
const ESME_RINVCMDID = 0x00000003; // Invalid Command ID
|
||
const ESME_RINVBNDSTS = 0x00000004; // Incorrect BIND Status for given command
|
||
const ESME_RALYBND = 0x00000005; // ESME Already in Bound State
|
||
const ESME_RINVPRTFLG = 0x00000006; // Invalid Priority Flag
|
||
const ESME_RINVREGDLVFLG = 0x00000007; // Invalid Registered Delivery Flag
|
||
const ESME_RSYSERR = 0x00000008; // System Error
|
||
const ESME_RINVSRCADR = 0x0000000A; // Invalid Source Address
|
||
const ESME_RINVDSTADR = 0x0000000B; // Invalid Dest Addr
|
||
const ESME_RINVMSGID = 0x0000000C; // Message ID is invalid
|
||
const ESME_RBINDFAIL = 0x0000000D; // Bind Failed
|
||
const ESME_RINVPASWD = 0x0000000E; // Invalid Password
|
||
const ESME_RINVSYSID = 0x0000000F; // Invalid System ID
|
||
const ESME_RCANCELFAIL = 0x00000011; // Cancel SM Failed
|
||
const ESME_RREPLACEFAIL = 0x00000013; // Replace SM Failed
|
||
const ESME_RMSGQFUL = 0x00000014; // Message Queue Full
|
||
const ESME_RINVSERTYP = 0x00000015; // Invalid Service Type
|
||
const ESME_RINVNUMDESTS = 0x00000033; // Invalid number of destinations
|
||
const ESME_RINVDLNAME = 0x00000034; // Invalid Distribution List name
|
||
const ESME_RINVDESTFLAG = 0x00000040; // Destination flag (submit_multi)
|
||
const ESME_RINVSUBREP = 0x00000042; // Invalid ‘submit with replace’ request (i.e. submit_sm with replace_if_present_flag set)
|
||
const ESME_RINVESMSUBMIT = 0x00000043; // Invalid esm_SUBMIT field data
|
||
const ESME_RCNTSUBDL = 0x00000044; // Cannot Submit to Distribution List
|
||
const ESME_RSUBMITFAIL = 0x00000045; // submit_sm or submit_multi failed
|
||
const ESME_RINVSRCTON = 0x00000048; // Invalid Source address TON
|
||
const ESME_RINVSRCNPI = 0x00000049; // Invalid Source address NPI
|
||
const ESME_RINVDSTTON = 0x00000050; // Invalid Destination address TON
|
||
const ESME_RINVDSTNPI = 0x00000051; // Invalid Destination address NPI
|
||
const ESME_RINVSYSTYP = 0x00000053; // Invalid system_type field
|
||
const ESME_RINVREPFLAG = 0x00000054; // Invalid replace_if_present flag
|
||
const ESME_RINVNUMMSGS = 0x00000055; // Invalid number of messages
|
||
const ESME_RTHROTTLED = 0x00000058; // Throttling error (ESME has exceeded allowed message limits)
|
||
const ESME_RINVSCHED = 0x00000061; // Invalid Scheduled Delivery Time
|
||
const ESME_RINVEXPIRY = 0x00000062; // Invalid message (Expiry time)
|
||
const ESME_RINVDFTMSGID = 0x00000063; // Predefined Message Invalid or Not Found
|
||
const ESME_RX_T_APPN = 0x00000064; // ESME Receiver Temporary App Error Code
|
||
const ESME_RX_P_APPN = 0x00000065; // ESME Receiver Permanent App Error Code
|
||
const ESME_RX_R_APPN = 0x00000066; // ESME Receiver Reject Message Error Code
|
||
const ESME_RQUERYFAIL = 0x00000067; // query_sm request failed
|
||
const ESME_RINVOPTPARSTREAM = 0x000000C0; // Error in the optional part of the PDU Body.
|
||
const ESME_ROPTPARNOTALLWD = 0x000000C1; // Optional Parameter not allowed
|
||
const ESME_RINVPARLEN = 0x000000C2; // Invalid Parameter Length.
|
||
const ESME_RMISSINGOPTPARAM = 0x000000C3; // Expected Optional Parameter missing
|
||
const ESME_RINVOPTPARAMVAL = 0x000000C4; // Invalid Optional Parameter Value
|
||
const ESME_RDELIVERYFAILURE = 0x000000FE; // Delivery Failure (data_sm_resp)
|
||
const ESME_RUNKNOWNERR = 0x000000FF; // Unknown Error
|
||
|
||
//SMPP bind parameters
|
||
var $system_type="WWW";
|
||
var $interface_version=0x34;
|
||
var $addr_ton=0;
|
||
var $addr_npi=0;
|
||
var $address_range="";
|
||
//Timeout of enquirelink, default 10 seconds
|
||
var $enquirelink_timeout = 10;
|
||
//ESME transmitter parameters
|
||
var $sms_service_type="";
|
||
var $sms_source_addr_ton=0;
|
||
var $sms_source_addr_npi=0;
|
||
var $sms_dest_addr_ton=0;
|
||
var $sms_dest_addr_npi=0;
|
||
var $sms_esm_class=0;
|
||
var $sms_protocol_id=0;
|
||
var $sms_priority_flag=0;
|
||
var $sms_schedule_delivery_time="";
|
||
var $sms_validity_period="";
|
||
var $sms_registered_delivery_flag=0;
|
||
var $sms_replace_if_present_flag=0;
|
||
var $sms_data_coding=0;
|
||
var $sms_sm_default_msg_id=0;
|
||
|
||
/**
|
||
* Constructs the smpp class
|
||
* @param $host - SMSC host name or host IP
|
||
* @param $port - SMSC port
|
||
*/
|
||
|
||
function __construct($host, $port){
|
||
|
||
//Last time the of the enquirelink
|
||
$this->lastenquire = 0;
|
||
//internal parameters
|
||
$this->sequence_number=1;
|
||
$this->debug=false;
|
||
$this->pdu_queue=array();
|
||
$this->host=$host;
|
||
$this->port=intval($port);
|
||
$this->state="closed";
|
||
//open the socket
|
||
$this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, 10);
|
||
if($this->socket)$this->state="open";
|
||
|
||
}
|
||
|
||
//Function to know if is necessary send the enquirelink to SMSC
|
||
function checkForEnquire(){
|
||
|
||
$now = microtime(true);
|
||
|
||
$duration = $now-$this->lastenquire;
|
||
$hours = (int)($duration/60/60);
|
||
$minutes = (int)($duration/60)-$hours*60;
|
||
$seconds = (int)$duration-$hours*60*60-$minutes*60;
|
||
|
||
if($this->debug){
|
||
echo "<br>" . 'Seconds from last enquire link = ' . $seconds . PHP_EOL;
|
||
}
|
||
|
||
if ($seconds >= $this->enquirelink_timeout){
|
||
$this->enquirelink();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
|
||
* @param $login - ESME system_id
|
||
* @param $port - ESME password
|
||
* @return true when bind was successful
|
||
*/
|
||
function bindReceiver($login, $pass){
|
||
|
||
if($this->state!="open")return false;
|
||
if($this->debug){
|
||
echo "<br>" . "Binding receiver...\n\n";
|
||
}
|
||
$status=$this->_bind($login, $pass, SMPP::BIND_RECEIVER);
|
||
if($this->debug){
|
||
echo "<br>" . "Binding status : $status\n\n";
|
||
}
|
||
if($status===0)$this->state="bind_rx";
|
||
return ($status===0);
|
||
}
|
||
|
||
/**
|
||
* Binds the transmitter. One object can be bound only as receiver or only as trancmitter.
|
||
* @param $login - ESME system_id
|
||
* @param $port - ESME password
|
||
* @return true when bind was successful
|
||
*/
|
||
function bindTransmitter($login, $pass){
|
||
|
||
if($this->state!="open")return false;
|
||
if($this->debug){
|
||
echo "<br>" . "Binding transmitter...\n\n";
|
||
}
|
||
$status=$this->_bind($login, $pass, SMPP::BIND_TRANSMITTER);
|
||
if($this->debug){
|
||
echo "<br>" . "Binding status : $status\n\n";
|
||
}
|
||
if($status===0)$this->state="bind_tx";
|
||
|
||
return ($status===0);
|
||
}
|
||
|
||
/**
|
||
* Closes the session on the SMSC server.
|
||
*/
|
||
function close(){
|
||
if($this->state=="closed")return;
|
||
if($this->debug){
|
||
echo "<br>" . "Unbinding...\n\n";
|
||
}
|
||
$status=$this->sendCommand(SMPP::UNBIND,"");
|
||
if($this->debug){
|
||
echo "<br>" . "Unbind status : $status\n\n";
|
||
}
|
||
fclose($this->socket);
|
||
$this->state="closed";
|
||
}
|
||
|
||
/**
|
||
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
|
||
* Receiver not send enquirelink
|
||
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
|
||
* @return sms associative array or false when reading failed or no more sms.
|
||
*/
|
||
|
||
function readSMS(){
|
||
|
||
if($this->state!="bind_rx")return false;
|
||
$command_id=SMPP::DELIVER_SM;
|
||
//check the queue
|
||
for($i=0;$i<count($this->pdu_queue);$i++){
|
||
$pdu=$this->pdu_queue[$i];
|
||
if($pdu['id']==$command_id){
|
||
//remove response
|
||
array_splice($this->pdu_queue, $i, 1);
|
||
return parseSMS($pdu);
|
||
}
|
||
}
|
||
//read pdu
|
||
do{
|
||
if($this->debug){
|
||
echo "<br>" . "read sms...\n\n";
|
||
}
|
||
$pdu=$this->readPDU();
|
||
//check for enquire link command
|
||
if($pdu['id']==SMPP::ENQUIRE_LINK){
|
||
$this->sendPDU(SMPP::ENQUIRE_LINK_RESP, "", $pdu['sn']);
|
||
return false;
|
||
}
|
||
array_push($this->pdu_queue, $pdu);
|
||
}while($pdu && $pdu['id']!=$command_id);
|
||
if($pdu){
|
||
array_pop($this->pdu_queue);
|
||
return $this->parseSMS($pdu);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Send one SMS from SMSC. Can be executed only after bindTransmitter() call.
|
||
* @return true on succesfull send, false if error encountered
|
||
*/
|
||
function sendSMS($from, $to, $message){
|
||
if (strlen($from)>20 || strlen($to)>20 || strlen($message)>160)return false;
|
||
if($this->state!="bind_tx")return false;
|
||
|
||
//TON
|
||
$this->sms_source_addr_ton = 2;//$this->setTon($from);
|
||
$this->sms_dest_addr_ton = 2;$this->setTon($to);
|
||
|
||
//NPI
|
||
$this->sms_source_addr_npi = $this->setNPI($from);
|
||
$this->sms_dest_addr_npi = $this->setNPI($to);
|
||
|
||
$pdu = pack('a1cca'.(strlen($from)+1).'cca'.(strlen($to)+1).'ccca1a1ccccca'.(strlen($message)),
|
||
$this->sms_service_type,
|
||
$this->sms_source_addr_ton,
|
||
$this->sms_source_addr_npi,
|
||
$from,//source_addr
|
||
$this->sms_dest_addr_ton,
|
||
$this->sms_dest_addr_npi,
|
||
$to,//destination_addr
|
||
$this->sms_esm_class,
|
||
$this->sms_protocol_id,
|
||
$this->sms_priority_flag,
|
||
$this->sms_schedule_delivery_time,
|
||
$this->sms_validity_period,
|
||
$this->sms_registered_delivery_flag,
|
||
$this->sms_replace_if_present_flag,
|
||
$this->sms_data_coding,
|
||
$this->sms_sm_default_msg_id,
|
||
strlen($message),//sm_length
|
||
$message//short_message
|
||
);
|
||
|
||
//Before each message verify if necessary send the enquirelink to SMSC
|
||
$this->checkForEnquire();
|
||
$status=$this->sendCommand(SMPP::SUBMIT_SM,$pdu);
|
||
return $status;
|
||
}
|
||
|
||
//Get text of error
|
||
function getStatusMessage($statuscode)
|
||
{
|
||
if (is_bool($statuscode)) return 'Connection Error';
|
||
|
||
switch ($statuscode) {
|
||
case SMPP::ESME_ROK: return 'OK';
|
||
case SMPP::ESME_RINVMSGLEN: return 'Message Length is invalid';
|
||
case SMPP::ESME_RINVCMDLEN: return 'Command Length is invalid';
|
||
case SMPP::ESME_RINVCMDID: return 'Invalid Command ID';
|
||
case SMPP::ESME_RINVBNDSTS: return 'Incorrect BIND Status for given command';
|
||
case SMPP::ESME_RALYBND: return 'ESME Already in Bound State';
|
||
case SMPP::ESME_RINVPRTFLG: return 'Invalid Priority Flag';
|
||
case SMPP::ESME_RINVREGDLVFLG: return 'Invalid Registered Delivery Flag';
|
||
case SMPP::ESME_RSYSERR: return 'System Error';
|
||
case SMPP::ESME_RINVSRCADR: return 'Invalid Source Address';
|
||
case SMPP::ESME_RINVDSTADR: return 'Invalid Dest Addr';
|
||
case SMPP::ESME_RINVMSGID: return 'Message ID is invalid';
|
||
case SMPP::ESME_RBINDFAIL: return 'Bind Failed';
|
||
case SMPP::ESME_RINVPASWD: return 'Invalid Password';
|
||
case SMPP::ESME_RINVSYSID: return 'Invalid System ID';
|
||
case SMPP::ESME_RCANCELFAIL: return 'Cancel SM Failed';
|
||
case SMPP::ESME_RREPLACEFAIL: return 'Replace SM Failed';
|
||
case SMPP::ESME_RMSGQFUL: return 'Message Queue Full';
|
||
case SMPP::ESME_RINVSERTYP: return 'Invalid Service Type';
|
||
case SMPP::ESME_RINVNUMDESTS: return 'Invalid number of destinations';
|
||
case SMPP::ESME_RINVDLNAME: return 'Invalid Distribution List name';
|
||
case SMPP::ESME_RINVDESTFLAG: return 'Destination flag (submit_multi)';
|
||
case SMPP::ESME_RINVSUBREP: return 'Invalid ‘submit with replace’ request (i.e. submit_sm with replace_if_present_flag set)';
|
||
case SMPP::ESME_RINVESMSUBMIT: return 'Invalid esm_SUBMIT field data';
|
||
case SMPP::ESME_RCNTSUBDL: return 'Cannot Submit to Distribution List';
|
||
case SMPP::ESME_RSUBMITFAIL: return 'submit_sm or submit_multi failed';
|
||
case SMPP::ESME_RINVSRCTON: return 'Invalid Source address TON';
|
||
case SMPP::ESME_RINVSRCNPI: return 'Invalid Source address NPI';
|
||
case SMPP::ESME_RINVDSTTON: return 'Invalid Destination address TON';
|
||
case SMPP::ESME_RINVDSTNPI: return 'Invalid Destination address NPI';
|
||
case SMPP::ESME_RINVSYSTYP: return 'Invalid system_type field';
|
||
case SMPP::ESME_RINVREPFLAG: return 'Invalid replace_if_present flag';
|
||
case SMPP::ESME_RINVNUMMSGS: return 'Invalid number of messages';
|
||
case SMPP::ESME_RTHROTTLED: return 'Throttling error (ESME has exceeded allowed message limits)';
|
||
case SMPP::ESME_RINVSCHED: return 'Invalid Scheduled Delivery Time';
|
||
case SMPP::ESME_RINVEXPIRY: return 'Invalid message (Expiry time)';
|
||
case SMPP::ESME_RINVDFTMSGID: return 'Predefined Message Invalid or Not Found';
|
||
case SMPP::ESME_RX_T_APPN: return 'ESME Receiver Temporary App Error Code';
|
||
case SMPP::ESME_RX_P_APPN: return 'ESME Receiver Permanent App Error Code';
|
||
case SMPP::ESME_RX_R_APPN: return 'ESME Receiver Reject Message Error Code';
|
||
case SMPP::ESME_RQUERYFAIL: return 'query_sm request failed';
|
||
case SMPP::ESME_RINVOPTPARSTREAM: return 'Error in the optional part of the PDU Body.';
|
||
case SMPP::ESME_ROPTPARNOTALLWD: return 'Optional Parameter not allowed';
|
||
case SMPP::ESME_RINVPARLEN: return 'Invalid Parameter Length.';
|
||
case SMPP::ESME_RMISSINGOPTPARAM: return 'Expected Optional Parameter missing';
|
||
case SMPP::ESME_RINVOPTPARAMVAL: return 'Invalid Optional Parameter Value';
|
||
case SMPP::ESME_RDELIVERYFAILURE: return 'Delivery Failure (data_sm_resp)';
|
||
case SMPP::ESME_RUNKNOWNERR: return 'Unknown Error';
|
||
default:
|
||
return 'Unknown error';
|
||
}
|
||
}
|
||
|
||
////////////////private functions///////////////
|
||
|
||
|
||
/*
|
||
TON:
|
||
Unknown = 0,
|
||
International = 1,
|
||
National = 2,
|
||
NetworkSpecific = 3,
|
||
SubscriberNumber = 4,
|
||
Alphanumeric = 5,
|
||
Abbreviated = 6,
|
||
*/
|
||
|
||
function setTon($address){
|
||
|
||
//Check if empty or only number
|
||
if (empty($address) || ctype_digit($address)){
|
||
$NationalNumberLenght = 8;
|
||
|
||
//If length is 8 then TON is National (2)
|
||
if(strlen($address) == $NationalNumberLenght) return 2; //National
|
||
|
||
//If empty and length is > 8 then TON is International (1)
|
||
if(empty($address) || strlen($address) > $NationalNumberLenght) return 1; //International
|
||
}
|
||
|
||
//If address is alphanumeric then TON is Alphanumeric (5)
|
||
if (!ctype_digit($address)) return 5; //Alphanumeric
|
||
|
||
//If no apply condition then TON is Unknown (0)
|
||
return 0; //Unknown
|
||
|
||
}
|
||
|
||
/*
|
||
NPI:
|
||
Unknown = 0,
|
||
ISDN = 1
|
||
*/
|
||
function setNPI($address){
|
||
|
||
//If address is alphanumeric then NPI is Unknown(0)
|
||
if (!ctype_digit($address)) return 0; //Unknown
|
||
|
||
//If no apply condition then NPI is ISDN (1)
|
||
return 1; //ISDN
|
||
|
||
}
|
||
|
||
//Send the enquirelink
|
||
public function enquireLink()
|
||
{
|
||
$response = $this->sendCommand(SMPP::ENQUIRE_LINK, "");
|
||
if ($response == 0) {
|
||
$this->lastenquire = microtime(true);
|
||
if ($this->debug){
|
||
echo "<br>" . "ENQUIRE!!" . PHP_EOL;
|
||
}
|
||
}
|
||
return $response;
|
||
}
|
||
|
||
/**
|
||
* @private function
|
||
* Binds the socket and opens the session on SMSC
|
||
* @param $login - ESME system_id
|
||
* @param $port - ESME password
|
||
* @return bind status or false on error
|
||
*/
|
||
function _bind($login, $pass, $command_id){
|
||
//make PDU
|
||
$pdu = pack(
|
||
'a'.(strlen($login)+1).
|
||
'a'.(strlen($pass)+1).
|
||
'a'.(strlen($this->system_type)+1).
|
||
'CCCa'.(strlen($this->address_range)+1),
|
||
$login, $pass, $this->system_type,
|
||
$this->interface_version, $this->addr_ton,
|
||
$this->addr_npi, $this->address_range);
|
||
$status=$this->sendCommand($command_id,$pdu);
|
||
return $status;
|
||
}
|
||
/**
|
||
* @private function
|
||
* Parse deliver PDU from SMSC.
|
||
* @param $pdu - deliver PDU from SMSC.
|
||
* @return parsed PDU as array.
|
||
*/
|
||
function parseSMS($pdu){
|
||
//check command id
|
||
if($pdu['id']!=SMPP::DELIVER_SM)return false;
|
||
//unpack PDU
|
||
$ar=unpack("C*",$pdu['body']);
|
||
print_r($ar);
|
||
$sms=array('service_type'=>$this->getString($ar,6),
|
||
'source_addr_ton'=>array_shift($ar),
|
||
'source_addr_npi'=>array_shift($ar),
|
||
'source_addr'=>$this->getString($ar,21),
|
||
'dest_addr_ton'=>array_shift($ar),
|
||
'dest_addr_npi'=>array_shift($ar),
|
||
'destination_addr'=>$this->getString($ar,21),
|
||
'esm_class'=>array_shift($ar),
|
||
'protocol_id'=>array_shift($ar),
|
||
'priority_flag'=>array_shift($ar),
|
||
'schedule_delivery_time'=>array_shift($ar),
|
||
'validity_period'=>array_shift($ar),
|
||
'registered_delivery'=>array_shift($ar),
|
||
'replace_if_present_flag'=>array_shift($ar),
|
||
'data_coding'=>array_shift($ar),
|
||
'sm_default_msg_id'=>array_shift($ar),
|
||
'sm_length'=>array_shift($ar),
|
||
'short_message'=>$this->getString($ar,255)
|
||
);
|
||
if($this->debug){
|
||
echo "<br>" . "Delivered sms:\n";
|
||
print_r($sms);
|
||
echo "<br>" . "\n";
|
||
}
|
||
//send response of recieving sms
|
||
$this->sendPDU(SMPP::DELIVER_SM_RESP, "\0", $pdu['sn']);
|
||
return $sms;
|
||
}
|
||
/**
|
||
* @private function
|
||
* Sends the PDU command to the SMSC and waits for responce.
|
||
* @param $command_id - command ID
|
||
* @param $pdu - PDU body
|
||
* @return PDU status or false on error
|
||
*/
|
||
function sendCommand($command_id, $pdu){
|
||
if($this->state=="closed")return false;
|
||
$this->sendPDU($command_id, $pdu, $this->sequence_number);
|
||
$status=$this->readPDU_resp($this->sequence_number, $command_id);
|
||
$this->sequence_number=$this->sequence_number+1;
|
||
return $status;
|
||
}
|
||
/**
|
||
* @private function
|
||
* Prepares and sends PDU to SMSC.
|
||
* @param $command_id - command ID
|
||
* @param $pdu - PDU body
|
||
* @param $seq_number - PDU sequence number
|
||
*/
|
||
function sendPDU($command_id, $pdu, $seq_number){
|
||
$length=strlen($pdu) + 16;
|
||
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
|
||
if($this->debug){
|
||
echo "<br>" . "Send PDU : $length bytes\n";
|
||
$this->printHex($header.$pdu);
|
||
echo "<br>" . "command_id : ".$command_id."\n";
|
||
echo "<br>" . "sequence number : $seq_number\n\n";
|
||
}
|
||
|
||
$writed = @fwrite($this->socket, $header.$pdu, $length);
|
||
|
||
//Close conection if not bind
|
||
if ($writed == FALSE) {
|
||
if ($this->debug) echo "<br>" . "Lost connection to SMSC" . PHP_EOL;
|
||
exit();
|
||
};
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
* @private function
|
||
* Waits for SMSC responce on specific PDU.
|
||
* @param $seq_number - PDU sequence number
|
||
* @param $command_id - PDU command ID
|
||
* @return PDU status or false on error
|
||
*/
|
||
function readPDU_resp($seq_number, $command_id){
|
||
//create response id
|
||
$command_id=$command_id|SMPP::GENERIC_NACK;
|
||
//check queue
|
||
for($i=0;$i<count($this->pdu_queue);$i++){
|
||
$pdu=$this->pdu_queue[$i];
|
||
if($pdu['sn']==$seq_number && $pdu['id']==$command_id){
|
||
//remove response
|
||
array_splice($this->pdu_queue, $i, 1);
|
||
return $pdu['status'];
|
||
}
|
||
}
|
||
//read pdu
|
||
do{
|
||
$pdu=$this->readPDU();
|
||
if($pdu)array_push($this->pdu_queue, $pdu);
|
||
}while($pdu && ($pdu['sn']!=$seq_number || $pdu['id']!=$command_id));
|
||
//remove response from queue
|
||
if($pdu){
|
||
array_pop($this->pdu_queue);
|
||
return $pdu['status'];
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @private function
|
||
* Reads incoming PDU from SMSC.
|
||
* @return readed PDU or false on error.
|
||
*/
|
||
function readPDU(){
|
||
//read PDU length
|
||
$tmp=fread($this->socket, 4);
|
||
if(!$tmp)return false;
|
||
extract(unpack("Nlength", $tmp));
|
||
//read PDU headers
|
||
$tmp2=fread($this->socket, 12);
|
||
if(!$tmp2)return false;
|
||
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2));
|
||
//read PDU body
|
||
if($length-16>0){
|
||
$body=fread($this->socket, $length-16);
|
||
if(!$body)return false;
|
||
}else{
|
||
$body="";
|
||
}
|
||
if($this->debug){
|
||
echo "<br>" . "Read PDU : $length bytes\n";
|
||
$this->printHex($tmp.$tmp2.$body);
|
||
echo "<br>" . "body len : " . strlen($body) . "\n";
|
||
echo "<br>" . "Command id : $command_id\n";
|
||
echo "<br>" . "Command status : $command_status\n";
|
||
echo "<br>" . "sequence number : $sequence_number\n\n";
|
||
}
|
||
$pdu=array(
|
||
'id'=>$command_id,
|
||
'status'=>$command_status,
|
||
'sn'=>$sequence_number,
|
||
'body'=>$body);
|
||
return $pdu;
|
||
}
|
||
|
||
/**
|
||
* @private function
|
||
* Reads C style zero padded string from the char array.
|
||
* @param $ar - input array
|
||
* @param $maxlen - maximum length to read.
|
||
* @return readed string.
|
||
*/
|
||
function getString(&$ar, $maxlen=255){
|
||
$s="";
|
||
$i=0;
|
||
do{
|
||
$c=array_shift($ar);
|
||
if($c!=0)$s.=chr($c);
|
||
$i++;
|
||
}while($i<$maxlen && $c!=0);
|
||
return $s;
|
||
}
|
||
|
||
/**
|
||
* @private function
|
||
* Prints the binary string as hex bytes.
|
||
* @param $maxlen - maximum length to read.
|
||
*/
|
||
function printHex($pdu){
|
||
$ar=unpack("C*",$pdu);
|
||
foreach($ar as $v){
|
||
$s=dechex($v);
|
||
if(strlen($s)<2)$s="0$s";
|
||
print "$s ";
|
||
}
|
||
print "\n";
|
||
}
|
||
}
|