twitter-archive / cloudhopper-smpp

Efficient, scalable, and flexible Java implementation of the Short Messaging Peer to Peer Protocol (SMPP)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

concatenating sms parts to a long sms from UDH

behrad opened this issue · comments

Is there any code implementing concatenation of a list of single sms parts based on UDH into long messages (what that normally mobile phones do)?

P.S. may be considered as an off-topic issue but found it a good idea to ask in related repositories

commented

Hello behard,

Below code snippet will return Message Parts Into Byte Array

private static final int MAX_MULTIPART_MSG_SEGMENT_SIZE_UCS2 = 134;
private static final int MAX_SINGLE_MSG_SEGMENT_SIZE_UCS2 = 70;
private static final int MAX_MULTIPART_MSG_SEGMENT_SIZE_7BIT = 153;
private static final int MAX_SINGLE_MSG_SEGMENT_SIZE_7BIT = 160;
 public static byte[][] getParts(String messageBody, String charSet) {
        int maximumSingleMessageSize = 0;
        int maximumMultipartMessageSegmentSize = 0;
        byte[] byteSingleMessage = null;
        if (! CharsetUtil.NAME_UCS_2.equals(charSet)) {
            byteSingleMessage = CharsetUtil.encode(messageBody, charSet);
            maximumSingleMessageSize = MAX_SINGLE_MSG_SEGMENT_SIZE_7BIT;
            maximumMultipartMessageSegmentSize = MAX_MULTIPART_MSG_SEGMENT_SIZE_7BIT;
        } else {
            byteSingleMessage = CharsetUtil.encode(messageBody, charSet);
            maximumSingleMessageSize = MAX_SINGLE_MSG_SEGMENT_SIZE_UCS2;
            maximumMultipartMessageSegmentSize = MAX_MULTIPART_MSG_SEGMENT_SIZE_UCS2;
        }
        byte[][] byteMessagesArray = null;
        if (messageBody.length() > maximumSingleMessageSize) {
            // split message according to the maximum length of a segment
            byteMessagesArray = splitUnicodeMessage(byteSingleMessage, maximumMultipartMessageSegmentSize);
            // set UDHI so PDU will decode the header
       //     esmClass = new ESMClass(MessageMode.DEFAULT, TrayIcon.MessageType.DEFAULT, GSMSpecificFeature.UDHI);
        } else {
            byteMessagesArray = new byte[][] { byteSingleMessage };
         //   esmClass = new ESMClass();
        }


        return  byteMessagesArray;
    }

private static byte[][] splitUnicodeMessage(byte[] aMessage, Integer maximumMultipartMessageSegmentSize) {

        final byte UDHIE_HEADER_LENGTH = 0x05;
        final byte UDHIE_IDENTIFIER_SAR = 0x00;
        final byte UDHIE_SAR_LENGTH = 0x03;

        // determine how many messages have to be sent
        int numberOfSegments = aMessage.length / maximumMultipartMessageSegmentSize;
        int messageLength = aMessage.length;
        if (numberOfSegments > 255) {
            numberOfSegments = 255;
            messageLength = numberOfSegments * maximumMultipartMessageSegmentSize;
        }
        if ((messageLength % maximumMultipartMessageSegmentSize) > 0) {
            numberOfSegments++;
        }

        // prepare array for all of the msg segments
        byte[][] segments = new byte[numberOfSegments][];

        int lengthOfData;

        // generate new reference number
        byte[] referenceNumber = new byte[1];
        new Random().nextBytes(referenceNumber);

        // split the message adding required headers
        for (int i = 0; i < numberOfSegments; i++) {
            if (numberOfSegments - i == 1) {
                lengthOfData = messageLength - i * maximumMultipartMessageSegmentSize;
            } else {
                lengthOfData = maximumMultipartMessageSegmentSize;
            }

            // new array to store the header
            segments[i] = new byte[6 + lengthOfData];

            // UDH header
            // doesn't include itself, its header length
            segments[i][0] = UDHIE_HEADER_LENGTH;
            // SAR identifier
            segments[i][1] = UDHIE_IDENTIFIER_SAR;
            // SAR length
            segments[i][2] = UDHIE_SAR_LENGTH;
            // reference number (same for all messages)
            segments[i][3] = referenceNumber[0];
            // total number of segments
            segments[i][4] = (byte) numberOfSegments;
            // segment number
            segments[i][5] = (byte) (i + 1);

            // copy the data into the array
            System.arraycopy(aMessage, (i * maximumMultipartMessageSegmentSize), segments[i], 6, lengthOfData);

        }
        return segments;
    }

You can loop generated byte Array to submit each part to connected smsc

Thanks

thank you @gruntday but I was looking for the reverse operation code, I have a list of message parts and I want to construct long sms bodies from them :)

take a look on these methods:
com.cloudhopper.smpp.util.SmppUtil#isUserDataHeaderIndicatorEnabled
com.cloudhopper.commons.gsm.GsmUtil#getShortMessageUserDataHeader
com.cloudhopper.commons.gsm.GsmUtil#getShortMessageUserData
using them, you can get short messages without UDH, and then you just join them.

commented

@behrad do you have the reference number and total parts and current seq per each message what i mean how current parts saved at your side ? so that i can give you exact example for concatenation

