!C99Shell v. 2.5 [PHP 8 Update] [24.05.2025]!

Software: Apache. PHP/8.1.30 

uname -a: Linux server1.tuhinhossain.com 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC
2025 x86_64
 

uid=1002(picotech) gid=1003(picotech) groups=1003(picotech),0(root)  

Safe-mode: OFF (not secure)

/home/picotech/domains/sms.picotech.app/public_html/vendor/alexandr-mironov/php8-smpp/src/   drwxr-xr-x
Free 29.22 GB of 117.98 GB (24.77%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Self remove    Logout    


Viewing file:     Client.php (37.03 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php

declare(strict_types=1);

namespace 
smpp;

use 
DateInterval;
use 
DateTime;
use 
Exception;
use 
smpp\exceptions\ClosedTransportException;
use 
smpp\exceptions\SmppException;
use 
smpp\exceptions\SmppInvalidArgumentException;
use 
smpp\exceptions\SocketTransportException;
use 
smpp\transport\Socket;

/**
 * Class for receiving or sending sms through SMPP protocol.
 * This is a reduced implementation of the SMPP protocol, and as such not all features will or ought to be available.
 * The purpose is to create a lightweight and simplified SMPP client.
 *
 * @author hd@onlinecity.dk, paladin, Alexandr Mironov
 * @see http://en.wikipedia.org/wiki/Short_message_peer-to-peer_protocol - SMPP 3.4 protocol specification
 * Derived from work done by paladin, see: http://sourceforge.net/projects/phpsmppapi/
 *
 * Copyright (C) 2020 Alexandr Mironov
 * Copyright (C) 2011 OnlineCity
 * Copyright (C) 2006 Paladin
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * This license can be read at: http://www.opensource.org/licenses/lgpl-2.1.php
 */
class Client
{
    
// Available modes
    /** @var string */
    
public const MODE_TRANSMITTER 'transmitter';

    
/** @var string */
    
public const MODE_TRANSCEIVER 'transceiver';

    
/** @var string */
    
public const MODE_RECEIVER 'receiver';

    
/** @var integer Use sar_msg_ref_num and sar_total_segments with 16 bit tags */
    
public const CSMS_16BIT_TAGS 0;

    
/** @var integer Use message payload for CSMS */
    
public const CSMS_PAYLOAD 1;

    
/** @var integer Embed a UDH in the message with 8-bit reference. */
    
public const CSMS_8BIT_UDH 2;

    
// SMPP bind parameters
    /** @var string */
    
public static string $systemType "WWW";

    
/** @var int */
    
public static int $interfaceVersion 0x34;

    
/** @var int */
    
public static int $addrTon 0;

    
/** @var int */
    
public static int $addrNPI 0;

    
/** @var string */
    
public static string $addressRange "";

    
// ESME transmitter parameters
    /** @var string */
    
public static string $smsServiceType "";

    
/** @var int */
    
public static int $smsEsmClass 0x00;

    
/** @var int */
    
public static int $smsProtocolID 0x00;

    
/** @var int */
    
public static int $smsPriorityFlag 0x00;

    
/** @var int */
    
public static int $smsRegisteredDeliveryFlag 0x00;

    
/** @var int */
    
public static int $smsReplaceIfPresentFlag 0x00;

    
/** @var int */
    
public static int $smsSmDefaultMessageID 0x00;

    
/**
     * SMPP v3.4 says octet string are "not necessarily NULL terminated".
     * Switch to toggle this feature
     * @var boolean
     *
     * set NULL terminate octetstrings FALSE as default
     */
    
public static bool $smsNullTerminateOctetstrings false;

    
/** @var int */
    
public static int $csmsMethod self::CSMS_16BIT_TAGS;

    
/** @var Pdu[] */
    
protected array $pduQueue = [];

    
// Used for reconnect
    /** @var string */
    
protected string $mode;

    
/** @var string $login Login of SMPP gateway */
    
private string $login '';

    
/** @var string $pass Password of SMPP gateway */
    
private string $pass '';

    
/** @var int */
    
protected int $sequenceNumber 1;

    
/** @var int */
    
protected int $sarMessageReferenceNumber;

    
/** @var LoggerDecorator */
    
public LoggerDecorator $logger;

    
/**
     * Construct the SMPP class
     *
     * @param Socket $transport
     * @param LoggerInterface ...$loggers
     */
    
public function __construct(
        public 
Socket $transport,
        
LoggerInterface ...$loggers
    
)
    {
        
LoggerDecorator::$debug Socket::$defaultDebug;
        
$this->logger = new LoggerDecorator(...$loggers);
    }

    
/**
     * Binds the receiver. One object can be bound only as receiver or only as transmitter.
     * @param string $login - ESME system_id
     * @param string $pass - ESME password
     *
     * @return void
     *
     * @throws SmppException
     * @throws ClosedTransportException
     * @throws Exception
     */
    
public function bindReceiver(string $loginstring $pass): void
    
{
        if (!
$this->transport->isOpen()) {
            throw new 
ClosedTransportException();
        }

        
$this->logger->info('Binding receiver...');

        
$response $this->bind($login$passSmpp::BIND_RECEIVER);

        
$this->logger->info("Binding status  : " $response->status);

        
$this->mode self::MODE_RECEIVER;
        
$this->login $login;
        
$this->pass $pass;
    }

    
/**
     * Binds the transmitter. One object can be bound only as receiver or only as transmitter.
     *
     * @param string $login - ESME system_id
     * @param string $pass - ESME password
     *
     * @return void
     * @throws Exception
     */
    
public function bindTransmitter(string $loginstring $pass): void
    
{
        if (!
$this->transport->isOpen()) {
            throw new 
ClosedTransportException();
        }

        
$this->logger->info('Binding transmitter...');

        
$response $this->bind($login$passSmpp::BIND_TRANSMITTER);

        
$this->logger->info("Binding status  : " $response->status);

        
$this->mode self::MODE_TRANSMITTER;
        
$this->login $login;
        
$this->pass $pass;
    }

    
/**
     * Bind transceiver, this object bound as receiver and transmitter at same time,
     * only if available in SMPP gateway
     *
     * @param string $login - ESME system_id
     * @param string $pass - ESME password
     *
     * @return void
     * @throws Exception
     */
    
public function bindTransceiver(string $loginstring $pass): void
    
{
        if (!
$this->transport->isOpen()) {
            throw new 
ClosedTransportException();
        }

        
$this->logger->info('Binding transciever...');

        
$response $this->bind($login$passSmpp::BIND_TRANSCEIVER);

        
$this->logger->info("Binding status  : " $response->status);

        
$this->mode self::MODE_TRANSCEIVER;
        
$this->login $login;
        
$this->pass $pass;
    }

    
/**
     * Closes the session on the SMSC server.
     *
     * @return void
     * @throws Exception
     */
    
public function close(): void
    
{
        if (!
$this->transport->isOpen()) {
            return;
        }

        
$this->logger->info('Unbinding...');

        
$response $this->sendCommand(Smpp::UNBIND"");

        
$this->logger->info("Unbind status   : " $response->status);

        
$this->transport->close();
    }

    
/**
     * Parse a time string as formatted by SMPP v3.4 section 7.1.
     * Returns an object of either DateTime or DateInterval is returned.
     *
     * @param string $input
     *
     * @return DateTime|DateInterval|null
     *
     * @throws Exception
     */
    
public function parseSmppTime(string $input): null|DateTime|DateInterval
    
{
        if (
            !
preg_match(
                
'/^(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{1})(\\d{2})([R+-])$/',
                
$input,
                
$matches
            
)
        ) {
            return 
null;
        }

        
/**
         * @var int $y
         * @var int $m
         * @var int $d
         * @var int $h
         * @var int $i
         * @var int $s
         * @var int $n
         * @var string $p
         */
        
[$whole$y$m$d$h$i$s$t$n$p] = $matches;

        if (
$p == 'R') {
            
$spec "P";
            if (
$y) {
                
$spec .= $y 'Y';
            }
            if (
$m) {
                
$spec .= $m 'M';
            }
            if (
$d) {
                
$spec .= $d 'D';
            }
            if (
$h || $i || $s) {
                
$spec .= 'T';
            }
            if (
$h) {
                
$spec .= $h 'H';
            }
            if (
$i) {
                
$spec .= $i 'M';
            }
            if (
$s) {
                
$spec .= $s 'S';
            }
            return new 
DateInterval($spec);
        } else {
            
$offsetHours floor($n 4);
            
$offsetMinutes = ($n 4) * 15;
            
// Not Y3K safe
            
$time sprintf(
                
"20%02s-%02s-%02sT%02s:%02s:%02s%s%02s:%02s",
                
$y,
                
$m,
                
$d,
                
$h,
                
$i,
                
$s,
                
$p,
                
$offsetHours,
                
$offsetMinutes
            
);
            return new 
DateTime($time);
        }
    }

    
/**
     * Query the SMSC about current state/status of a previous sent SMS.
     * You must specify the SMSC assigned message id and source of the sent SMS.
     * Returns an associative array with elements: message_id, final_date, message_state and error_code.
     *    message_state would be one of the SMPP::STATE_* constants. (SMPP v3.4 section 5.2.28)
     *    error_code depends on the telco network, so could be anything.
     *
     * @param string $messageID
     * @param Address $source
     *
     * @return null|array<string, mixed>
     *
     * @throws Exception
     */
    
public function queryStatus(string $messageIDAddress $source): null|array
    {
        
$pduBody pack(
            
'a' . (strlen($messageID) + 1) . 'cca' . (strlen($source->value) + 1),
            
$messageID,
            
$source->ton,
            
$source->npi,
            
$source->value
        
);

        
$reply $this->sendCommand(Smpp::QUERY_SM$pduBody);

        if (
$reply->status !== Smpp::ESME_ROK) {
            return 
null;
        }

        
// Parse reply
        
$posID strpos($reply->body"\0"0);
        
$posDate strpos($reply->body"\0"$posID 1);

        if (
$posID === false) {
            
// todo: replace exception and add message
            
throw new Exception();
        }

        
$data = [
            
'message_id' => substr($reply->body0$posID),
            
'final_date' => substr($reply->body$posID, (int)$posDate $posID),
        ];
        
$data['final_date'] = $data['final_date'] ? $this->parseSmppTime(trim($data['final_date'])) : null;
        
/** @var false|array{message_state: mixed, error_code: mixed} $status */
        
$status unpack("cmessage_state/cerror_code"substr($reply->body$posDate 1));

        if (!
$status) {
            
// todo: replace exception and add message
            
throw new Exception();
        }

        return 
array_merge($data$status);
    }

    
/**
     * Read one SMS from SMSC. Can be executed only after bindReceiver() call.
     * This method blocks. Method returns on socket timeout or enquire_link signal from SMSC.
     *
     * @return DeliveryReceipt|Sms|bool
     * @throws Exception
     */
    
public function readSMS(): bool|DeliveryReceipt|Sms
    
{
        
// Check the queue
        
$queueLength count($this->pduQueue);
        for (
$i 0$i $queueLength$i++) {
            
$pdu $this->pduQueue[$i];
            if (
$pdu->id === Smpp::DELIVER_SM) {
                
//remove response
                
array_splice($this->pduQueue$i1);
                return 
$this->parseSMS($pdu);
            }
        }
        
// Read pdu
        
do {
            
$pdu $this->readPDU();
            if (
$pdu === false) {
                return 
false;
            } 
// TSocket v. 0.6.0+ returns false on timeout
            //check for enquire link command
            
if ($pdu->id === Smpp::ENQUIRE_LINK) {
                
$response = new Pdu(Smpp::ENQUIRE_LINK_RESPSmpp::ESME_ROK$pdu->sequence"\x00");
                
$this->sendPDU($response);
            } else if (
$pdu->id !== Smpp::DELIVER_SM) { // if this is not the correct PDU add to queue
                
array_push($this->pduQueue$pdu);
            }
        } while (
$pdu->id !== Smpp::DELIVER_SM);

        return 
$this->parseSMS($pdu);
    }

    
/**
     * Send one SMS to SMSC. Can be executed only after bindTransmitter() call.
     * $message is always in octets regardless of the data encoding.
     * For correct handling of Concatenated SMS,
     * message must be encoded with GSM 03.38 (data_coding 0x00) or UCS-2BE (0x08).
     * Concatenated SMS'es uses 16-bit reference numbers, which gives 152 GSM 03.38 chars or 66 UCS-2BE chars per CSMS.
     * If we are using 8-bit ref numbers in the UDH for CSMS it's 153 GSM 03.38 chars
     *
     * @param Address $from
     * @param Address $to
     * @param string $message
     * @param Tag[]|null $tags (optional)
     * @param int $dataCoding (optional)
     * @param int $priority (optional)
     * @param null $scheduleDeliveryTime (optional)
     * @param null $validityPeriod (optional)
     *
     * @return bool|string message id
     *
     * @throws Exception
     */
    
public function sendSMS(
        
Address $from,
        
Address $to,
        
string $message,
        array 
$tags null,
        
int $dataCoding Smpp::DATA_CODING_DEFAULT,
        
int $priority 0x00,
        
$scheduleDeliveryTime null,
        
$validityPeriod null
    
): bool|string
    
{
        
$messageLength strlen($message);

        if (
$messageLength 160 && !in_array($dataCoding, [Smpp::DATA_CODING_UCS2Smpp::DATA_CODING_DEFAULT])) {
            return 
false;
        }

        switch (
$dataCoding) {
            case 
Smpp::DATA_CODING_UCS2:
                
// in octets, 70 UCS-2 chars
                
$singleSmsOctetLimit 140;
                
// There are 133 octets available, but this would split the UCS the middle so use 132 instead
                
$csmsSplit 132;
                
$message mb_convert_encoding($message'UCS-2');
                
//Update message length with current encoding
                
$messageLength mb_strlen($message);
                break;
            case 
Smpp::DATA_CODING_DEFAULT:
                
// we send data in octets, but GSM 03.38 will be packed in septets (7-bit) by SMSC.
                
$singleSmsOctetLimit 160;
                
// send 152/153 chars in each SMS (SMSC will format data)
                
$csmsSplit = (self::$csmsMethod == self::CSMS_8BIT_UDH) ? 153 152;
                break;
            default:
                
$singleSmsOctetLimit 254// From SMPP standard
                
break;
        }

        
// Figure out if we need to do CSMS, since it will affect our PDU
        
if ($messageLength $singleSmsOctetLimit) {
            
$doCsms true;
            if (
self::$csmsMethod != self::CSMS_PAYLOAD) {
                
$parts $this->splitMessageString($message$csmsSplit$dataCoding);
                
$shortMessage reset($parts);
                
$csmsReference $this->getCsmsReference();
            }
        } else {
            
$shortMessage $message;
            
$doCsms false;
        }

        
// Deal with CSMS
        
if ($doCsms) {
            if (
self::$csmsMethod == self::CSMS_PAYLOAD) {
                
$payload = new Tag(Tag::MESSAGE_PAYLOAD$message$messageLength);
                
// todo: replace array to k=>v storage (Collection??), where key is tag id
                
$tags[] = $payload;
                return 
$this->submitShortMessage(
                    
$from,
                    
$to,
                    
null,
                    
$tags,
                    
$dataCoding,
                    
$priority,
                    
$scheduleDeliveryTime,
                    
$validityPeriod
                
);
            } elseif (
self::$csmsMethod == self::CSMS_8BIT_UDH) {
                
$sequenceNumber 1;
                foreach (
$parts as $part) {
                    
$udh pack(
                        
'cccccc',
                        
5,
                        
0,
                        
3,
                        
substr((string)$csmsReference11),
                        
count($parts),
                        
$sequenceNumber
                    
);
                    
$res $this->submitShortMessage(
                        
$from,
                        
$to,
                        
$udh $part,
                        
$tags,
                        
$dataCoding,
                        
$priority,
                        
$scheduleDeliveryTime,
                        
$validityPeriod,
                        (string)(
self::$smsEsmClass 0x40//todo: check this
                    
);
                    
$sequenceNumber++;
                }
                return 
$res;
            } else {
                
$sarMessageRefNumber = new Tag(Tag::SAR_MSG_REF_NUM$csmsReference2'n');
                
$sarTotalSegments = new Tag(Tag::SAR_TOTAL_SEGMENTScount($parts), 1'c');
                
$sequenceNumber 1;
                foreach (
$parts as $part) {
                    
$sartags = [
                        
$sarMessageRefNumber,
                        
$sarTotalSegments,
                        new 
Tag(Tag::SAR_SEGMENT_SEQNUM$sequenceNumber1'c')
                    ];
                    
$res $this->submitShortMessage(
                        
$from,
                        
$to,
                        (string)
$part,
                        (empty(
$tags) ? $sartags array_merge($tags$sartags)),
                        
$dataCoding,
                        
$priority,
                        
$scheduleDeliveryTime,
                        
$validityPeriod
                    
);
                    
$sequenceNumber++;
                }
                return 
$res;
            }
        }

        return 
$this->submitShortMessage($from$to, (string)($shortMessage ?? ''), $tags$dataCoding$priority);
    }

    
/**
     * Perform the actual submit_sm call to send SMS.
     * Implemented as a protected method to allow automatic sms concatenation.
     * Tags must be an array of already packed and encoded TLV-params.
     *
     * @param Address $source
     * @param Address $destination
     * @param string|null $shortMessage
     * @param Tag[]|null $tags
     * @param integer $dataCoding
     * @param integer $priority
     * @param string|null $scheduleDeliveryTime
     * @param string|null $validityPeriod
     * @param string|null $esmClass
     *
     * @return string message id
     *
     * @throws Exception
     */
    
protected function submitShortMessage(
        
Address $source,
        
Address $destination,
        
string $shortMessage null,
        array 
$tags null,
        
int $dataCoding Smpp::DATA_CODING_DEFAULT,
        
int $priority 0x00,
        
string $scheduleDeliveryTime null,
        
string $validityPeriod null,
        
string $esmClass null
    
): string
    
{
        if (
is_null($esmClass)) {
            
$esmClass self::$smsEsmClass;
        }

        
$shortMessageLength strlen($shortMessage);
        
// Construct PDU with mandatory fields
        
$pdu pack(
            
'a1cca' . (strlen($source->value) + 1)
            . 
'cca' . (strlen($destination->value) + 1)
            . 
'ccc' . ($scheduleDeliveryTime 'a16x' 'a1') . ($validityPeriod 'a16x' 'a1')
            . 
'ccccca' . ($shortMessageLength + (self::$smsNullTerminateOctetstrings 0)),
            
self::$smsServiceType,
            
$source->ton,
            
$source->npi,
            
$source->value,
            
$destination->ton,
            
$destination->npi,
            
$destination->value,
            
$esmClass,
            
self::$smsProtocolID,
            
$priority,
            
$scheduleDeliveryTime,
            
$validityPeriod,
            
self::$smsRegisteredDeliveryFlag,
            
self::$smsReplaceIfPresentFlag,
            
$dataCoding,
            
self::$smsSmDefaultMessageID,
            
$shortMessageLength//sm_length
            
$shortMessage //short_message
        
);

        
// Add any tags
        
if (!empty($tags)) {
            foreach (
$tags as $tag) {
                
$pdu .= $tag->getBinary();
            }
        }

        
$response $this->sendCommand(Smpp::SUBMIT_SM$pdu);
        
/** @var array{msgid: string}|false $body */
        
$body unpack("a*msgid"$response->body);
        if (!
$body) {
            throw new 
SmppException('unable to unpack response body:' $response->body);
        }
        return 
$body['msgid'];
    }

    
/**
     * Get a CSMS reference number for sar_msg_ref_num.
     * Initializes with a random value, and then returns the number in sequence with each call.
     *
     * @return int
     */
    
protected function getCsmsReference(): int
    
{
        
$limit = (self::$csmsMethod == self::CSMS_8BIT_UDH) ? 255 65535;
        if (!isset(
$this->sarMessageReferenceNumber)) {
            
$this->sarMessageReferenceNumber mt_rand(0$limit);
        }
        
$this->sarMessageReferenceNumber++;

        if (
$this->sarMessageReferenceNumber $limit) {
            
$this->sarMessageReferenceNumber 0;
        }
        return 
$this->sarMessageReferenceNumber;
    }


    
/**
     * Split a message into multiple parts, taking the encoding into account.
     * A character represented by an GSM 03.38 escape-sequence shall not be split in the middle.
     * Uses str_split if at all possible, and will examine all split points for escape chars if it's required.
     *
     * @param string $message
     * @param int<1,max> $split
     * @param integer $dataCoding (optional)
     *
     * @return array<int|string>
     */
    
protected function splitMessageString(
        
string $message,
        
int $split,
        
int $dataCoding Smpp::DATA_CODING_DEFAULT
    
): array
    {
        switch (
$dataCoding) {
            case 
Smpp::DATA_CODING_DEFAULT:
                
$messageLength strlen($message);
                
// Do we need to do php based split?
                
$numParts floor($messageLength $split);
                if (
$messageLength $split == 0) {
                    
$numParts--;
                }
                
$slowSplit false;

                for (
$i 1$i <= $numParts$i++) {
                    if (
$message[$i $split 1] == "\x1B") {
                        
$slowSplit true;
                        break;
                    }
                }
                if (!
$slowSplit) {
                    return 
str_split($message$split);
                }

                
// Split the message char-by-char
                
$parts = [];
                
$part null;
                
$n 0;
                for (
$i 0$i $messageLength$i++) {
                    
$c $message[$i];
                    
// reset on $split or if last char is a GSM 03.38 escape char
                    
if ($n == $split || ($n == ($split 1) && $c == "\x1B")) {
                        
$parts[] = $part;
                        
$n 0;
                        
$part null;
                    }
                    
$part .= $c;
                }
                
$parts[] = $part;
                return 
$parts;
            
/**
             * UCS2-BE can just use str_split since we send 132 octets per message,
             * which gives a fine split using UCS2
             */
            
case Smpp::DATA_CODING_UCS2:
            default:
                return 
str_split($message$split);
        }
    }

    
/**
     * Binds the socket and opens the session on SMSC
     *
     * @param string $login - ESME system_id
     * @param string $pass
     * @param int $commandID (todo replace to ENUM in php 8.1)
     *
     * @return Pdu
     *
     * @throws Exception
     */
    
protected function bind(string $loginstring $passint $commandID): Pdu
    
{
        
// Make PDU body
        
$pduBody pack(
            
'a' . (strlen($login) + 1)
            . 
'a' . (strlen($pass) + 1)
            . 
'a' . (strlen(self::$systemType) + 1)
            . 
'CCCa' . (strlen(self::$addressRange) + 1),
            
$login,
            
$pass,
            
self::$systemType,
            
self::$interfaceVersion,
            
self::$addrTon,
            
self::$addrNPI,
            
self::$addressRange
        
);

        
$response $this->sendCommand($commandID$pduBody);
        if (
$response->status != Smpp::ESME_ROK) {
            throw new 
SmppException(Smpp::getStatusMessage($response->status), $response->status);
        }

        return 
$response;
    }

    
/**
     * Parse received PDU from SMSC.
     * @param Pdu $pdu - received PDU from SMSC.
     *
     * @return DeliveryReceipt|Sms parsed PDU as array.
     *
     * @throws Exception
     */
    
protected function parseSMS(Pdu $pdu): DeliveryReceipt|Sms
    
{
        
// Check command id
        
if ($pdu->id != Smpp::DELIVER_SM) {
            throw new 
SmppInvalidArgumentException('PDU is not an received SMS');
        }

        
// Unpack PDU
        
$ar unpack("C*"$pdu->body);

        if (!
$ar) {
            throw new 
SmppException(''); // todo: update message
        
}

        
// Read mandatory params
        
$serviceType $this->getString($ar6true);

        
//
        
$sourceAddrTon next($ar);
        
$sourceAddrNPI next($ar);
        
$sourceAddr $this->getString($ar21);
        
$source = new Address($sourceAddr$sourceAddrTon$sourceAddrNPI);

        
//
        
$destinationAddrTon next($ar);
        
$destinationAddrNPI next($ar);
        
$destinationAddr $this->getString($ar21);
        
$destination = new Address($destinationAddr$destinationAddrTon$destinationAddrNPI);

        
$esmClass next($ar);
        
$protocolId next($ar);
        
$priorityFlag next($ar);
        
next($ar); // schedule_delivery_time
        
next($ar); // validity_period
        
$registeredDelivery next($ar);
        
next($ar); // replace_if_present_flag
        
$dataCoding next($ar);
        
next($ar); // sm_default_msg_id
        
$sm_length next($ar);
        
$message $this->getString($ar$sm_length);

        
// Check for optional params, and parse them
        
if (current($ar) !== false) {
            
$tags = [];
            do {
                
$tag $this->parseTag($ar);
                if (
$tag !== false) {
                    
$tags[] = $tag;
                }
            } while (
current($ar) !== false);
        } else {
            
$tags null;
        }

        if ((
$esmClass Smpp::ESM_DELIVER_SMSC_RECEIPT) != 0) {
            
$sms = new DeliveryReceipt(
                
$pdu->id,
                
$pdu->status,
                
$pdu->sequence,
                
$pdu->body,
                
$serviceType,
                
$source,
                
$destination,
                
$esmClass,
                
$protocolId,
                
$priorityFlag,
                
$registeredDelivery,
                
$dataCoding,
                
$message,
                
$tags
            
);
            
$sms->parseDeliveryReceipt();
        } else {
            
$sms = new Sms(
                
$pdu->id,
                
$pdu->status,
                
$pdu->sequence,
                
$pdu->body,
                
$serviceType,
                
$source,
                
$destination,
                
$esmClass,
                
$protocolId,
                
$priorityFlag,
                
$registeredDelivery,
                
$dataCoding,
                
$message,
                
$tags
            
);
        }

        
$this->logger->info("Received sms:\n" print_r($smstrue));

        
// Send response of recieving sms
        
$response = new Pdu(Smpp::DELIVER_SM_RESPSmpp::ESME_ROK$pdu->sequence"\x00");
        
$this->sendPDU($response);
        return 
$sms;
    }

    
/**
     * Send the enquire link command.
     * @return Pdu
     * @throws Exception
     */
    
public function enquireLink(): Pdu
    
{
        return 
$this->sendCommand(Smpp::ENQUIRE_LINKnull);
    }

    
/**
     * Respond to any enquire link we might have waiting.
     * If will check the queue first and respond to any enquire links we have there.
     * Then it will move on to the transport, and if the first PDU is enquire link respond,
     * otherwise add it to the queue and return.
     *
     */
    
public function respondEnquireLink(): void
    
{
        
// Check the queue first
        
$queueLength count($this->pduQueue);
        for (
$i 0$i $queueLength$i++) {
            
$pdu $this->pduQueue[$i];
            if (
$pdu->id == Smpp::ENQUIRE_LINK) {
                
//remove response
                
array_splice($this->pduQueue$i1);
                
$this->sendPDU(new Pdu(Smpp::ENQUIRE_LINK_RESPSmpp::ESME_ROK$pdu->sequence"\x00"));
            }
        }

        
// Check the transport for data
        
if ($this->transport->hasData()) {
            
$pdu $this->readPDU();
            if (
$pdu && $pdu->id == Smpp::ENQUIRE_LINK) {
                
$this->sendPDU(new Pdu(Smpp::ENQUIRE_LINK_RESPSmpp::ESME_ROK$pdu->sequence"\x00"));
            } elseif (
$pdu) {
                
array_push($this->pduQueue$pdu);
            }
        }
    }

    
/**
     * Reconnect to SMSC.
     * This is mostly to deal with the situation were we run out of sequence numbers
     *
     * @throws SmppException|Exception
     */
    
protected function reconnect(): void
    
{
        
$this->close();
        
sleep(1);
        
$this->transport->open();
        
$this->sequenceNumber 1;

        match (
$this->mode) {
            
self::MODE_TRANSMITTER => $this->bindTransmitter($this->login$this->pass),
            
self::MODE_RECEIVER => $this->bindReceiver($this->login$this->pass),
            
self::MODE_TRANSCEIVER => $this->bindTransceiver($this->login$this->pass),
            default => throw new 
SmppException('Invalid mode: ' $this->mode)
        };
    }

    
/**
     * Sends the PDU command to the SMSC and waits for response.
     * @param int $id - command ID
     * @param ?string $pduBody - PDU body
     * @return Pdu
     *
     * @throws Exception
     */
    
protected function sendCommand(int $id, ?string $pduBody): Pdu
    
{
        if (!
$this->transport->isOpen()) {
            throw new 
SocketTransportException('Socket is closed');
            
//return false;
        
}
        
$pdu = new Pdu($id0$this->sequenceNumber$pduBody);
        
$this->sendPDU($pdu);
        
$response $this->readPduResponse($this->sequenceNumber$pdu->id);

        if (
$response === false) {
            throw new 
SmppException('Failed to read reply to command: 0x' dechex($id));
        }

        if (
$response->status != Smpp::ESME_ROK) {
            throw new 
SmppException(Smpp::getStatusMessage($response->status), $response->status);
        }

        
$this->sequenceNumber++;

        
// Reached max sequence number, spec does not state what happens now, so we re-connect
        
if ($this->sequenceNumber >= 0x7FFFFFFF) {
            
$this->reconnect();
        }

        return 
$response;
    }

    
/**
     * Prepares and sends PDU to SMSC.
     * @param Pdu $pdu
     * @throws Exception
     */
    
protected function sendPDU(Pdu $pdu): void
    
{
        
$length strlen($pdu->body) + 16;
        
$header pack("NNNN"$length$pdu->id$pdu->status$pdu->sequence);

        
$this->logger->info("Read PDU         : $length bytes");
        
$this->logger->info(' ' chunk_split(bin2hex($header $pdu->body), 2" "));
        
$this->logger->info(' command_id      : 0x' dechex($pdu->id));
        
$this->logger->info(' sequence number : ' $pdu->sequence);

        
$this->transport->write($header $pdu->body$length);
    }

    
/**
     * Waits for SMSC response on specific PDU.
     * If a GENERIC_NACK with a matching sequence number, or null sequence is received instead it's also accepted.
     * Some SMPP servers, ie. logica returns GENERIC_NACK on errors.
     *
     * @param int $sequenceNumber - PDU sequence number
     * @param int $commandID - PDU command ID
     *
     * @return Pdu|false
     * @throws SmppException
     */
    
protected function readPduResponse(int $sequenceNumberint $commandID): Pdu|false
    
{
        
// Get response cmd id from command ID
        
$commandID $commandID Smpp::GENERIC_NACK;

        
// Check the queue first
        
$queueLength count($this->pduQueue);
        for (
$i 0$i $queueLength$i++) {
            
$pdu $this->pduQueue[$i];
            if (
                (
$pdu->sequence == $sequenceNumber && ($pdu->id == $commandID || $pdu->id == Smpp::GENERIC_NACK))
                ||
                (
$pdu->sequence == null && $pdu->id == Smpp::GENERIC_NACK)
            ) {
                
// remove response pdu from queue
                
array_splice($this->pduQueue$i1);
                return 
$pdu;
            }
        }

        
// Read PDUs until the one we are looking for shows up, or a generic nack pdu with matching sequence or null sequence
        
do {
            
$pdu $this->readPDU();
            if (
$pdu) {
                if (
                    
$pdu->sequence == $sequenceNumber
                    
&& ($pdu->id == $commandID || $pdu->id == Smpp::GENERIC_NACK)
                ) {
                    return 
$pdu;
                }
                if (
$pdu->sequence == null && $pdu->id == Smpp::GENERIC_NACK) {
                    return 
$pdu;
                }
                
array_push($this->pduQueue$pdu); // unknown PDU push to queue
            
}
        } while (
$pdu);
        return 
false;
    }

    
/**
     * Reads incoming PDU from SMSC.
     * @return false|Pdu
     */
    
protected function readPDU(): Pdu|false
    
{
        
// Read PDU length
        
$bufLength $this->transport->read(4);
        if (!
$bufLength) {
            return 
false;
        }

        
$extract unpack("Nlength"$bufLength);
        if (!
$extract) {
            throw new 
SmppException('unable to unpack string');
        }
        
/**
         * extraction define next variables:
         * @var $length
         */
        
extract($extract);

        
// Read PDU headers
        
$bufHeaders $this->transport->read(12);
        if (!
$bufHeaders) {
            return 
false;
        }

        
$extract unpack("Ncommand_id/Ncommand_status/Nsequence_number"$bufHeaders);
        if (!
$extract) {
            throw new 
SmppException('unable to unpack string');
        }
        
/**
         * @var $command_id
         * @var $command_status
         * @var $sequence_number
         */
        
extract($extract);

        if (!isset(
$command_id$command_status$sequence_number$length)) {
            return 
false// todo: maybe replace to exception??
        
}

        
// Read PDU body
        
$bodyLength $length 16;
        if (
$bodyLength 0) {
            if (!
$body $this->transport->readAll($bodyLength)) {
                throw new 
SmppException('Could not read PDU body');
            }
        } else {
            
$body null;
        }

        
$this->logger->info("Read PDU         : $length bytes");
        
$this->logger->info(' ' chunk_split(bin2hex($bufLength $bufHeaders $body), 2" "));
        
$this->logger->info(" command id      : 0x" dechex($command_id));
        
$this->logger->info(" command status  : 0x" dechex($command_status) . " " Smpp::getStatusMessage($command_status));
        
$this->logger->info(' sequence number : ' $sequence_number);

        return new 
Pdu($command_id$command_status$sequence_number$body);
    }

    
/**
     * Reads C style null padded string from the char array.
     * Reads until $maxlen or null byte.
     *
     * @param array<mixed> $ar - input array
     * @param integer $maxLength - maximum length to read.
     * @param boolean $firstRead - is this the first bytes read from array?
     * @return string.
     */
    
protected function getString(array &$arint $maxLength 255bool $firstRead false): string
    
{
        
$s "";
        
$i 0;
        do {
            
$c = ($firstRead && $i == 0) ? current($ar) : next($ar);
            if (
$c != 0$s .= chr($c);
            
$i++;
        } while (
$i $maxLength && $c != 0);
        return 
$s;
    }

    
/**
     * Read a specific number of octets from the char array.
     * Does not stop at null byte
     *
     * @param array<mixed> $ar - input array
     * @param int $length
     * @return string
     */
    
protected function getOctets(array &$arint $length): string
    
{
        
$s "";
        for (
$i 0$i $length$i++) {
            
$c next($ar);
            if (
$c === false) {
                return 
$s;
            }
            
$s .= chr($c);
        }
        return 
$s;
    }

    
/**
     * @param array<mixed> $ar
     * @return false|Tag
     */
    
protected function parseTag(array &$ar): false|Tag
    
{
        
$unpackedData unpack(
            
'nid/nlength',
            
pack("C2C2"next($ar), next($ar), next($ar), next($ar))
        );

        if (!
$unpackedData) {
            throw new 
SmppInvalidArgumentException('Could not read tag data');
        }
        
/**
         * Extraction create variables:
         * @var $length
         * @var $id
         */
        
extract($unpackedData);

        
// Sometimes SMSC return an extra null byte at the end
        
if (!isset($id$length) || ($length == && $id == 0)) {
            return 
false;
        }

        
$value $this->getOctets($ar$length);
        
$tag = new Tag($id$value$length);

        
$this->logger->info("Parsed tag:");
        
$this->logger->info(" id     :0x" dechex($tag->id));
        
$this->logger->info(" length :" $tag->length);
        
$this->logger->info(" value  :" chunk_split(bin2hex((string)$tag->value), 2" "));

        return 
$tag;
    }
}

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ ok ]

:: Make Dir ::
 
[ ok ]
:: Make File ::
 
[ ok ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0152 ]--