%% @doc SMPP PDU Helper %% %% This module constructs and parses SMPP PDUs to and from octet formats -module(esmpp_pdu). %% Main exports -export([ bind/2, enquire_link/1, submit_sm/2, deliver_sm_resp/1 ]). %% Internally exposed -export([ pad2/1, pad4/1, iodata_to_cstring/2 ]). -include("constants.hrl"). -include("types.hrl"). -include("commands.hrl"). %% @doc Encode a bind PDU from a sequence number and a bind_pdu record -spec bind(integer(), bind_pdu()) -> {pdu, binary()}. bind(SeqNumInt, Bind) -> Status = pad4(binary:encode_unsigned(?NULL)), SeqNum = pad4(binary:encode_unsigned(SeqNumInt)), Binding = pad4(binary:encode_unsigned(Bind#bind_pdu.binding)), Version = binary:encode_unsigned(?SMPP_VERSION), AddrTon = binary:encode_unsigned(Bind#bind_pdu.addr_ton), AddrNpi = binary:encode_unsigned(Bind#bind_pdu.addr_npi), SystemId = iodata_to_cstring(Bind#bind_pdu.system_id, 16), Password = iodata_to_cstring(Bind#bind_pdu.password, 9), SystemType = iodata_to_cstring(Bind#bind_pdu.system_type, 13), AddrRange = iodata_to_cstring(Bind#bind_pdu.addr_range, 41), TmpPDU = <>, Length = pad4(binary:encode_unsigned(size(TmpPDU) + 4)), PDU = <>, {pdu, PDU}. %% @doc Encode an enquire_link PDU from a sequence number -spec enquire_link(integer()) -> {pdu, binary()}. enquire_link(SeqNumInt) -> Status = pad4(binary:encode_unsigned(?NULL)), SeqNum = pad4(binary:encode_unsigned(SeqNumInt)), Command = pad4(binary:encode_unsigned(?ENQUIRE_LINK)), TmpPDU = <>, Length = pad4(binary:encode_unsigned(size(TmpPDU) + 4)), PDU = <>, {pdu, PDU}. %% @doc Encode a submit_sm PDU from a sequence number and a submit_sm record -spec submit_sm(integer(), submit_sm_pdu()) -> {pdu, binary()}. submit_sm(SeqNumInt, SubmitSm) -> Status = pad4(binary:encode_unsigned(?NULL)), SeqNum = pad4(binary:encode_unsigned(SeqNumInt)), Command = pad4(binary:encode_unsigned(?SUBMIT_SM)), SrcAddrTon = binary:encode_unsigned(SubmitSm#submit_sm_pdu.src_addr_ton), SrcAddrNpi = binary:encode_unsigned(SubmitSm#submit_sm_pdu.src_addr_npi), DstAddrTon = binary:encode_unsigned(SubmitSm#submit_sm_pdu.dst_addr_ton), DstAddrNpi = binary:encode_unsigned(SubmitSm#submit_sm_pdu.dst_addr_npi), EsmClass = binary:encode_unsigned(SubmitSm#submit_sm_pdu.esm_class), ProtocolId = binary:encode_unsigned(SubmitSm#submit_sm_pdu.protocol_id), PriorityFlag = binary:encode_unsigned(SubmitSm#submit_sm_pdu.priority_flag), RegDelivery = binary:encode_unsigned(SubmitSm#submit_sm_pdu.reg_delivery), ReplaceFlag = binary:encode_unsigned(SubmitSm#submit_sm_pdu.replace_flag), DataCoding = binary:encode_unsigned(SubmitSm#submit_sm_pdu.data_coding), MsgId = binary:encode_unsigned(SubmitSm#submit_sm_pdu.msg_id), SmLength = binary:encode_unsigned(SubmitSm#submit_sm_pdu.sm_length), DeliveryTime = iodata_to_cstring(SubmitSm#submit_sm_pdu.delivery_time, 17), Validity = iodata_to_cstring(SubmitSm#submit_sm_pdu.validity, 17), SrcAddr = iodata_to_cstring(SubmitSm#submit_sm_pdu.src_addr, 21), DstAddr = iodata_to_cstring(SubmitSm#submit_sm_pdu.dst_addr, 21), ServiceType = iodata_to_cstring(SubmitSm#submit_sm_pdu.service_type, 6), ShortMessage = SubmitSm#submit_sm_pdu.short_message, OptParams = SubmitSm#submit_sm_pdu.optional_params, Tlv = pack_tlv(OptParams, ?SUBMIT_SM_OPT_PARAMS), TmpPDU = <>, Length = pad4(binary:encode_unsigned(size(TmpPDU) + 4)), PDU = <>, {pdu, PDU}. %% @doc Encode a deliver_sm_resp PDU from a sequence number -spec deliver_sm_resp(integer()) -> {pdu, binary()}. deliver_sm_resp(SeqNumInt) -> Status = pad4(binary:encode_unsigned(?NULL)), SeqNum = pad4(binary:encode_unsigned(SeqNumInt)), Command = pad4(binary:encode_unsigned(?DELIVER_SM_RESP)), MessageId = binary:encode_unsigned(?NULL), TmpPDU = <>, Length = pad4(binary:encode_unsigned(size(TmpPDU) + 4)), PDU = <>, {pdu, PDU}. %% ---------------------------------------------------------------------------- %% internal - but exposed nonetheless %% ---------------------------------------------------------------------------- %% @private Used to convert erlang string to a C string %% terminated by a NULL character iodata_to_cstring(<<>>, _MaxLength) -> <<0>>; iodata_to_cstring(<<0>>, _MaxLength) -> <<0>>; iodata_to_cstring(Data, MaxLength) when size(Data) >= MaxLength -> Limit = MaxLength - 1, DataBin = esmpp_format:ensure_binary(Data), <> = DataBin, <>; iodata_to_cstring(Data, _MaxLength) -> DataBin = esmpp_format:ensure_binary(Data), <>. %% @private Pads erlang binary with max 2 octets pad2(Bin) when size(Bin) >= 2 -> Bin; pad2(Bin) -> pad2(Bin, big). pad2(Bin, big) -> <<0:((2 - (size(Bin) rem 4)) * 8), Bin/binary>>; pad2(Bin, small) -> <>. %% @private Pads erlang binary with max 4 octets pad4(Bin) when size(Bin) >= 4 -> Bin; pad4(Bin) -> pad4(Bin, big). pad4(Bin, big) -> <<0:((4 - (size(Bin) rem 4)) * 8), Bin/binary>>; pad4(Bin, small) -> <>. %% @private Packs optional params map to TLV binary. Will also strip unknown keys pack_tlv(TmpParams, Keys) -> OptParams = maps:with(Keys, TmpParams), maps:fold(fun(Tag, Value, TmpTlv1) -> TmpTlv2 = esmpp_tags:pack(Tag, Value), <> end, <<>>, OptParams).