minhO
minhO

Reputation: 31

How to build concatenated sms pdu? Getting junk chars

I'm trying to build some PHP code to send SMS through telnet to a SIM server, but I'm having trouble in sending concatenated messages.

I've read some things about using padding bits to make the coded message septets into octets, but I don't fully understand how it works.

I have a class that receives the phone number, the message (already split in 153chars maximum), the total number of SMSs and the order number of the present part of text.

It works as long as I have that '20' added before the $hexmessage. But I get a junk char in the beginning of the first part (before the 1st letter of my msg), and the same junk char replacing the first letter of the second part! (using '20' so it would show a blank space, but it shows a triangle)

I can't understand why, or what I'd had to change for it to work properly.

I hope that someone can help me understand what am I doing wrong.

Here's what I've got so far:

<?php

 // Generate PDU string
public function generatePDUm($receiverNumber,$message, $sms_count, $msg_nr) {

    //Append filler digit if number is national
    if( strlen($receiverNumber)==9){
        $nacional=1;
        $receiverNumber = $receiverNumber."F";                              
        $network = substr($receiverNumber, 0, 2);                       //NETWORK code, used to decide the SIM Card to be used

    //Check for international flags and set the number type accordingly
    }else{
        $nacional=0;
        if(substr($receiverNumber, 0, 1)=='+'){
            $network = substr($receiverNumber, 4, 2);                   //NETWORK code, used to decide the SIM Card to be used 
            $receiverNumber = substr($receiverNumber, 1, 12);           //remove international indicator    
        } 
        else if(substr($receiverNumber, 0, 2)== '00'){
            $network = substr($receiverNumber, 5, 2);                   //NETWORK code, used to decide the SIM Card to be used 
            $receiverNumber = substr($receiverNumber, 2, 12);           //remove international indicator
        }

    }

    /* Flag the network to be used */
    switch ($network){
        case  "92":
            $network="TMN";
            break;

        case  "96":
            $network="TMN";
            break;

        case "91":
            $network="VODAFONE";
            break;

        case "93":
            $network="OPTIMUS";
            break;
    }
    // Receiver number must be 10 characters long ('national nr' + filler digit) or less than 13 ('351'+'national nr'). (Portugal)
    if( strlen($receiverNumber) < 10 || strlen($receiverNumber) > 12) {
        // Error, not 10 or over 12 numbers long (Code 1)
        $this->setErrorCode(1);
        return false;
    }

    // Message must be 2 characters long at least
    if( strlen($message) < 2 ) {
        // Error, message too short (Code 2)
        $this->setErrorCode(2);
        return false;
    }

    // Message can't be longer than 153 characters. 3SMS.
    if( strlen($message) > 153 ) {
        // Error, message too long (Code 3)
        $this->setErrorCode(3);
        return false;
    }

    // Length of servicecenter number (00 = automatically fixed by phone)
    $serviceCenterNumberLength = '00';

    // SMS-? : 04=sms-deliver(recieve), 11=sms-submit, 01 = dont know but it works, 41 = SMS-SUBMIT + UDH bit (for extended/concatenated SMS)
    // You can try to change this if your phone does not work with 01 command try to use 11 command
    $smsType = '41';

    // TP Message Reference: (placeholder), let the phone set the message reference number itself
    $messageRef = '00';

    // Number length. If national -> 9, if international -> 12
    if($nacional==1){
        $numberLength = '09';
    }else{
        $numberLength = '0C';
    }


    // Type of phone adress: (81=Unknown=10dec, 91=InternationalFormat, 92=National?)
    if($nacional==1){
        $numberType = '81';
    }else{
        $numberType = '91';
    }

    // Get the PDU version of the number
    $number = $this->getNumberAsPDU( $receiverNumber );

    // TP-PID (Protocol Identifier)
    $protocolId = '00';

    // TP-DCS (Data coding scheme)
    $dataCodingScheme = '00';

    // TP-Validity-Period (timestamp), AA=4days expiry, disabled for SonyEricsson support.
   // $validityPeriod = 'A0';
    // $validityPeriod = 'AA'; // Add this if the PDU command fails

    /*user data header information (05 - User Data header info length 
    *                               00 - Information element identifier for a concatenated short message
    *                               03 - Information element data length
    *                               00 - Reference number, auto
    *                               0.$sms_count - total SMS nr
    *                               0.$msg_nr    - current SMS order */
    $udhi = '050003000'.$sms_count.'0'.$msg_nr;
   // echo 'UDHinfo: '.$udhi."\n";

    // Data length of message (in hex format)
    $dataLength = $this->strToHexLen($message);
   // echo 'DATA LENGHT: '.$dataLength."\n\n";

    // Convert message, string > 7bits > 8bits > hex
    $hexMessage = $this->bit7tohex( $this->strto7bit( $message ) );


    // Create the complete PDU string
    $pdu = $serviceCenterNumberLength . $smsType . $messageRef . $numberLength .
            $numberType . $number . $protocolId . $dataCodingScheme . $dataLength .
            $udhi . '20' .$hexMessage;

    /*
     * Generate the length of var $pdu (pdu/2 minus 1) as pdu format requests
     * The -1 is because we don't count the first two characters '00', needed for this command: 'cmgs=24'
     */
    $cmgslen = strlen($pdu)/2-1;

    // Build data array to return with required information
    $data = array();
    $data['pdu'] = $pdu;
    $data['cmgslen'] = $cmgslen;
    $data['rede'] = $network;

    // Return the data array with PDU information
    return $data;
}


