├── .github └── FUNDING.yml ├── README.md ├── examples ├── receiver.php └── transmitter.php ├── gsmencoder.php └── smpp.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/hit'] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP SMPP LIGHT is a fork from phplwsmpp with necessary functions 2 | 3 | Lightweight PHP implementation of the SMPP 3.3 and SMPP 3.4 API. Includes the SMPP receiver and SMPP transmitter implementations. 4 | This use code from paladin and online city 5 | 6 | -Tested in PHP [5.6.40] [7.1.30] [7.2.19] [7.3.6] 7 | 8 | Tested in Windows 10 and Ubuntu 9 | 10 | ## Transmitter 11 | ```php 12 | debug=false; 17 | $tx->bindTransmitter("username","password"); 18 | $result = $tx->sendSMS("2121","999999999","Hello world"); 19 | echo $tx->getStatusMessage($result); 20 | $tx->close(); 21 | unset($tx); 22 | 23 | ?> 24 | 25 | ``` 26 | 27 | If the SMSC receive GSM 03.38 encoder you can encode your message with: 28 | 29 | ```php 30 | require_once "../gsmencoder.php"; 31 | $gsmencoder = new GsmEncoder; 32 | $message = $gsmencoder->utf8_to_gsm0338($message); 33 | ``` 34 | 35 | And send it! 36 | 37 | The TON and NPI of source and destination address are setting automatically, so you don't need set it. 38 | 39 | This library send enquire link to SMPP server every 10 seconds, you need to change this value according to timeout of connection from SMPP Server. Majority libraries I found send enquire link every second, this is a lack in performance and network (a little) and some SMPP have connection timeout of hours, so if talk with your SMPP provider for ak. 40 | 41 | 42 | ## Receiver 43 | 44 | ```php 45 | "; 49 | require_once "../smpp.php";//SMPP protocol 50 | //connect to the smpp server 51 | $tx=new SMPP('IP_SMSC',PORT); 52 | $tx->debug=true; 53 | 54 | //bind the receiver 55 | //$tx->system_type="WWW"; 56 | $tx->addr_npi=1; 57 | $tx->bindReceiver("username","password"); 58 | 59 | do{ 60 | //read incoming sms 61 | $sms=$tx->readSMS(); 62 | //check sms data 63 | if($sms && !empty($sms['source_addr']) && !empty($sms['destination_addr']) && !empty($sms['short_message'])){ 64 | //send sms for processing in smsadv 65 | $from=$sms['source_addr']; 66 | $to=$sms['destination_addr']; 67 | $message=$sms['short_message']; 68 | //run some processing function for incomming sms 69 | process_message($from,$to,$message); 70 | } 71 | //until we have sms in queue 72 | }while($sms); 73 | //close the smpp connection 74 | $tx->close(); 75 | unset($tx); 76 | //clean any output 77 | ob_end_clean(); 78 | 79 | function process_message($from,$to,$message){ 80 | print "Received SMS\nFrom: $from\nTo: $to\nMsg: $message"; 81 | } 82 | ?> 83 | ``` 84 | -------------------------------------------------------------------------------- /examples/receiver.php: -------------------------------------------------------------------------------- 1 | "; 5 | require_once "../smpp.php";//SMPP protocol 6 | //connect to the smpp server 7 | $tx=new SMPP('192.168.1.90',5018); 8 | $tx->debug=true; 9 | 10 | //bind the receiver 11 | //$tx->system_type="WWW"; 12 | $tx->addr_npi=1; 13 | $tx->bindReceiver("username","password"); 14 | 15 | do{ 16 | //read incoming sms 17 | $sms=$tx->readSMS(); 18 | //check sms data 19 | if($sms && !empty($sms['source_addr']) && !empty($sms['destination_addr']) && !empty($sms['short_message'])){ 20 | //send sms for processing in smsadv 21 | $from=$sms['source_addr']; 22 | $to=$sms['destination_addr']; 23 | $message=$sms['short_message']; 24 | //run some processing function for incomming sms 25 | process_message($from,$to,$message); 26 | } 27 | //until we have sms in queue 28 | }while($sms); 29 | //close the smpp connection 30 | $tx->close(); 31 | unset($tx); 32 | //clean any output 33 | ob_end_clean(); 34 | 35 | function process_message($from,$to,$message){ 36 | print "Received SMS\nFrom: $from\nTo: $to\nMsg: $message"; 37 | } 38 | ?> -------------------------------------------------------------------------------- /examples/transmitter.php: -------------------------------------------------------------------------------- 1 | debug=false; 6 | $tx->bindTransmitter("username","password"); 7 | $result = $tx->sendSMS("2121","999999999","Hello world"); 8 | echo $tx->getStatusMessage($result); 9 | $tx->close(); 10 | unset($tx); 11 | 12 | ?> 13 | -------------------------------------------------------------------------------- /gsmencoder.php: -------------------------------------------------------------------------------- 1 | "\x00", '£' => "\x01", '$' => "\x02", '¥' => "\x03", 'è' => "\x04", 'é' => "\x05", 'ù' => "\x06", 'ì' => "\x07", 'ò' => "\x08", 'Ç' => "\x09", 'Ø' => "\x0B", 'ø' => "\x0C", 'Å' => "\x0E", 'å' => "\x0F", 28 | 'Δ' => "\x10", '_' => "\x11", 'Φ' => "\x12", 'Γ' => "\x13", 'Λ' => "\x14", 'Ω' => "\x15", 'Π' => "\x16", 'Ψ' => "\x17", 'Σ' => "\x18", 'Θ' => "\x19", 'Ξ' => "\x1A", 'Æ' => "\x1C", 'æ' => "\x1D", 'ß' => "\x1E", 'É' => "\x1F", 29 | // all \x2? removed 30 | // all \x3? removed 31 | // all \x4? removed 32 | 'Ä' => "\x5B", 'Ö' => "\x5C", 'Ñ' => "\x5D", 'Ü' => "\x5E", '§' => "\x5F", 33 | '¿' => "\x60", 34 | 'ä' => "\x7B", 'ö' => "\x7C", 'ñ' => "\x7D", 'ü' => "\x7E", 'à' => "\x7F", 35 | '^' => "\x1B\x14", '{' => "\x1B\x28", '}' => "\x1B\x29", '\\' => "\x1B\x2F", '[' => "\x1B\x3C", '~' => "\x1B\x3D", ']' => "\x1B\x3E", '|' => "\x1B\x40", '€' => "\x1B\x65" 36 | ); 37 | $converted = strtr($string, $dict); 38 | 39 | // Replace unconverted UTF-8 chars from codepages U+0080-U+07FF, U+0080-U+FFFF and U+010000-U+10FFFF with a single ? 40 | return preg_replace('/([\\xC0-\\xDF].)|([\\xE0-\\xEF]..)|([\\xF0-\\xFF]...)/m','?',$converted); 41 | } 42 | 43 | /** 44 | * Count the number of GSM 03.38 chars a conversion would contain. 45 | * It's about 3 times faster to count than convert and do strlen() if conversion is not required. 46 | * 47 | * @param string $utf8String 48 | * @return integer 49 | */ 50 | public static function countGsm0338Length($utf8String) 51 | { 52 | $len = mb_strlen($utf8String,'utf-8'); 53 | $len += preg_match_all('/[\\^{}\\\~€|\\[\\]]/mu',$utf8String,$m); 54 | return $len; 55 | } 56 | 57 | /** 58 | * Pack an 8-bit string into 7-bit GSM format 59 | * Returns the packed string in binary format 60 | * 61 | * @param string $data 62 | * @return string 63 | */ 64 | public static function pack7bit($data) 65 | { 66 | $l = strlen($data); 67 | $currentByte = 0; 68 | $offset = 0; 69 | $packed = ''; 70 | for ($i = 0; $i < $l; $i++) { 71 | // cap off any excess bytes 72 | $septet = ord($data[$i]) & 0x7f; 73 | // append the septet and then cap off excess bytes 74 | $currentByte |= ($septet << $offset) & 0xff; 75 | // update offset 76 | $offset += 7; 77 | 78 | if ($offset > 7) { 79 | // the current byte is full, add it to the encoded data. 80 | $packed .= chr($currentByte); 81 | // shift left and append the left shifted septet to the current byte 82 | $currentByte = $septet = $septet >> (7 - ($offset - 8 )); 83 | // update offset 84 | $offset -= 8; // 7 - (7 - ($offset - 8)) 85 | } 86 | } 87 | if ($currentByte > 0) $packed .= chr($currentByte); // append the last byte 88 | 89 | return $packed; 90 | } 91 | } -------------------------------------------------------------------------------- /smpp.php: -------------------------------------------------------------------------------- 1 | lastenquire = 0; 136 | //internal parameters 137 | $this->sequence_number=1; 138 | $this->debug=false; 139 | $this->pdu_queue=array(); 140 | $this->host=$host; 141 | $this->port=intval($port); 142 | $this->state="closed"; 143 | //open the socket 144 | $this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, 10); 145 | if($this->socket)$this->state="open"; 146 | 147 | } 148 | 149 | //Function to know if is necessary send the enquirelink to SMSC 150 | function checkForEnquire(){ 151 | 152 | $now = microtime(true); 153 | 154 | $duration = $now-$this->lastenquire; 155 | $hours = (int)($duration/60/60); 156 | $minutes = (int)($duration/60)-$hours*60; 157 | $seconds = (int)$duration-$hours*60*60-$minutes*60; 158 | 159 | if($this->debug){ 160 | echo 'Seconds from last enquire link = ' . $seconds . PHP_EOL; 161 | } 162 | 163 | if ($seconds >= $this->enquirelink_timeout){ 164 | $this->enquirelink(); 165 | } 166 | } 167 | 168 | /** 169 | * Binds the receiver. One object can be bound only as receiver or only as trancmitter. 170 | * @param $login - ESME system_id 171 | * @param $port - ESME password 172 | * @return true when bind was successful 173 | */ 174 | function bindReceiver($login, $pass){ 175 | 176 | if($this->state!="open")return false; 177 | if($this->debug){ 178 | echo "Binding receiver...\n\n"; 179 | } 180 | $status=$this->_bind($login, $pass, SMPP::BIND_RECEIVER); 181 | if($this->debug){ 182 | echo "Binding status : $status\n\n"; 183 | } 184 | if($status===0)$this->state="bind_rx"; 185 | return ($status===0); 186 | } 187 | 188 | /** 189 | * Binds the transmitter. One object can be bound only as receiver or only as trancmitter. 190 | * @param $login - ESME system_id 191 | * @param $port - ESME password 192 | * @return true when bind was successful 193 | */ 194 | function bindTransmitter($login, $pass){ 195 | 196 | if($this->state!="open")return false; 197 | if($this->debug){ 198 | echo "Binding transmitter...\n\n"; 199 | } 200 | $status=$this->_bind($login, $pass, SMPP::BIND_TRANSMITTER); 201 | if($this->debug){ 202 | echo "Binding status : $status\n\n"; 203 | } 204 | if($status===0)$this->state="bind_tx"; 205 | 206 | return ($status===0); 207 | } 208 | 209 | /** 210 | * Closes the session on the SMSC server. 211 | */ 212 | function close(){ 213 | if($this->state=="closed")return; 214 | if($this->debug){ 215 | echo "Unbinding...\n\n"; 216 | } 217 | $status=$this->sendCommand(SMPP::UNBIND,""); 218 | if($this->debug){ 219 | echo "Unbind status : $status\n\n"; 220 | } 221 | fclose($this->socket); 222 | $this->state="closed"; 223 | } 224 | 225 | /** 226 | * Read one SMS from SMSC. Can be executed only after bindReceiver() call. 227 | * Receiver not send enquirelink 228 | * This method bloks. Method returns on socket timeout or enquire_link signal from SMSC. 229 | * @return sms associative array or false when reading failed or no more sms. 230 | */ 231 | 232 | function readSMS(){ 233 | 234 | if($this->state!="bind_rx")return false; 235 | $command_id=SMPP::DELIVER_SM; 236 | //check the queue 237 | for($i=0;$ipdu_queue);$i++){ 238 | $pdu=$this->pdu_queue[$i]; 239 | if($pdu['id']==$command_id){ 240 | //remove response 241 | array_splice($this->pdu_queue, $i, 1); 242 | return parseSMS($pdu); 243 | } 244 | } 245 | //read pdu 246 | do{ 247 | if($this->debug){ 248 | echo "read sms...\n\n"; 249 | } 250 | $pdu=$this->readPDU(); 251 | //check for enquire link command 252 | if($pdu['id']==SMPP::ENQUIRE_LINK){ 253 | $this->sendPDU(SMPP::ENQUIRE_LINK_RESP, "", $pdu['sn']); 254 | return false; 255 | } 256 | array_push($this->pdu_queue, $pdu); 257 | }while($pdu && $pdu['id']!=$command_id); 258 | if($pdu){ 259 | array_pop($this->pdu_queue); 260 | return $this->parseSMS($pdu); 261 | } 262 | return false; 263 | } 264 | 265 | /** 266 | * Send one SMS from SMSC. Can be executed only after bindTransmitter() call. 267 | * @return true on succesfull send, false if error encountered 268 | */ 269 | function sendSMS($from, $to, $message){ 270 | if (strlen($from)>20 || strlen($to)>20 || strlen($message)>160)return false; 271 | if($this->state!="bind_tx")return false; 272 | 273 | //TON 274 | $this->sms_source_addr_ton = $this->setTon($from); 275 | $this->sms_dest_addr_ton = $this->setTon($to); 276 | 277 | //NPI 278 | $this->sms_source_addr_npi = $this->setNPI($from); 279 | $this->sms_dest_addr_npi = $this->setNPI($to); 280 | 281 | $pdu = pack('a1cca'.(strlen($from)+1).'cca'.(strlen($to)+1).'ccca1a1ccccca'.(strlen($message)+1), 282 | $this->sms_service_type, 283 | $this->sms_source_addr_ton, 284 | $this->sms_source_addr_npi, 285 | $from,//source_addr 286 | $this->sms_dest_addr_ton, 287 | $this->sms_dest_addr_npi, 288 | $to,//destination_addr 289 | $this->sms_esm_class, 290 | $this->sms_protocol_id, 291 | $this->sms_priority_flag, 292 | $this->sms_schedule_delivery_time, 293 | $this->sms_validity_period, 294 | $this->sms_registered_delivery_flag, 295 | $this->sms_replace_if_present_flag, 296 | $this->sms_data_coding, 297 | $this->sms_sm_default_msg_id, 298 | strlen($message),//sm_length 299 | $message//short_message 300 | ); 301 | 302 | //Before each message verify if necessary send the enquirelink to SMSC 303 | $this->checkForEnquire(); 304 | $status=$this->sendCommand(SMPP::SUBMIT_SM,$pdu); 305 | return $status; 306 | } 307 | 308 | //Get text of error 309 | function getStatusMessage($statuscode) 310 | { 311 | if (is_bool($statuscode)) return 'Connection Error'; 312 | 313 | switch ($statuscode) { 314 | case SMPP::ESME_ROK: return 'OK'; 315 | case SMPP::ESME_RINVMSGLEN: return 'Message Length is invalid'; 316 | case SMPP::ESME_RINVCMDLEN: return 'Command Length is invalid'; 317 | case SMPP::ESME_RINVCMDID: return 'Invalid Command ID'; 318 | case SMPP::ESME_RINVBNDSTS: return 'Incorrect BIND Status for given command'; 319 | case SMPP::ESME_RALYBND: return 'ESME Already in Bound State'; 320 | case SMPP::ESME_RINVPRTFLG: return 'Invalid Priority Flag'; 321 | case SMPP::ESME_RINVREGDLVFLG: return 'Invalid Registered Delivery Flag'; 322 | case SMPP::ESME_RSYSERR: return 'System Error'; 323 | case SMPP::ESME_RINVSRCADR: return 'Invalid Source Address'; 324 | case SMPP::ESME_RINVDSTADR: return 'Invalid Dest Addr'; 325 | case SMPP::ESME_RINVMSGID: return 'Message ID is invalid'; 326 | case SMPP::ESME_RBINDFAIL: return 'Bind Failed'; 327 | case SMPP::ESME_RINVPASWD: return 'Invalid Password'; 328 | case SMPP::ESME_RINVSYSID: return 'Invalid System ID'; 329 | case SMPP::ESME_RCANCELFAIL: return 'Cancel SM Failed'; 330 | case SMPP::ESME_RREPLACEFAIL: return 'Replace SM Failed'; 331 | case SMPP::ESME_RMSGQFUL: return 'Message Queue Full'; 332 | case SMPP::ESME_RINVSERTYP: return 'Invalid Service Type'; 333 | case SMPP::ESME_RINVNUMDESTS: return 'Invalid number of destinations'; 334 | case SMPP::ESME_RINVDLNAME: return 'Invalid Distribution List name'; 335 | case SMPP::ESME_RINVDESTFLAG: return 'Destination flag (submit_multi)'; 336 | case SMPP::ESME_RINVSUBREP: return 'Invalid ‘submit with replace’ request (i.e. submit_sm with replace_if_present_flag set)'; 337 | case SMPP::ESME_RINVESMSUBMIT: return 'Invalid esm_SUBMIT field data'; 338 | case SMPP::ESME_RCNTSUBDL: return 'Cannot Submit to Distribution List'; 339 | case SMPP::ESME_RSUBMITFAIL: return 'submit_sm or submit_multi failed'; 340 | case SMPP::ESME_RINVSRCTON: return 'Invalid Source address TON'; 341 | case SMPP::ESME_RINVSRCNPI: return 'Invalid Source address NPI'; 342 | case SMPP::ESME_RINVDSTTON: return 'Invalid Destination address TON'; 343 | case SMPP::ESME_RINVDSTNPI: return 'Invalid Destination address NPI'; 344 | case SMPP::ESME_RINVSYSTYP: return 'Invalid system_type field'; 345 | case SMPP::ESME_RINVREPFLAG: return 'Invalid replace_if_present flag'; 346 | case SMPP::ESME_RINVNUMMSGS: return 'Invalid number of messages'; 347 | case SMPP::ESME_RTHROTTLED: return 'Throttling error (ESME has exceeded allowed message limits)'; 348 | case SMPP::ESME_RINVSCHED: return 'Invalid Scheduled Delivery Time'; 349 | case SMPP::ESME_RINVEXPIRY: return 'Invalid message (Expiry time)'; 350 | case SMPP::ESME_RINVDFTMSGID: return 'Predefined Message Invalid or Not Found'; 351 | case SMPP::ESME_RX_T_APPN: return 'ESME Receiver Temporary App Error Code'; 352 | case SMPP::ESME_RX_P_APPN: return 'ESME Receiver Permanent App Error Code'; 353 | case SMPP::ESME_RX_R_APPN: return 'ESME Receiver Reject Message Error Code'; 354 | case SMPP::ESME_RQUERYFAIL: return 'query_sm request failed'; 355 | case SMPP::ESME_RINVOPTPARSTREAM: return 'Error in the optional part of the PDU Body.'; 356 | case SMPP::ESME_ROPTPARNOTALLWD: return 'Optional Parameter not allowed'; 357 | case SMPP::ESME_RINVPARLEN: return 'Invalid Parameter Length.'; 358 | case SMPP::ESME_RMISSINGOPTPARAM: return 'Expected Optional Parameter missing'; 359 | case SMPP::ESME_RINVOPTPARAMVAL: return 'Invalid Optional Parameter Value'; 360 | case SMPP::ESME_RDELIVERYFAILURE: return 'Delivery Failure (data_sm_resp)'; 361 | case SMPP::ESME_RUNKNOWNERR: return 'Unknown Error'; 362 | default: 363 | return 'Unknown error'; 364 | } 365 | } 366 | 367 | ////////////////private functions/////////////// 368 | 369 | 370 | /* 371 | TON: 372 | Unknown = 0, 373 | International = 1, 374 | National = 2, 375 | NetworkSpecific = 3, 376 | SubscriberNumber = 4, 377 | Alphanumeric = 5, 378 | Abbreviated = 6, 379 | 380 | */ 381 | 382 | function setTon($address){ 383 | 384 | //Check if empty or only number 385 | if (empty($address) || ctype_digit($address)){ 386 | $NationalNumberLenght = 8; 387 | 388 | //If length is 8 then TON is National (2) 389 | if(strlen($address) == $NationalNumberLenght) return 2; //National 390 | 391 | //If empty and length is > 8 then TON is International (1) 392 | if(empty($address) || strlen($address) > $NationalNumberLenght) return 1; //International 393 | } 394 | 395 | //If address is alphanumeric then TON is Alphanumeric (5) 396 | if (!ctype_digit($address)) return 5; //Alphanumeric 397 | 398 | //If no apply condition then TON is Unknown (0) 399 | return 0; //Unknown 400 | 401 | } 402 | 403 | /* 404 | NPI: 405 | Unknown = 0, 406 | ISDN = 1 407 | */ 408 | function setNPI($address){ 409 | 410 | //If address is alphanumeric then NPI is Unknown(0) 411 | if (!ctype_digit($address)) return 0; //Unknown 412 | 413 | //If no apply condition then NPI is ISDN (1) 414 | return 1; //ISDN 415 | 416 | } 417 | 418 | //Send the enquirelink 419 | public function enquireLink() 420 | { 421 | $response = $this->sendCommand(SMPP::ENQUIRE_LINK, ""); 422 | if ($response == 0) { 423 | $this->lastenquire = microtime(true); 424 | if ($this->debug){ 425 | echo "ENQUIRE!!" . PHP_EOL; 426 | } 427 | } 428 | return $response; 429 | } 430 | 431 | /** 432 | * @private function 433 | * Binds the socket and opens the session on SMSC 434 | * @param $login - ESME system_id 435 | * @param $port - ESME password 436 | * @return bind status or false on error 437 | */ 438 | function _bind($login, $pass, $command_id){ 439 | //make PDU 440 | $pdu = pack( 441 | 'a'.(strlen($login)+1). 442 | 'a'.(strlen($pass)+1). 443 | 'a'.(strlen($this->system_type)+1). 444 | 'CCCa'.(strlen($this->address_range)+1), 445 | $login, $pass, $this->system_type, 446 | $this->interface_version, $this->addr_ton, 447 | $this->addr_npi, $this->address_range); 448 | $status=$this->sendCommand($command_id,$pdu); 449 | return $status; 450 | } 451 | /** 452 | * @private function 453 | * Parse deliver PDU from SMSC. 454 | * @param $pdu - deliver PDU from SMSC. 455 | * @return parsed PDU as array. 456 | */ 457 | function parseSMS($pdu){ 458 | //check command id 459 | if($pdu['id']!=SMPP::DELIVER_SM)return false; 460 | //unpack PDU 461 | $ar=unpack("C*",$pdu['body']); 462 | print_r($ar); 463 | $sms=array('service_type'=>$this->getString($ar,6), 464 | 'source_addr_ton'=>array_shift($ar), 465 | 'source_addr_npi'=>array_shift($ar), 466 | 'source_addr'=>$this->getString($ar,21), 467 | 'dest_addr_ton'=>array_shift($ar), 468 | 'dest_addr_npi'=>array_shift($ar), 469 | 'destination_addr'=>$this->getString($ar,21), 470 | 'esm_class'=>array_shift($ar), 471 | 'protocol_id'=>array_shift($ar), 472 | 'priority_flag'=>array_shift($ar), 473 | 'schedule_delivery_time'=>array_shift($ar), 474 | 'validity_period'=>array_shift($ar), 475 | 'registered_delivery'=>array_shift($ar), 476 | 'replace_if_present_flag'=>array_shift($ar), 477 | 'data_coding'=>array_shift($ar), 478 | 'sm_default_msg_id'=>array_shift($ar), 479 | 'sm_length'=>array_shift($ar), 480 | 'short_message'=>$this->getString($ar,255) 481 | ); 482 | if($this->debug){ 483 | echo "Delivered sms:\n"; 484 | print_r($sms); 485 | echo "\n"; 486 | } 487 | //send response of recieving sms 488 | $this->sendPDU(SMPP::DELIVER_SM_RESP, "\0", $pdu['sn']); 489 | return $sms; 490 | } 491 | /** 492 | * @private function 493 | * Sends the PDU command to the SMSC and waits for responce. 494 | * @param $command_id - command ID 495 | * @param $pdu - PDU body 496 | * @return PDU status or false on error 497 | */ 498 | function sendCommand($command_id, $pdu){ 499 | if($this->state=="closed")return false; 500 | $this->sendPDU($command_id, $pdu, $this->sequence_number); 501 | $status=$this->readPDU_resp($this->sequence_number, $command_id); 502 | $this->sequence_number=$this->sequence_number+1; 503 | return $status; 504 | } 505 | /** 506 | * @private function 507 | * Prepares and sends PDU to SMSC. 508 | * @param $command_id - command ID 509 | * @param $pdu - PDU body 510 | * @param $seq_number - PDU sequence number 511 | */ 512 | function sendPDU($command_id, $pdu, $seq_number){ 513 | $length=strlen($pdu) + 16; 514 | $header=pack("NNNN", $length, $command_id, 0, $seq_number); 515 | if($this->debug){ 516 | echo "Send PDU : $length bytes\n"; 517 | $this->printHex($header.$pdu); 518 | echo "command_id : ".$command_id."\n"; 519 | echo "sequence number : $seq_number\n\n"; 520 | } 521 | 522 | $writed = @fwrite($this->socket, $header.$pdu, $length); 523 | 524 | //Close conection if not bind 525 | if ($writed == FALSE) { 526 | if ($this->debug) echo "Lost connection to SMSC" . PHP_EOL; 527 | exit(); 528 | }; 529 | 530 | 531 | } 532 | 533 | /** 534 | * @private function 535 | * Waits for SMSC responce on specific PDU. 536 | * @param $seq_number - PDU sequence number 537 | * @param $command_id - PDU command ID 538 | * @return PDU status or false on error 539 | */ 540 | function readPDU_resp($seq_number, $command_id){ 541 | //create response id 542 | $command_id=$command_id|SMPP::GENERIC_NACK; 543 | //check queue 544 | for($i=0;$ipdu_queue);$i++){ 545 | $pdu=$this->pdu_queue[$i]; 546 | if($pdu['sn']==$seq_number && $pdu['id']==$command_id){ 547 | //remove response 548 | array_splice($this->pdu_queue, $i, 1); 549 | return $pdu['status']; 550 | } 551 | } 552 | //read pdu 553 | do{ 554 | $pdu=$this->readPDU(); 555 | if($pdu)array_push($this->pdu_queue, $pdu); 556 | }while($pdu && ($pdu['sn']!=$seq_number || $pdu['id']!=$command_id)); 557 | //remove response from queue 558 | if($pdu){ 559 | array_pop($this->pdu_queue); 560 | return $pdu['status']; 561 | } 562 | return false; 563 | } 564 | 565 | /** 566 | * @private function 567 | * Reads incoming PDU from SMSC. 568 | * @return readed PDU or false on error. 569 | */ 570 | function readPDU(){ 571 | //read PDU length 572 | $tmp=fread($this->socket, 4); 573 | if(!$tmp)return false; 574 | extract(unpack("Nlength", $tmp)); 575 | //read PDU headers 576 | $tmp2=fread($this->socket, 12); 577 | if(!$tmp2)return false; 578 | extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2)); 579 | //read PDU body 580 | if($length-16>0){ 581 | $body=fread($this->socket, $length-16); 582 | if(!$body)return false; 583 | }else{ 584 | $body=""; 585 | } 586 | if($this->debug){ 587 | echo "Read PDU : $length bytes\n"; 588 | $this->printHex($tmp.$tmp2.$body); 589 | echo "body len : " . strlen($body) . "\n"; 590 | echo "Command id : $command_id\n"; 591 | echo "Command status : $command_status\n"; 592 | echo "sequence number : $sequence_number\n\n"; 593 | } 594 | $pdu=array( 595 | 'id'=>$command_id, 596 | 'status'=>$command_status, 597 | 'sn'=>$sequence_number, 598 | 'body'=>$body); 599 | return $pdu; 600 | } 601 | 602 | /** 603 | * @private function 604 | * Reads C style zero padded string from the char array. 605 | * @param $ar - input array 606 | * @param $maxlen - maximum length to read. 607 | * @return readed string. 608 | */ 609 | function getString(&$ar, $maxlen=255){ 610 | $s=""; 611 | $i=0; 612 | do{ 613 | $c=array_shift($ar); 614 | if($c!=0)$s.=chr($c); 615 | $i++; 616 | }while($i<$maxlen && $c!=0); 617 | return $s; 618 | } 619 | 620 | /** 621 | * @private function 622 | * Prints the binary string as hex bytes. 623 | * @param $maxlen - maximum length to read. 624 | */ 625 | function printHex($pdu){ 626 | $ar=unpack("C*",$pdu); 627 | foreach($ar as $v){ 628 | $s=dechex($v); 629 | if(strlen($s)<2)$s="0$s"; 630 | print "$s "; 631 | } 632 | print "\n"; 633 | } 634 | } 635 | ?> --------------------------------------------------------------------------------