% This Source Code Form is subject to the terms of the Mozilla Public % License, v. 2.0. If a copy of the MPL was not distributed with this % file, You can obtain one at https://mozilla.org/MPL/2.0/. % (C) 2019 Andrey Velikiy % (C) 2019 Fairwaves (edited) -module(gsup_protocol). -include ("gsup_protocol.hrl"). -include ("ipa.hrl"). -export([decode/1, encode/1, decode_bcd/1, encode_bcd/1]). -export_type(['GSUPMessage'/0, 'GSUPMessageType'/0]). -define (CHECK_SIZE(IE, Len, Value), Value >= 0 andalso Value < (1 bsl (Len * 8)) orelse error({ie_value_length_mismatch, IE, Value}) ). -define (CHECK_LEN(IE, Len, Min, Max), Len >= Min andalso Len =< Max orelse error({ie_length_mismatch, IE, Len}) ). -ifdef (TEST). -export ([encode_ie/2, decode_ie/2]). -endif. -spec decode(binary()) -> 'GSUPMessage'() | no_return(). decode(<>) -> case ?GSUP_MESSAGES() of #{MsgType := #{message_type := MsgTypeAtom, mandatory := Mandatory0}} -> GSUPMessage = decode_ie(Tail, #{message_type => MsgTypeAtom}), Mandatory = Mandatory0 ++ ?MANDATORY_DEFAULT, case maps:size(maps:with(Mandatory, GSUPMessage)) == length(Mandatory) of true -> GSUPMessage; false -> error({mandatory_ie_missing, MsgTypeAtom, Mandatory -- maps:keys(GSUPMessage)}) end; _ -> error({unknown_gsup_msg_type, MsgType}) end. decode_ie(<<>>, Map) -> Map; decode_ie(<>, Map) -> ?CHECK_LEN(imsi, Len, 0, 8), decode_ie(Tail, Map#{imsi => decode_bcd(IMSI, <<>>)}); decode_ie(<>, Map) -> ?CHECK_LEN(cause, Len, 1, 1), decode_ie(Tail, Map#{cause => Cause}); decode_ie(<>, Map) -> List = maps:get(auth_tuples, Map, []), ?CHECK_LEN(auth_tuples, length(List) + 1, 1, 5), AuthTuple = decode_auth_tuple(AuthTuple0, #{}), check_auth_tuple(AuthTuple) orelse error({bad_auth_tuple, AuthTuple}), decode_ie(Tail, Map#{auth_tuples => List ++ [AuthTuple]}); decode_ie(<>, Map) -> ?CHECK_LEN(pdp_info_complete, Len, 0, 0), decode_ie(Tail, Map#{pdp_info_complete => true}); decode_ie(<>, Map) -> List = maps:get(pdp_info_list, Map, []), ?CHECK_LEN(pdp_info_list, length(List) + 1, 1, 10), PDPInfo = decode_pdp_info(PDPInfo0, #{}), check_pdp_info(PDPInfo) orelse error({bad_pdp_info, PDPInfo}), decode_ie(Tail, Map#{pdp_info_list => List ++ [PDPInfo]}); decode_ie(<>, Map) -> ?CHECK_LEN(cancellation_type, Len, 1, 1), decode_ie(Tail, Map#{cancellation_type => CancellationType}); decode_ie(<>, Map) -> ?CHECK_LEN(freeze_p_tmsi, Len, 0, 0), decode_ie(Tail, Map#{freeze_p_tmsi => true}); decode_ie(<>, Map) -> ?CHECK_LEN(msisdn, Len, 0, 8), decode_ie(Tail, Map#{msisdn => MSISDN}); decode_ie(<>, Map) -> ?CHECK_LEN(hlr_number, Len, 0, 8), decode_ie(Tail, Map#{hlr_number => HLRNumber}); decode_ie(<>, Map) -> ?CHECK_LEN(message_class, Len, 1, 1), decode_ie(Tail, Map#{message_class => MessageClass}); decode_ie(<>, Map) -> ?CHECK_LEN(pdp_context_id, Len, 1, 1), List = maps:get(pdp_context_id, Map, []), ?CHECK_LEN(pdp_context_id_list, length(List) + 1, 1, 10), decode_ie(Tail, Map#{pdp_context_id => List ++ [PDPContextId]}); decode_ie(<>, Map) -> ?CHECK_LEN(pdp_charging, Len, 2, 2), decode_ie(Tail, Map#{pdp_charging => PDPCharging}); decode_ie(<>, Map) -> ?CHECK_LEN(rand, Len, 16, 16), decode_ie(Tail, Map#{rand => Rand}); decode_ie(<>, Map) -> ?CHECK_LEN(auts, Len, 14, 14), decode_ie(Tail, Map#{auts => AUTS}); decode_ie(<>, Map) -> ?CHECK_LEN(cn_domain, Len, 1, 1), decode_ie(Tail, Map#{cn_domain => CN_Domain}); decode_ie(<>, Map) -> ?CHECK_LEN(supported_rat_types, Len, 1, 8), decode_ie(Tail, Map#{supported_rat_types => decode_rat_types(binary_to_list(Rat_Type))}); decode_ie(<>, Map) -> ?CHECK_LEN(current_rat_type, Len, 1, 1), decode_ie(Tail, Map#{current_rat_type => decode_rat_type(Rat_Type)}); decode_ie(<>, Map) -> ?CHECK_LEN(session_id, Len, 4, 4), decode_ie(Tail, Map#{session_id => SesID}); decode_ie(<>, Map) -> ?CHECK_LEN(session_state, Len, 1, 1), decode_ie(Tail, Map#{session_state => SesState}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{ss_info => SesInfo}); decode_ie(<>, Map) -> ?CHECK_LEN(sm_rp_mr, Len, 1, 1), decode_ie(Tail, Map#{sm_rp_mr => MsgRef}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{sm_rp_da => DA}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{sm_rp_oa => OA}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{sm_rp_ui => MessageBody}); decode_ie(<>, Map) -> ?CHECK_LEN(sm_rp_cause, Len, 1, 1), decode_ie(Tail, Map#{sm_rp_cause => RPCause}); decode_ie(<>, Map) -> ?CHECK_LEN(sm_rp_mms, Len, 1, 1), decode_ie(Tail, Map#{sm_rp_mms => RPMMS}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{sm_alert_reason => AlertReason}); decode_ie(<>, Map) -> ?CHECK_LEN(imei, Len, 9, 9), decode_ie(Tail, Map#{imei => IMEI}); decode_ie(<>, Map) -> ?CHECK_LEN(imei_check_result, Len, 1, 1), decode_ie(Tail, Map#{imei_check_result => IMEIResult}); decode_ie(<>, Map) -> ?CHECK_LEN(num_vectors_req, Len, 1, 1), decode_ie(Tail, Map#{num_vectors_req => NumVectorsRequest}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{source_name => SourceName}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{destination_name => DestName}); decode_ie(<>, Map) -> decode_ie(Tail, Map#{an_apdu => AN_APDU}); decode_ie(<>, Map) -> ?CHECK_LEN(rr_cause, Len, 1, 1), decode_ie(Tail, Map#{rr_cause => RRCause}); decode_ie(<>, Map) -> ?CHECK_LEN(bssap_cause, Len, 1, 1), decode_ie(Tail, Map#{bssap_cause => BSSAPCause}); decode_ie(<>, Map) -> ?CHECK_LEN(session_management_cause, Len, 1, 1), decode_ie(Tail, Map#{session_management_cause => SMCause}); decode_ie(<<_, Len, _:Len/binary, Tail/binary>>, Map) -> %% skip unknown IE decode_ie(Tail, Map); decode_ie(X, Map) -> error({cannot_decode_ie, X, Map}). -spec decode_bcd(binary()) -> binary(). decode_bcd(BCDNumber) -> decode_bcd(BCDNumber, <<>>). decode_bcd(<<>>, Number) -> Number; decode_bcd(<>, Number) when A < 15, B < 15 -> decode_bcd(Tail, <>); decode_bcd(<<_:4, B:4, _Tail/binary>>, Number) when B < 15 -> <>. decode_nibble(0) -> $0; decode_nibble(1) -> $1; decode_nibble(2) -> $2; decode_nibble(3) -> $3; decode_nibble(4) -> $4; decode_nibble(5) -> $5; decode_nibble(6) -> $6; decode_nibble(7) -> $7; decode_nibble(8) -> $8; decode_nibble(9) -> $9; decode_nibble(10) -> $*; decode_nibble(11) -> $#; decode_nibble(12) -> $a; decode_nibble(13) -> $b; decode_nibble(14) -> $c. decode_auth_tuple(<>, Map) -> ?CHECK_LEN(rand, Len, 16, 16), decode_auth_tuple(Tail, Map#{rand => Rand}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(sres, Len, 4, 4), decode_auth_tuple(Tail, Map#{sres => SRES}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(kc, Len, 8, 8), decode_auth_tuple(Tail, Map#{kc => KC}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(ik, Len, 16, 16), decode_auth_tuple(Tail, Map#{ik => IK}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(ck, Len, 16, 16), decode_auth_tuple(Tail, Map#{ck => CK}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(autn, Len, 16, 16), decode_auth_tuple(Tail, Map#{autn => AUTN}); decode_auth_tuple(<>, Map) -> ?CHECK_LEN(res, Len, 0, 16), decode_auth_tuple(Tail, Map#{res => Res}); decode_auth_tuple(<<>>, Map) -> Map. decode_pdp_info(<>, Map) -> ?CHECK_LEN(pdp_context_id, Len, 1, 1), decode_pdp_info(Tail, Map#{pdp_context_id => PDPContextId}); decode_pdp_info(<>, Map) -> ?CHECK_LEN(pdp_type, Len, 2, 2), decode_pdp_info(Tail, Map#{pdp_type => PDPType}); decode_pdp_info(<>, Map) -> ?CHECK_LEN(access_point_name, Len, 1, 100), decode_pdp_info(Tail, Map#{access_point_name => APN}); decode_pdp_info(<>, Map) -> ?CHECK_LEN(quality_of_service, Len, 1, 20), decode_pdp_info(Tail, Map#{quality_of_service => QOS}); decode_pdp_info(<>, Map) -> ?CHECK_LEN(pdp_charging, Len, 2, 2), decode_pdp_info(Tail, Map#{pdp_charging => PDPCharging}); decode_pdp_info(<<>>, Map) -> Map. decode_rat_type(0) -> rat_unknown; decode_rat_type(1) -> rat_geran_a; decode_rat_type(2) -> rat_utran_iu; decode_rat_type(3) -> rat_eutran_sgs. decode_rat_types([], Acc) -> lists:reverse(Acc); decode_rat_types([Head|Tail], Acc) -> T = decode_rat_type(Head), decode_rat_types(Tail, [T|Acc]). decode_rat_types(List) -> decode_rat_types(List, []). -spec encode('GSUPMessage'()) -> binary() | no_return(). encode(GSUPMessage = #{message_type := MsgTypeAtom}) when is_atom(MsgTypeAtom) -> F = fun (MsgType_, #{message_type := MsgTypeAtom_}, undefined) when MsgTypeAtom_ == MsgTypeAtom -> MsgType_; (_, _, Acc) -> Acc end, case maps:fold(F, undefined, ?GSUP_MESSAGES()) of undefined -> error({unknown_message_type, MsgTypeAtom}), MsgType = undefined; MsgType when is_integer(MsgType) -> ok end, encode(MsgType, GSUPMessage). encode(MsgType, GSUPMessage) when is_integer(MsgType), is_map(GSUPMessage), MsgType >=0, MsgType =< 255 -> case ?GSUP_MESSAGES() of #{MsgType := #{message_type := MsgTypeAtom, mandatory := Mandatory0} = Map} -> Mandatory = Mandatory0 ++ ?MANDATORY_DEFAULT, Possible = Mandatory ++ maps:get(optional, Map, []) ++ ?OPTIONAL_DEFAULT, case {maps:size(maps:with(Mandatory, GSUPMessage)) == length(Mandatory), maps:size(maps:without(Possible, GSUPMessage)) == 0} of {true, true} -> Tail = encode_ie(GSUPMessage, <<>>), <>; {false, _} -> error({mandatory_ie_missing, MsgTypeAtom, Mandatory -- maps:keys(GSUPMessage)}); {_, false} -> error({ie_not_expected, MsgTypeAtom, maps:keys(GSUPMessage) -- Possible}) end; _ -> error({unknown_gsup_msg_type, MsgType}) end. encode_ie(#{imsi := Value0} = GSUPMessage, Head) -> Value = encode_bcd(Value0, <<>>), Len = size(Value), ?CHECK_LEN(imsi, Len, 0, 8), encode_ie(maps:without([imsi], GSUPMessage), <>); encode_ie(#{cause := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(cause, Len, Value), encode_ie(maps:without([cause], GSUPMessage), <>); encode_ie(#{auth_tuples := Tuples0} = GSUPMessage, Head) -> ?CHECK_LEN(auth_tuples, length(Tuples0), 0, 5), Tuples = << begin check_auth_tuple(Tuple) orelse error({bad_auth_tuple, Tuple}), Value = encode_auth_tuple(Tuple, <<>>), Len = size(Value), <> end || Tuple <- Tuples0>>, encode_ie(maps:without([auth_tuples], GSUPMessage), <>); encode_ie(#{msisdn := Value} = GSUPMessage, Head) -> Len = size(Value), ?CHECK_LEN(msisdn, Len, 0, 8), encode_ie(maps:without([msisdn], GSUPMessage), <>); encode_ie(#{hlr_number := Value} = GSUPMessage, Head) -> Len = size(Value), ?CHECK_LEN(hlr_number, Len, 0, 8), encode_ie(maps:without([hlr_number], GSUPMessage), <>); encode_ie(#{pdp_info_complete := true} = GSUPMessage, Head) -> encode_ie(maps:without([pdp_info_complete], GSUPMessage), <>); encode_ie(#{pdp_info_complete := _} = _GSUPMessage, _Head) -> error(pdp_info_complete_must_be_true); encode_ie(#{pdp_info_list := PDPInfoList0} = GSUPMessage, Head) -> %% PDPInfo ?CHECK_LEN(pdp_info_list, length(PDPInfoList0), 0, 10), PDPInfoList = << begin check_pdp_info(PDPInfo) orelse error({bad_pdp_info, PDPInfo}), Value = encode_pdp_info(PDPInfo, <<>>), Len = size(Value), <> end || PDPInfo <- PDPInfoList0>>, encode_ie(maps:without([pdp_info_list], GSUPMessage), <>); encode_ie(#{cancellation_type := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(cancellation_type, Len, Value), encode_ie(maps:without([cancellation_type], GSUPMessage), <>); encode_ie(#{freeze_p_tmsi := true} = GSUPMessage, Head) -> encode_ie(maps:without([freeze_p_tmsi], GSUPMessage), <>); encode_ie(#{freeze_p_tmsi := _} = _GSUPMessage, _Head) -> error(freeze_p_tmsi_must_be_true); encode_ie(#{session_id := Value} = GSUPMessage, Head) -> Len = 4, ?CHECK_SIZE(session_id, Len, Value), encode_ie(maps:without([session_id], GSUPMessage), <>); encode_ie(#{session_state := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(session_state, Len, Value), encode_ie(maps:without([session_state], GSUPMessage), <>); encode_ie(#{message_class := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(message_class, Len, Value), encode_ie(maps:without([message_class], GSUPMessage), <>); encode_ie(#{pdp_context_id := PDPCIdList0} = GSUPMessage, Head) -> Len = 1, PDPCIdList = << begin ?CHECK_SIZE(pdp_context_id, Len, Value), <> end || Value <- PDPCIdList0>>, encode_ie(maps:without([pdp_context_id], GSUPMessage), <>); encode_ie(#{pdp_charging := Value} = GSUPMessage, Head) -> Len = 2, ?CHECK_SIZE(pdp_charging, Len, Value), encode_ie(maps:without([pdp_charging], GSUPMessage), <>); encode_ie(#{auts := Value} = GSUPMessage, Head) -> Len = 14, ?CHECK_LEN(auts, size(Value), Len, Len), encode_ie(maps:without([auts], GSUPMessage), <>); encode_ie(#{rand := Value} = GSUPMessage, Head) -> Len = 16, ?CHECK_LEN(rand, size(Value), Len, Len), encode_ie(maps:without([rand], GSUPMessage), <>); encode_ie(#{cn_domain := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(cn_domain, Len, Value), encode_ie(maps:without([cn_domain], GSUPMessage), <>); encode_ie(#{supported_rat_types := Value} = GSUPMessage, Head) when is_list(Value) -> Len = length(Value), ?CHECK_LEN(supported_rat_types, Len, 1, 8), RatList = encode_rat_types(Value), encode_ie(maps:without([supported_rat_types], GSUPMessage), <>); encode_ie(#{current_rat_type := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_LEN(current_rat_type, Len, 1, 1), Rat = encode_rat_type(Value), encode_ie(maps:without([current_rat_type], GSUPMessage), <>); encode_ie(#{ss_info := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([ss_info], GSUPMessage), <>); encode_ie(#{sm_rp_mr := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(sm_rp_mr, Len, Value), encode_ie(maps:without([sm_rp_mr], GSUPMessage), <>); encode_ie(#{sm_rp_da := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([sm_rp_da], GSUPMessage), <>); encode_ie(#{sm_rp_oa := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([sm_rp_oa], GSUPMessage), <>); encode_ie(#{sm_rp_ui := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([sm_rp_ui], GSUPMessage), <>); encode_ie(#{sm_rp_cause := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(sm_rp_cause, Len, Value), encode_ie(maps:without([sm_rp_cause], GSUPMessage), <>); encode_ie(#{sm_rp_mms := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(sm_rp_mms, Len, Value), encode_ie(maps:without([sm_rp_mms], GSUPMessage), <>); encode_ie(#{sm_alert_reason := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(sm_alert_reason, Len, Value), encode_ie(maps:without([sm_alert_reason], GSUPMessage), <>); encode_ie(#{imei := Value} = GSUPMessage, Head) -> Len = size(Value), ?CHECK_LEN(imei, Len, 9, 9), encode_ie(maps:without([imei], GSUPMessage), <>); encode_ie(#{imei_check_result := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(imei_check_result, Len, Value), encode_ie(maps:without([imei_check_result], GSUPMessage), <>); encode_ie(#{num_vectors_req := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(num_vectors_req, Len, Value), encode_ie(maps:without([num_vectors_req], GSUPMessage), <>); encode_ie(#{source_name := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([source_name], GSUPMessage), <>); encode_ie(#{destination_name := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([destination_name], GSUPMessage), <>); encode_ie(#{an_apdu := Value} = GSUPMessage, Head) -> Len = size(Value), encode_ie(maps:without([an_apdu], GSUPMessage), <>); encode_ie(#{rr_cause := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(rr_cause, Len, Value), encode_ie(maps:without([rr_cause], GSUPMessage), <>); encode_ie(#{bssap_cause := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(bssap_cause, Len, Value), encode_ie(maps:without([bssap_cause], GSUPMessage), <>); encode_ie(#{session_management_cause := Value} = GSUPMessage, Head) -> Len = 1, ?CHECK_SIZE(session_management_cause, Len, Value), encode_ie(maps:without([session_management_cause], GSUPMessage), <>); encode_ie(_, Head) -> Head. encode_bcd(BCDNumber) -> encode_bcd(BCDNumber, <<>>). encode_bcd(<>, BCDNumber) -> encode_bcd(Tail, <>); encode_bcd(<>, BCDNumber) -> <>; encode_bcd(<<>>, BCDNumber) -> BCDNumber. encode_nibble($0) -> 0; encode_nibble($1) -> 1; encode_nibble($2) -> 2; encode_nibble($3) -> 3; encode_nibble($4) -> 4; encode_nibble($5) -> 5; encode_nibble($6) -> 6; encode_nibble($7) -> 7; encode_nibble($8) -> 8; encode_nibble($9) -> 9; encode_nibble($*) -> 10; encode_nibble($#) -> 11; encode_nibble($a) -> 12; encode_nibble($b) -> 13; encode_nibble($c) -> 14; encode_nibble($A) -> 12; encode_nibble($B) -> 13; encode_nibble($C) -> 14; encode_nibble(X) -> error({bad_bcd_character, X}). check_auth_tuple(AuthTuple) -> Mandatory = ?AUTH_TUPLE_MANDATORY, Possible = Mandatory ++ ?AUTH_TUPLE_OPTIONAL, (maps:size(maps:with(Mandatory, AuthTuple)) == length(Mandatory)) orelse error({mandatory_ie_missing, auth_tuples, Mandatory -- maps:keys(AuthTuple)}), (maps:size(maps:without(Possible, AuthTuple)) == 0) orelse error({ie_not_expected, auth_tuples, maps:keys(AuthTuple) -- Possible}). check_pdp_info(PDPInfo) -> Mandatory = ?PDP_INFO_MANDATORY, Possible = Mandatory ++ ?PDP_INFO_OPTIONAL, (maps:size(maps:with(Mandatory, PDPInfo)) == length(Mandatory)) orelse error({mandatory_ie_missing, pdp_info_list, Mandatory -- maps:keys(PDPInfo)}), (maps:size(maps:without(Possible, PDPInfo)) == 0) orelse error({ie_not_expected, pdp_info_list, maps:keys(PDPInfo) -- Possible}). encode_auth_tuple(#{rand := Value} = Map, Head) -> Len = 16, ?CHECK_LEN(rand, size(Value), Len, Len), encode_auth_tuple(maps:without([rand], Map), <>); encode_auth_tuple(#{sres := Value} = Map, Head) -> Len = 4, ?CHECK_LEN(sres, size(Value), Len, Len), encode_auth_tuple(maps:without([sres], Map), <>); encode_auth_tuple(#{kc := Value} = Map, Head) -> Len = 8, ?CHECK_LEN(kc, size(Value), Len, Len), encode_auth_tuple(maps:without([kc], Map), <>); encode_auth_tuple(#{ik := Value} = Map, Head) -> Len = 16, ?CHECK_LEN(ik, size(Value), Len, Len), encode_auth_tuple(maps:without([ik], Map), <>); encode_auth_tuple(#{ck := Value} = Map, Head) -> Len = 16, ?CHECK_LEN(ck, size(Value), Len, Len), encode_auth_tuple(maps:without([ck], Map), <>); encode_auth_tuple(#{autn := Value} = Map, Head) -> Len = 16, ?CHECK_LEN(autn, size(Value), Len, Len), encode_auth_tuple(maps:without([autn], Map), <>); encode_auth_tuple(#{res := Value} = Map, Head) -> Len = size(Value), ?CHECK_LEN(res, size(Value), Len, Len), encode_auth_tuple(maps:without([res], Map), <>); encode_auth_tuple(#{}, Head) -> Head. encode_pdp_info(#{pdp_context_id := Value} = Map, Head) -> Len = 1, ?CHECK_SIZE(pdp_context_id, Len, Value), encode_pdp_info(maps:without([pdp_context_id], Map), <>); encode_pdp_info(#{pdp_type := Value} = Map, Head) -> Len = 2, ?CHECK_SIZE(pdp_type, Len, Value), encode_pdp_info(maps:without([pdp_type], Map), <>); encode_pdp_info(#{access_point_name := Value} = Map, Head) -> Len = size(Value), ?CHECK_LEN(access_point_name, Len, 1, 100), encode_pdp_info(maps:without([access_point_name], Map), <>); encode_pdp_info(#{quality_of_service := Value} = Map, Head) -> Len = size(Value), ?CHECK_LEN(quality_of_service, Len, 1, 20), encode_pdp_info(maps:without([quality_of_service], Map), <>); encode_pdp_info(#{pdp_charging := Value} = Map, Head) -> Len = 2, ?CHECK_SIZE(pdp_charging, Len, Value), encode_pdp_info(maps:without([pdp_charging], Map), <>); encode_pdp_info(#{}, Head) -> Head. encode_rat_type(rat_unknown) -> 0; encode_rat_type(rat_geran_a) -> 1; encode_rat_type(rat_utran_iu) -> 2; encode_rat_type(rat_eutran_sgs) -> 3. encode_rat_types([], Acc) -> list_to_binary(lists:reverse(Acc)); encode_rat_types([Head|Tail], Acc) -> T = encode_rat_type(Head), encode_rat_types(Tail, [T|Acc]). encode_rat_types(List) -> encode_rat_types(List, []).