%%%------------------------------------------------------------------- %%% @author cheese %%% @copyright (C) 2014, %%% @doc %%% %%% @end %%% Created : 20. Jan 2014 4:04 PM %%%------------------------------------------------------------------- -module(protocol_titp). -author("cheese"). -include("../../include/types_finance_protocol.hrl"). %% API -export([parse/1, get_field_description/1, pack/1, create_field_as_string/2, protocol_info_string/1, field_info_string/1]). %% Internal Functions -export([internal_pack_field_header/1, internal_pack_fields/2, internal_pack_field/1, internal_parse_fields/4, internal_parse_fields_from_byte_in_bitmap/5, internal_parse_field_from_bytes/2, internal_get_valid_message_size/2, internal_get_size_align/1, internal_get_view_data/1, internal_check_size/2, internal_pack_bitmap/3, internal_protocol_info_string/2]). parse(<<_L2L1:2/binary, Header_1:1/binary, Header_2_From:2/binary, Header_3_To:2/binary, MTI_Bin:2/binary, Bitmap:8/binary, Data/binary>>) -> MTI = bytes_extension:bcd_int(MTI_Bin), BitMapParse = internal_parse_fields(Bitmap, 0, Data, []), #parsed_iso_message{ header = <>, mti = MTI, fields = BitMapParse }; parse(Other) -> logger_Cheese:info("Invalid request: ~w~n", [Other]), {error, invalid_data}. %%---------------------------------------------------------------------------------------------------------------------- %% Разбор полей по битовой маске %%---------------------------------------------------------------------------------------------------------------------- -spec(internal_parse_fields(binary(), number(), binary(), [#iso_field{}]) -> [#iso_field{}]). internal_parse_fields(<<>>, _Pos, _Data, AllFields) -> AllFields; internal_parse_fields(<>, Pos, Data, AllFields) -> case internal_parse_fields_from_byte_in_bitmap(Bt, 128, Pos * 8 + 1, Data, []) of {error, Reason} -> {error, Reason}; {NewData, Fields} -> internal_parse_fields(Tail, Pos + 1, NewData, AllFields ++ Fields) end. %%---------------------------------------------------------------------------------------------------------------------- %% Разбор полей по одному байту из битовой маски %%---------------------------------------------------------------------------------------------------------------------- -spec(internal_parse_fields_from_byte_in_bitmap(number(), number(), number(), binary(), [#iso_field{}]) -> {binary(), [#iso_field{}]} | {error, any()}). internal_parse_fields_from_byte_in_bitmap(_, Pow, _, Bytes, AllFields) when Pow == 0 -> {Bytes, lists:reverse(AllFields)}; internal_parse_fields_from_byte_in_bitmap(Bt, Pow, Pos, Bytes, AllFields) -> IsPresent = Bt band Pow, case IsPresent > 0 of true -> Res = get_field_description(Pos), case Res of undefined -> internal_parse_fields_from_byte_in_bitmap(Bt, Pow div 2, Pos + 1, Bytes, AllFields); FieldInfo -> case internal_parse_field_from_bytes(FieldInfo, Bytes) of {error, Reason} -> {error, Reason}; {Field, NewBytes} -> internal_parse_fields_from_byte_in_bitmap(Bt, Pow div 2, Pos + 1, NewBytes, [Field | AllFields]) end end; false -> internal_parse_fields_from_byte_in_bitmap(Bt, Pow div 2, Pos + 1, Bytes, AllFields) end. %%---------------------------------------------------------------------------------------------------------------------- %% Разбор одного поля %%---------------------------------------------------------------------------------------------------------------------- -spec(internal_parse_field_from_bytes(#iso_field{}, binary()) -> {error, any()} | {#iso_field{}, binary()}). internal_parse_field_from_bytes(#iso_field{header = Header, pos = _Pos, type = _Type, size = _Size, info = _Info} = FieldInfo, Bytes) -> %%logger:simple_message("=============================================", []), %%logger:simple_message("Parsing field. pos:~w, size:~w, type: ~w, head: ~w, info: ~p", [Pos,Size, Type, Header, Info]), case internal_get_valid_message_size(FieldInfo, Bytes) of {error, Reason} -> {error, Reason}; RealDataSizeFixed -> %%logger:simple_message("RealDataSizeFixed: ~p", [RealDataSizeFixed]), FieldDataBytes = binary:part(Bytes, Header, RealDataSizeFixed), BytesOther = binary:part(Bytes, Header + RealDataSizeFixed, byte_size(Bytes) - Header - RealDataSizeFixed), %%logger:simple_message("BytesOther: ~w", [BytesOther]), NewFieldInfoRaw = FieldInfo#iso_field{ raw_data = FieldDataBytes }, ViewData = internal_get_view_data(NewFieldInfoRaw), NewFieldInfo = NewFieldInfoRaw#iso_field{ view_data = ViewData }, {NewFieldInfo, BytesOther} end; %%---------------------------------------------------------------------------------------------------------------------- internal_parse_field_from_bytes(FieldInfo, _Bytes) -> {error, {'Not parsed field', FieldInfo}}. %%---------------------------------------------------------------------------------------------------------------------- %% Получить размер реальных данных в поле %%---------------------------------------------------------------------------------------------------------------------- internal_get_valid_message_size(#iso_field{header = 0, size = Size, type = Type}, Bytes) when Type == d -> internal_check_size(internal_get_size_align(Size), Bytes); internal_get_valid_message_size(#iso_field{header = 0, size = Size, type = Type}, Bytes) when Type == b -> internal_check_size(Size, Bytes); internal_get_valid_message_size(#iso_field{header = 0, size = Size}, Bytes) -> internal_check_size(Size, Bytes); internal_get_valid_message_size(#iso_field{header = Header, type = Type}, Bytes) when Type == d -> RealDataSize = bytes_extension:bcd_int(binary:part(Bytes, 0, Header)), internal_check_size(internal_get_size_align(RealDataSize), Bytes); internal_get_valid_message_size(#iso_field{header = Header, type = Type}, Bytes) when Type == b -> RealDataSize = bytes_extension:bcd_int(binary:part(Bytes, 0, Header)), internal_check_size(RealDataSize, Bytes); internal_get_valid_message_size(#iso_field{header = Header}, Bytes) -> RealDataSize = string:to_integer(binary_to_list(binary:part(Bytes, 0, Header))), internal_check_size(internal_get_size_align(RealDataSize), Bytes). %%---------------------------------------------------------------------------------------------------------------------- %% Получить данные %%---------------------------------------------------------------------------------------------------------------------- internal_check_size(Size, Bytes) -> case Size > byte_size(Bytes) of true -> {error, {not_enough_data, {size, Size}}}; false -> Size end. %%---------------------------------------------------------------------------------------------------------------------- %% Получить данные %%---------------------------------------------------------------------------------------------------------------------- internal_get_view_data(#iso_field{type = Type, raw_data = Bytes}) when Type == d -> bytes_extension:bcd_string(Bytes); internal_get_view_data(#iso_field{raw_data = Bytes}) -> Bytes. %%---------------------------------------------------------------------------------------------------------------------- %% Размер, кратный 2 %%---------------------------------------------------------------------------------------------------------------------- -spec(internal_get_size_align(any()) -> integer() | {error, _}). internal_get_size_align(Size) when is_integer(Size) -> Size div 2 + Size rem 2; internal_get_size_align(Size) -> {error, {invalid_size_input, Size}}. %%---------------------------------------------------------------------------------------------------------------------- %% Описание одного поля протокола %%---------------------------------------------------------------------------------------------------------------------- get_field_description(0 = Pos) -> #iso_field{ pos = Pos, size = 8, header = 0, type = b, info = "Primary Bit Map" }; get_field_description(2 = Pos) -> #iso_field{ pos = Pos, size = 19, header = 1, type = d, info = "Primary Acct. Num." }; get_field_description(3 = Pos) -> #iso_field{ pos = Pos, size = 6, header = 0, type = d, info = "Processing Code" }; get_field_description(4 = Pos) -> #iso_field{ pos = Pos, size = 12, header = 0, type = d, info = "Amount, Trans." }; get_field_description(11 = Pos) -> #iso_field{ pos = Pos, size = 6, header = 0, type = d, info = "Systems Trace No" }; get_field_description(12 = Pos) -> #iso_field{ pos = Pos, size = 6, header = 0, type = d, info = "Time, Local Trans." }; get_field_description(13 = Pos) -> #iso_field{ pos = Pos, size = 4, header = 0, type = d, info = "Date, Local Trans." }; get_field_description(14 = Pos) -> #iso_field{ pos = Pos, size = 4, header = 0, type = d, info = "Date, Expiration" }; get_field_description(22 = Pos) -> #iso_field{ pos = Pos, size = 3, header = 0, type = d, info = "POS Entry Mode" }; get_field_description(23 = Pos) -> #iso_field{ pos = Pos, size = 3, header = 0, type = d, info = "Card Seq. Number" }; get_field_description(24 = Pos) -> #iso_field{ pos = Pos, size = 3, header = 0, type = d, info = "NII" }; get_field_description(25 = Pos) -> #iso_field{ pos = Pos, size = 2, header = 0, type = d, info = "POS Condition Code" }; get_field_description(35 = Pos) -> #iso_field{ pos = Pos, size = 37, header = 1, type = d, info = "Track 2 Data" }; get_field_description(37 = Pos) -> #iso_field{ pos = Pos, size = 12, header = 0, type = s, info = "Retrieval Ref. No." }; get_field_description(38 = Pos) -> #iso_field{ pos = Pos, size = 6, header = 0, type = s, info = "Auth. Id. Response" }; get_field_description(39 = Pos) -> #iso_field{ pos = Pos, size = 2, header = 0, type = s, info = "Response Code" }; get_field_description(41 = Pos) -> #iso_field{ pos = Pos, size = 8, header = 0, type = s, info = "Terminal Id" }; get_field_description(42 = Pos) -> #iso_field{ pos = Pos, size = 15, header = 0, type = s, info = "Card Acq. Id" }; get_field_description(43 = Pos) -> #iso_field{ pos = Pos, size = 40, header = 0, type = s, info = "Card Acq. Name" }; get_field_description(45 = Pos) -> #iso_field{ pos = Pos, size = 76, header = 1, type = s, info = "Track 1 Data" }; get_field_description(48 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Add. Data - Private" }; get_field_description(49 = Pos) -> #iso_field{ pos = Pos, size = 3, header = 0, type = s, info = "Currency Code, Trans." }; get_field_description(52 = Pos) -> #iso_field{ pos = Pos, size = 8, header = 0, type = b, info = "PIN Data" }; get_field_description(53 = Pos) -> #iso_field{ pos = Pos, size = 16, header = 0, type = d, info = "Security Control Info" }; get_field_description(54 = Pos) -> #iso_field{ pos = Pos, size = 120, header = 2, type = b, info = "Additional Amounts" }; get_field_description(55 = Pos) -> #iso_field{ pos = Pos, size = 255, header = 2, type = b, info = "ICC Sys Related Data" }; get_field_description(57 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Field 57 (Compass+)" }; get_field_description(60 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Private Use" }; get_field_description(61 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Private Use" }; get_field_description(62 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Private Use" }; get_field_description(63 = Pos) -> #iso_field{ pos = Pos, size = 999, header = 2, type = b, info = "Private Use" }; get_field_description(64 = Pos) -> #iso_field{ pos = Pos, size = 8, header = 0, type = b, info = "Message Auth. Code" }; get_field_description(_Pos) -> undefined. -spec(pack(#parsed_iso_message{}) -> binary()). pack(#parsed_iso_message{fields = Fields, header = Header, mti = Mti}) -> BytesMTI = <<(Mti div 100):8, (Mti rem 100):8>>, BytesBitmap = internal_pack_bitmap(Fields, 1, <<>>), BytesFields = internal_pack_fields(Fields, <<>>), BytesAll = <
>, DataSize = bytes_extension:binary_length(BytesAll), <<(DataSize div 256):8, (DataSize rem 256):8, BytesAll/binary>>. -spec(internal_pack_bitmap([#iso_field{}], integer(), binary()) -> binary()). internal_pack_bitmap([#iso_field{pos = Idx} | Tail], Idx, Bitmaps) -> NewBitmap = <>, internal_pack_bitmap(Tail, Idx + 1, NewBitmap); internal_pack_bitmap(_Fields, 65, Bitmaps) -> Bitmaps; internal_pack_bitmap(Fields, Idx, Bitmaps) -> NewBitmap = <>, internal_pack_bitmap(Fields, Idx + 1, NewBitmap). -spec(internal_pack_fields([#iso_field{}], binary()) -> binary()). internal_pack_fields([], Res) -> Res; internal_pack_fields([#iso_field{pos = Pos} = Field | Tail], Res) -> Bytes = internal_pack_field(Field), logger_Cheese:simple_message("Field_~w: ~p, |||| ~w", [Pos, Field, Bytes]), internal_pack_fields(Tail, <>). -spec(internal_pack_field(#iso_field{}) -> binary()). internal_pack_field(#iso_field{raw_data = Data} = Field) -> BytesHeader = internal_pack_field_header(Field), <>. -spec(internal_pack_field_header(#iso_field{}) -> binary()). internal_pack_field_header(#iso_field{header = 0}) -> <<>>; internal_pack_field_header(#iso_field{type = Type, header = Header, raw_data = Data}) when Type =:= b -> BytesCountHeadLen = Header, BytesCountData = bytes_extension:binary_length(Data), bytes_extension:int_to_bcd(BytesCountData, BytesCountHeadLen); internal_pack_field_header(#iso_field{type = Type, header = Header, raw_data = Data}) when Type =:= d -> BytesCountHeadLen = Header, BytesCountData = case bytes_extension:last_byte(Data) rem 16 of 15 -> bytes_extension:binary_length(Data) * 2 - 1; _ -> bytes_extension:binary_length(Data) * 2 end, bytes_extension:int_to_bcd(BytesCountData, BytesCountHeadLen); internal_pack_field_header(#iso_field{type = Type, raw_data = Data}) -> error({error, {internal_pack_header, not_realized_for_type, {type, Type}, {raw_data, Data}}}). create_field_as_string(Idx, Data) -> case get_field_description(Idx) of undefined -> error({error, {undefined_field_index, Idx}}); #iso_field{type = d} = Field -> Field#iso_field{ view_data = Data, raw_data = protocol_iso_utilites:prepare_data_string_to_bcd(Data) }; #iso_field{type = s} = Field -> Field#iso_field{ view_data = Data, raw_data = <> }; #iso_field{type = b} = Field -> Field#iso_field{ view_data = Data, raw_data = list_to_binary(Data) }; Field -> error({error, {unsupported_field_for_setting, Field}}) end. protocol_info_string(#parsed_iso_message{mti = Mti} = Titp) -> MtiBytes = integer_to_binary(Mti), FiledsBytes = internal_protocol_info_string(Titp#parsed_iso_message.fields, <<>>), <<"[MTI: ", MtiBytes/binary, "]", FiledsBytes/bytes>>. internal_protocol_info_string([], Res) -> Res; internal_protocol_info_string([#iso_field{pos = Pos} = Field | Tail], Res) -> Data = field_info_string(Field), IdxBytes = integer_to_binary(Pos), internal_protocol_info_string(Tail, <>). field_info_string(#iso_field{view_data = Data}) -> Data.