%%%------------------------------------------------------------------- %%% @author Anders Nygren %%% @doc Encoding and decoding of DIME messages. %%% The Direct Internet Message Encapsulation (DIME) specification %%% defines a mechanism for packaging binary data with SOAP messages. %%% http://bgp.potaroo.net/ietf/all-ids/draft-nielsen-dime-02.txt %%% Layout of a DIME encoded message is like this %%%
%%%  0                   1                   2                   3
%%%  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |         |M|M|C|       |       |                               |
%%% | VERSION |B|E|F| TYPE_T| RESRVD|         OPTIONS_LENGTH        |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |            ID_LENGTH          |           TYPE_LENGTH         |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |                          DATA_LENGTH                          |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |                                                               /
%%% /                     OPTIONS + PADDING                         /
%%% /                                                               |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |                                                               /
%%% /                          ID + PADDING                         /
%%% /                                                               |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |                                                               /
%%% /                        TYPE + PADDING                         /
%%% /                                                               |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%% |                                                               /
%%% /                        DATA + PADDING                         /
%%% /                                                               |
%%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
%%%
%%% @end
%%%
%%% Created :  7 Apr 2008 by Anders Nygren <>
%%%-------------------------------------------------------------------
-module(detergent_dime).

%% API
-export([encode/2,
     decode/1,
     pad_len/1]).


-include_lib("kernel/include/file.hrl").

-define(VERSION1, 1).
-define(T_UNCHANGED, 0).
-define(T_MEDIA_TYPE, 1).
-define(T_ABS_URI, 2).
-define(T_UNKNOWN, 3).
-define(T_NONE, 4).
-define(SOAP_URI, "http://schemas.xmlsoap.org/soap/envelope").
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% @spec (Req, Attachments) ->binary()
%% Description:
%%--------------------------------------------------------------------
encode(Req, []) ->
    encode_part(1, 1, 0, ?T_ABS_URI, <<"">>, <<"">>, <>, Req);
encode(Req, As) ->
    list_to_binary([encode_part(1, 0, 0, ?T_ABS_URI, <<"">>, <<"">>, <>, Req)|
    encode_attachments(As)]).

encode_attachments([{attachment, Id, Type, File}]) ->
    encode_part(0, 1, 0, ?T_ABS_URI, <<"">>, list_to_binary(Id), list_to_binary(Type), File);
encode_attachments([{attachment, Id, Type, File} | As]) ->
    [encode_part(0, 0, 0, ?T_ABS_URI, <<"">>, list_to_binary(Id), list_to_binary(Type), File)|
     encode_attachments(As)].

encode_part(MB, ME, CF, TypeT, Opts, ID, Type, Data) ->
    Opts_len = size_of(Opts),
    Opts_pad = pad_len(Opts_len),
    Id_len = size_of(ID),
    Id_pad = pad_len(Id_len),
    Type_len = size_of(Type),
    Type_pad = pad_len(Type_len),
    Data_len = size_of(Data),
    Data_pad = pad_len(Data_len),
    Data1 = get_data(Data),
    <>.

pad_len(Len) ->
    case Len rem 4 of
    0 ->
        0;
    N ->
        4-N
    end.

size_of(X) when is_list(X) ->
    length(X);
size_of(X) when is_binary(X)->
    size(X);
size_of({file, File}) ->
    {ok,R} = file:read_file_info(File),
    R#file_info.size.

get_data({file, File}) ->
    {ok, Data} = file:read_file(File),
    Data;
get_data(Data) when is_list(Data) ->
    list_to_binary(Data);

get_data(Data) ->
    Data.
%%--------------------------------------------------------------------
%% @spec (Msg::binary()) ->
%% @doc Decode a DIME encoded message.
%%--------------------------------------------------------------------
decode(Msg) ->
    decode_recs(Msg, [], []).

decode_recs(<<>>, Acc, _Chunks) ->
    Acc;
decode_recs(Msg, Acc, Chunks) ->
    case {decode_rec(Msg), Chunks} of
    %% A normal record
    {{_ME=0, _CF=0, Opts, ID, Type, Chunk, More}, []} ->
        decode_recs(More, [{Opts, ID, Type, Chunk}|Acc], []);

    %% The last chunk of a block, but not the last record
    {{_ME=0, _CF=0, Opts, ID, Type, Chunk, More}, Chunks} ->
        Cs = lists:reverse([{Opts, ID, Type, Chunk}|Chunks]),
        %% Only the first chunk has values for Opts, Id and Type
        {ROpts, RID, RType, _Chunk} = hd(Cs),
        Block = list_to_binary([Ch || {_Opts, _ID, _Type, Ch} <- Cs]),
        error_logger:info_report([?MODULE, decode_rec, merging,
                      {id, ID},{type, Type},
                      {chunks, length(Cs)}]),
        decode_recs(More, [{ROpts, RID, RType, Block}|Acc], []);

    %% Last record, but not chunked
    {{_ME=1, _CF=0, Opts, ID, Type, Data, _More}, []} ->
        lists:reverse([{Opts, ID, Type, Data}|Acc]);

    %% Last record, and last chunk of block
    {{_ME=1, _CF=0, Opts, ID, Type, Chunk, _More}, Chunks} ->
        Cs = lists:reverse([{Opts, ID, Type, Chunk}|Chunks]),
        {ROpts, RID, RType, _Chunk} = hd(Cs),
        Block = list_to_binary([Ch || {_Opts, _ID, _Type, Ch} <- Cs]),
        error_logger:info_report([?MODULE, decode_rec, merging,
                      {id, ID},{type, Type},
                      {chunks, length(Cs)}]),
        lists:reverse([{ROpts, RID, RType, Block}|Acc]);

    %% First or intermediate chunk, but not the last
    {{_ME=0, _CF=1, Opts, ID, Type, Data, More}, Chunks} ->
        decode_recs(More, Acc, [{Opts, ID, Type, Data}|Chunks]);

    %% Something wrong, ME=1 and CF=1
    ErrorResult ->
            error_logger:error_report([?MODULE, decode_recs,
                       {result, ErrorResult}])
    end.

decode_rec(<>=Block) ->
    error_logger:info_report([?MODULE, decode_rec,
                  {version,1},
                  {mb,_MB},{me,ME},{cf,CF},{type_t,_Type_T},
                  {res,_Res},{opt_len,Opt_Len},{id_len,ID_Len},
                  {type_len,Type_Len},{date_len,Data_Len},
                  {total_rec_size,byte_size(Block)}]),
    Opt_pad = pad_len(Opt_Len),
    Id_pad = pad_len(ID_Len),
    Type_pad = pad_len(Type_Len),
    Data_pad = pad_len(Data_Len),
    Header = Opt_Len+Opt_pad+ID_Len+Id_pad+Type_Len+Type_pad,
    DataTot = Data_Len+Data_pad,
    case byte_size(Rest) of
    N when N >= Header+DataTot ->
        <> = Rest,
        {ME, CF, Opts, ID, Type, Data, More};
    N when N > Header->
         <> = Rest,
        error_logger:error_report([?MODULE, decode_rec, short_record,
                       {need, Header+DataTot},
                       {has, N}]),
        {ME, CF, Opts, ID, Type, Data, <<>>};
    N ->
        error_logger:error_report([?MODULE, decode_rec, short_record,
                       {need, Header+DataTot},
                       {has, N}]),
        {error, not_enough_data}
    end;

decode_rec(Bin) ->
    error_logger:error_report([?MODULE, decode_rec, short_record,
                   no_header,
                   {has, byte_size(Bin)}]),
    {error, no_header}.


%%====================================================================
%% Internal functions
%%====================================================================