// Generate PDU formatted cellphone number
private function getNumberAsPDU($number) {

        // Length of number divided by 2 handle two characters each time
    $length = strlen( $number )/2;
    // Set counter to 1 for strlen
    $i = 1;
    $pduNumber = '';

    // Loop to handle every 2 characters of the phone number. 06 12 34 56 78
    while ($i <= $length) {
        // Get 2 characters of the complete string depending on the number of the current loop.
        // Then reverse these 2 characters and put them in var $pduNumber (06 = 60)
        $pduNumber .= strrev( substr( $number,$i*2-2,2) );
        // Counter + 1
        $i++;
    }

    // Return the generated number
    return $pduNumber;
}


/* Function to convert ascii character to 8 bits
 * Much more efficient than holding a complete ASCII table
 * Thanks to Mattijs F.
 */
private function asc2bin($input, $length=8) {

    $bin_out = '';
    // Loop through every character in the string
    for($charCount=0; $charCount < strlen($input); $charCount++) {
        $charAscii = ord($input{$charCount}); // ascii value of character
        $charBinary = decbin($charAscii); // decimal to binary
        $charBinary = str_pad($charBinary, $length, '0', STR_PAD_LEFT);
        $bin_out .= $charBinary;
    }

    // Return complete generated string
    return $bin_out;
}


// String to 7 bits array
private function strto7bit($message) {
    $message = trim($message);
    $length = strlen( $message );
    $i = 1;
    $bitArray = array();

    // Loop through every character in the string
    while ($i <= $length) {
        // Convert this character to a 7 bits value and insert it into the array
        $bitArray[] = $this->asc2bin( substr( $message ,$i-1,1) ,7);
        $i++;
    }


    // Return array containing 7 bits values
    return $bitArray;
}


// Convert 8 bits binary string to hex values (like F2)
private function bit8tohex($bin, $padding=false, $uppercase=true) {
    $hex = '';
    // Last item for counter (for-loop)
    $last = strlen($bin)-1;
    // Loop for every item
    for($i=0; $i<=$last; $i++) {
        $hex += $bin[$last-$i] * pow(2,$i);
    }

    // Convert from decimal to hexadecimal
    $hex = dechex($hex);
    // Add a 0 (zero) if there is only 1 value returned, like 'F'
    if($padding && strlen($hex) < 2 ) {
        $hex = '0'.$hex;
    }

    // If we want the output returned as UPPERCASE do this
    if($uppercase) {
        $hex = strtoupper($hex);
    }

    // Return the hexadecimal value
    return $hex;
}