do you have the reference number and total parts and current seq per each message what i mean how current parts saved at your side ? so that i can give you exact example for concatenation

they are standard smpp parts, the information you mean is in UDH headers.

Hi

I am trying to solve the same problem.

@krasa - thanks for pointing to the GSM Utils methods.

Using getShortMessageUserData and decoding based on the the relevant coding scheme I can get the message information.

But to get the information from the User Data Header (i.e. number of total messages, message id, number of this message) I am battling.

getShortMessageUserDataHeader returns a byte[] which I don't know how to parse to get the information.

Is there any example code for processing the User Data Header in cloudhopper?

In the provided example code I don't see any parsing of the actual user data header to get the information.

Hello @gruntday,
Apologies for resurrecting this thread.
I have a question about the example code you provided.
For the 7-bit encoding use case, the code example doesn't apply any padding bits between the UDH and the message.
The GSM specs [3GPP TS 23.040 section 9.2.3.24] indicate that for 7-bit character encoding, padding bits should added so that the short message starts at a septet boundary.

Therefore my question is, do padding bits need to be added between the UDH and the encoded bytes?

Or, because the 7-bit characters are packed into octets, can we ignore the padding bits?

Cheers,
Jaimie

@JaimieW The specification is correct, you need those padding bits - I had a problem with that once.

@krasa Thanks for that.
A followup if I may.
If I'm using the cloudhopper PackedGSMCharset to encode the short message, the message is packed into octets.
Where do the padding bits go?

I have the UDH determined which is in octets.
I have the message encoded in 7-bit characters packed into octets via the PackedGSMCharset.encode method.

I'm really struggling to see how to add the padding bits.
Any advice/tips would be appreciated.

Cheers,
Jaimie

Hi @JaimieW

Not sure if this will help - I battled to get the UDH 7 bit packing sorted and eventually modified @gruntday provided method to be like this - using PduBitPacker static class. Seems a bit of a hack but it works when integrating with out SMPP transit partner.

private static byte[][] splitMultipartMessage(byte[] aMessage) throws Exception {

        int maximumMultipartMessageSegmentSize = 134;

        // generate new reference number
        byte[] referenceNumber = new byte[1];
        new Random().nextBytes(referenceNumber);

        final byte UDHIE_HEADER_LENGTH = 0x05;
        final byte UDHIE_IDENTIFIER_SAR = 0x00;
        final byte UDHIE_SAR_LENGTH = 0x03;

        // determine how many messages have to be sent
        log.debug("Splitting message up: [{}]", aMessage);
        int numberOfSegments = aMessage.length / maximumMultipartMessageSegmentSize;
        int messageLength = aMessage.length;
        if (numberOfSegments > 255) {
            numberOfSegments = 255;
            messageLength = numberOfSegments * maximumMultipartMessageSegmentSize;
        }
        if ((messageLength % maximumMultipartMessageSegmentSize) > 0) {
            numberOfSegments++;
        }

        // prepare array for all of the msg segments
        byte[][] segments = new byte[numberOfSegments][];

        int lengthOfData;

        // split the message adding required headers
        for (int i = 0; i < numberOfSegments; i++) {
            if (numberOfSegments - i == 1) {
                lengthOfData = messageLength - i * maximumMultipartMessageSegmentSize;
            } else {
                lengthOfData = maximumMultipartMessageSegmentSize;
            }

            //prepend 0x00 to aMessage
            //bit pack aMessage
            byte[] bMessage = new byte[lengthOfData + 1];
            bMessage[0] = 0x00;
            System.arraycopy(aMessage, (i * maximumMultipartMessageSegmentSize), bMessage, 1, lengthOfData);
            //now pack bmessage using gsm 7 bit
            byte[] messageBytes = PduBitPacker.PackBytes(bMessage, 1);

            // new array to store the header
            segments[i] = new byte[6 + messageBytes.length - 1 /*remove 1 byte of bit padding*/];
            // UDH header
            // doesn't include itself, its header length
            segments[i][0] = UDHIE_HEADER_LENGTH;
            // SAR identifier
            segments[i][1] = UDHIE_IDENTIFIER_SAR;
            // SAR length
            segments[i][2] = UDHIE_SAR_LENGTH;
            // reference number (same for all messages)
            segments[i][3] = referenceNumber[0];
            // total number of segments
            segments[i][4] = (byte) numberOfSegments;
            // segment number
            segments[i][5] = (byte) (i + 1);
            // copy the data into the array

            System.arraycopy(messageBytes, 0, segments[i], 6, messageBytes.length - 1 /*remove 1 byte of bit padding*/);
            break;

            }

        }
        return segments;
    }


Also to answer my original question in case anyone else is battling with this:

getShortMessageUserDataHeader returns a byte[] which I don't know how to parse to get the information.

Is there any example code for processing the User Data Header in cloudhopper?

I managed to sort this as follows:

byte[] userDataHeader = GsmUtil.getShortMessageUserDataHeader(baseSm.getShortMessage());
int thisMessageId = userDataHeader[3] & 0xff; // Range 0 to 255, not -128 to 127;
int totalMessages = userDataHeader[4] & 0xff; // Range 0 to 255, not -128 to 127;
int currentMessageNum = userDataHeader[5] & 0xff; // Range 0 to 255, not -128 to 127;