funbox/smppex

Sending multipart message

kodepett opened this issue ยท 9 comments

I've been scratching my head trying to figure out how to send multipart message. I've tried using the below, however an error is returned from the SMSC.

pdu = SMPPEX.Pdu.new(4, %{
      sm_length: 0, short_message: "", destination_addr: "xxxxxxxxx"
    }, %{message_payload => "Message with length over 300"})

returns the below

 %SMPPEX.Pdu{command_id: 2147483652, command_status: 4, mandatory: %{message_id: "0000000000"}, optional: %{}, ref: #Reference<0.1222358344.4153409537.176228>, sequence_number: 3}

I'm not well versed in the usage of TLV and attempt to gain insight from other documents have proven futile.
Kindly assist, thank you.

Hello! The returned status is 4, i.e. RINVBNDSTS โ€” "Incorrect BIND Status for given command".

I dare to guess you hadn't sent bind command after establishing session or it hadn't been successful.
There are examples of sending bind commands: examples.

Hi, I'm binding to the SMSC, the above works when I remove the message_payload and send a normal 160 characters short_message. The command status 4 is only returned when I attempt to use message_payload optional parameter. Below is the binding function

 @impl true
  def handle_info({:bind, system_id, password}, state) do
    bind_pdu = PduFactory.bind_transceiver(system_id, password)
    Logger.info("#{inspect bind_pdu}")
    {:noreply, [bind_pdu], state}
  end

  @impl true
  def handle_resp(pdu, _original_pdu, state) do
    case Pdu.command_name(pdu) do
      :bind_transceiver_resp -> handle_bind_resp(pdu, state)
      :unbind_resp -> handle_unbind_resp(state, :response)
      :submit_sm_resp -> handle_submit_sm_resp(pdu, state)
      _ -> handle_any_resp(pdu, state)
    end
  end

Any pointers to how to send a payload greater than 160.

This PDU should be valid, at least for SMSC's I generally deal with.
Probably, a tcpdump of a session could help.

Below is a solution to the issue I encountered, in case anyone faces that same challenge, splits messages by 153 characters.

  def send_sms(pid, source, dest, message) do

    # msgs =
    #   message
    #   |> String.codepoints()
    #   |> Enum.chunk_every(153)
    #   |> List.to_string()

    msgs =
      message
      |> String.split(~r/.{153}/, include_captures: true, trim: true)


    ref = :rand.uniform(255)
    total = Enum.count(msgs)

    for {msg, index} <- Enum.with_index(msgs) do
      {:ok, msg} = SMPPEX.Pdu.Multipart.prepend_message_with_part_info({ref, total, index + 1}, msg)
      submit =
        SMPPEX.Pdu.Factory.submit_sm(source, dest, msg)
        |> SMPPEX.Pdu.set_mandatory_field(:esm_class, 0x40)

      SMPPEX.Session.send_pdu(pid, submit)
    end

  end

Thanks a lot for the wonderful library.

Hello @dowusu , thank you for the feedback.

As far as I can see, one could use a special split_message function to make things a bit shorter:

  def send_sms(pid, source, dest, message) when byte_size(message) > 0 do
    ref = :rand.uniform(255)
    {:ok, :split, msgs} = SMPPEX.Pdu.Multipart.split_message(ref, message, 0, 153)

    for msg <- msgs do
      submit =
        SMPPEX.Pdu.Factory.submit_sm(source, dest, msg)
        |> SMPPEX.Pdu.set_mandatory_field(:esm_class, 0x40)

      SMPPEX.Session.send_pdu(pid, submit)
    end
  end

Hi @savonarola, you're right.
I saw that function quite late, I think it makes the solution shorter.

Thank you.

I saw that function quite late, I think it makes the solution shorter.

There is another advantage of SMPPEX.Pdu.Multipart.split_message: it works with all target encodings.
The examples here assume ASCII (actually gsm0338, packing 160 chars into 140 octets), but generally we cannot rely on one codepoint to occupy one byte.

I tried the function SMPPEX.Pdu.Multipart.split_message; it didn't work as expected, I had to rely on a GSM library for conversion before splitting the message. Not sure if I'm doing something wrong, can share the message for test if it becomes necessary

   ref = :rand.uniform(255)
    message = GSM.to_gsm(message)
    {:ok, :split, msgs} = SMPPEX.Pdu.Multipart.split_message(ref, message, 0, 153)
    for msg <- msgs do
      submit =
        SMPPEX.Pdu.Factory.submit_sm("Econtact", msisdn, msg)
        |> SMPPEX.Pdu.set_mandatory_field(:esm_class, 0x40)

      SMPPEX.Session.send_pdu(pid, submit)
    end

Please learn what coding schemes are supported by your SMSC, and which encoding or encodings do you want to use. See SMPP 3.4 specs section "5.2.19 data_coding".
Encoding 0 (implicitly set in your examples) is "SMSC Default Alphabet" that is not defined in the specification. That means each SMS operator is free to define it as they like (causing a mess in interoperability!) In North America, some operators expect the encoding 0 to be GSM03.38, some want Latin-1.
To set different encoding, use a tuple in SMPPEX.Pdu.Factory.submit_sm("Econtact", msisdn, {encoding, msg}).

And yes, message has to be converted from Elixir internal representation (UTF-8) to target encoding before calling split_message. Converted message is a binary, but not necessary a String. Note that the spec of the method split_message correctly indicates type message :: binary, unlike spec of submit_sm.