// Convert 7 bits binary to hex, 7 bits > 8 bits > hex
private function bit7tohex($bits) {

    $i = 0;
    $hexOutput = '';
    $running = true;

    // For every 7 bits character array item
    while($running) {

        if(count($bits)==$i+1) {
            $running = false;
        }

        $value = $bits[$i];

        if($value=='') {
            $i++;
            continue;
        }

        // Convert the 7 bits value to the 8 bits value
        // Merge a part of the next array element and a part of the current one

        // Default: new value is current value
        $new = $value;

        if(key_exists(($i+1), $bits)) {
            // There is a next array item so make it 8 bits
            $neededChar = 8 - strlen($value);
            // Get the char;s from the next array item
            $charFromNext = substr($bits[$i+1], -$neededChar);
            // Remove used bit's from next array item
            $bits[$i+1] = substr($bits[$i+1], 0, strlen($bits[$i+1])-$neededChar );
            // New value is characters from next value and current value
            $new = $charFromNext.$value;
        }

        if($new!='') {
            // Always make 8 bits
            $new = str_pad($new, 8, '0', STR_PAD_LEFT);
            // The 8 bits to hex conversion
            $hexOutput .= $this->bit8tohex($new, true);
        }

        $i++;
    }

    // Return the 7bits->8bits->hexadecimal generated value
    return $hexOutput;
}

// String to length in Hex, String > StringLength > Hex
private function strToHexLen($message) {

    // Length of the string (message)
    $length = strlen( $message )+7; //+7 for UDH. the UDH is a total of (number of octets x bit size of octets) 6 x 8 = 48 bits long. Therefore a single bit of padding has to be prepended to the message. The UDH is therefore (bits for UDH / bits per septet) = (48 + 1)/7 = 7 septets in length.
    // Hex value of this string length
    $hex = dechex($length);

    // Length of the hex value
    $hexLength = strlen($hex);
    // If the hex strng length is lower dan 2
    if($hexLength < 2) {
        // Add a 0 (zero) before it
        $hex = '0'.$hex;
    }

    // Return the hex value in UPPERCASE characters
    return strtoupper($hex);
}

}
?>

Upvotes: 3

Views: 4495

Answers (2)

jmj101
jmj101

Reputation: 1

These questions are all over the place and no one seems to be able to answer them in way that makes any sense. Zero padding usually just makes things worse. I think easiest way to go around this design flaw in GMS standard is to use 8 bit encoding or 16-bit UCS2 even that it means less characters. In that way you don't need to care about difference in byte boundaries which is the reason creating concatenated SMS is so hard.

Upvotes: 0

KeyszerS
KeyszerS

Reputation: 684

As you are already aware, creating concatenated SMS messages requires you to add a UDH before your text message. The UDH becomes part of your payload, thus reducing the number of characters you can send per segment.

As it has become part of your payload, it needs to confirm with your payloads data requirement - which is 7 bit. The UDH however, is 8 bit, which clearly complicates things.

Consider the UDH of the following:

050003000302
  • 05 is the length of the UDH
  • 00 is the IEI
  • 03 is the IEDL (3 more octets)
  • 00 is a reference (this number must be the same in each of your concatenated message UDH's)
  • 03 is the maximum number of messages
  • 02 is the current message number.

This is 6 octets in total - equating to 48 bits. This is all and well, but since the UDH is actually part of your SMS message, what you have to do is add more bits so that the actual message starts on a septet boundary. A septet boundary is every 7 bits, so in this case, we will have to add 1 more bit of data to make the UDH 49 bits, and then we can add our standard GSM-7 encoded characters.

You can read up more about this from Here

Upvotes: 3

Related Questions