Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 1 | % This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | % License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | % file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 4 | % (C) 2019 Andrey Velikiy <agreat22@gmail.com> |
| 5 | % (C) 2019 Fairwaves (edited) |
| 6 | |
| 7 | -module(gsup_protocol). |
| 8 | |
| 9 | -include ("gsup_protocol.hrl"). |
| 10 | -include ("ipa.hrl"). |
| 11 | |
| 12 | -export([decode/1, encode/1, decode_bcd/1, encode_bcd/1]). |
| 13 | -export_type(['GSUPMessage'/0, 'GSUPMessageType'/0]). |
| 14 | |
| 15 | -define (CHECK_SIZE(IE, Len, Value), |
| 16 | Value >= 0 andalso Value < (1 bsl (Len * 8)) orelse error({ie_value_length_mismatch, IE, Value}) |
| 17 | ). |
| 18 | |
| 19 | -define (CHECK_LEN(IE, Len, Min, Max), |
| 20 | Len >= Min andalso Len =< Max orelse error({ie_length_mismatch, IE, Len}) |
| 21 | ). |
| 22 | |
| 23 | -ifdef (TEST). |
| 24 | -export ([encode_ie/2, decode_ie/2]). |
| 25 | -endif. |
| 26 | |
| 27 | -spec decode(binary()) -> 'GSUPMessage'() | no_return(). |
| 28 | decode(<<MsgType, Tail/binary>>) -> |
| 29 | case ?GSUP_MESSAGES() of |
| 30 | #{MsgType := #{message_type := MsgTypeAtom, mandatory := Mandatory0}} -> |
| 31 | GSUPMessage = decode_ie(Tail, #{message_type => MsgTypeAtom}), |
| 32 | Mandatory = Mandatory0 ++ ?MANDATORY_DEFAULT, |
| 33 | case maps:size(maps:with(Mandatory, GSUPMessage)) == length(Mandatory) of |
| 34 | true -> GSUPMessage; |
| 35 | false -> error({mandatory_ie_missing, MsgTypeAtom, Mandatory -- maps:keys(GSUPMessage)}) |
| 36 | end; |
| 37 | _ -> |
| 38 | error({unknown_gsup_msg_type, MsgType}) |
| 39 | end. |
| 40 | |
| 41 | decode_ie(<<>>, Map) -> Map; |
| 42 | |
| 43 | decode_ie(<<?IMSI, Len, IMSI:Len/binary, Tail/binary>>, Map) -> |
| 44 | ?CHECK_LEN(imsi, Len, 0, 8), |
| 45 | decode_ie(Tail, Map#{imsi => decode_bcd(IMSI, <<>>)}); |
| 46 | |
| 47 | decode_ie(<<?CAUSE, Len, Cause:Len/unit:8, Tail/binary>>, Map) -> |
| 48 | ?CHECK_LEN(cause, Len, 1, 1), |
| 49 | decode_ie(Tail, Map#{cause => Cause}); |
| 50 | |
| 51 | decode_ie(<<?AUTH_TUPLE, Len, AuthTuple0:Len/binary, Tail/binary>>, Map) -> |
| 52 | List = maps:get(auth_tuples, Map, []), |
| 53 | ?CHECK_LEN(auth_tuples, length(List) + 1, 1, 5), |
| 54 | AuthTuple = decode_auth_tuple(AuthTuple0, #{}), |
| 55 | check_auth_tuple(AuthTuple) orelse error({bad_auth_tuple, AuthTuple}), |
| 56 | decode_ie(Tail, Map#{auth_tuples => List ++ [AuthTuple]}); |
| 57 | |
| 58 | decode_ie(<<?PDP_INFO_COMPLETE, Len, _:Len/binary, Tail/binary>>, Map) -> |
| 59 | ?CHECK_LEN(pdp_info_complete, Len, 0, 0), |
| 60 | decode_ie(Tail, Map#{pdp_info_complete => true}); |
| 61 | |
| 62 | decode_ie(<<?PDP_INFO, Len, PDPInfo0:Len/binary, Tail/binary>>, Map) -> |
| 63 | List = maps:get(pdp_info_list, Map, []), |
| 64 | ?CHECK_LEN(pdp_info_list, length(List) + 1, 1, 10), |
| 65 | PDPInfo = decode_pdp_info(PDPInfo0, #{}), |
| 66 | check_pdp_info(PDPInfo) orelse error({bad_pdp_info, PDPInfo}), |
| 67 | decode_ie(Tail, Map#{pdp_info_list => List ++ [PDPInfo]}); |
| 68 | |
| 69 | decode_ie(<<?CANCELLATION_TYPE, Len, CancellationType:Len/unit:8, Tail/binary>>, Map) -> |
| 70 | ?CHECK_LEN(cancellation_type, Len, 1, 1), |
| 71 | decode_ie(Tail, Map#{cancellation_type => CancellationType}); |
| 72 | |
| 73 | decode_ie(<<?FREEZE_P_TMSI, Len, _:Len/binary, Tail/binary>>, Map) -> |
| 74 | ?CHECK_LEN(freeze_p_tmsi, Len, 0, 0), |
| 75 | decode_ie(Tail, Map#{freeze_p_tmsi => true}); |
| 76 | |
| 77 | decode_ie(<<?MSISDN, Len, MSISDN:Len/binary, Tail/binary>>, Map) -> |
| 78 | ?CHECK_LEN(msisdn, Len, 0, 8), |
| 79 | decode_ie(Tail, Map#{msisdn => MSISDN}); |
| 80 | |
| 81 | decode_ie(<<?HLR_NUMBER, Len, HLRNumber:Len/binary, Tail/binary>>, Map) -> |
| 82 | ?CHECK_LEN(hlr_number, Len, 0, 8), |
| 83 | decode_ie(Tail, Map#{hlr_number => HLRNumber}); |
| 84 | |
Andrey Velikiy | c66c8d0 | 2019-06-19 14:16:52 +0300 | [diff] [blame] | 85 | decode_ie(<<?MESSAGE_CLASS, Len, MessageClass:Len/unit:8, Tail/binary>>, Map) -> |
| 86 | ?CHECK_LEN(message_class, Len, 1, 1), |
| 87 | decode_ie(Tail, Map#{message_class => MessageClass}); |
| 88 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 89 | decode_ie(<<?PDP_CONTEXT_ID, Len, PDPContextId:Len/unit:8, Tail/binary>>, Map) -> |
| 90 | ?CHECK_LEN(pdp_context_id, Len, 1, 1), |
| 91 | List = maps:get(pdp_context_id, Map, []), |
| 92 | ?CHECK_LEN(pdp_context_id_list, length(List) + 1, 1, 10), |
| 93 | decode_ie(Tail, Map#{pdp_context_id => List ++ [PDPContextId]}); |
| 94 | |
| 95 | decode_ie(<<?PDP_CHARGING, Len, PDPCharging:Len/unit:8, Tail/binary>>, Map) -> |
| 96 | ?CHECK_LEN(pdp_charging, Len, 2, 2), |
| 97 | decode_ie(Tail, Map#{pdp_charging => PDPCharging}); |
| 98 | |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 99 | decode_ie(<<?RAND, Len, Rand:Len/binary, Tail/binary>>, Map) -> |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 100 | ?CHECK_LEN(rand, Len, 16, 16), |
| 101 | decode_ie(Tail, Map#{rand => Rand}); |
| 102 | |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 103 | decode_ie(<<?AUTS, Len, AUTS:Len/binary, Tail/binary>>, Map) -> |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 104 | ?CHECK_LEN(auts, Len, 14, 14), |
| 105 | decode_ie(Tail, Map#{auts => AUTS}); |
| 106 | |
| 107 | decode_ie(<<?CN_DOMAIN, Len, CN_Domain:Len/unit:8, Tail/binary>>, Map) -> |
| 108 | ?CHECK_LEN(cn_domain, Len, 1, 1), |
| 109 | decode_ie(Tail, Map#{cn_domain => CN_Domain}); |
| 110 | |
Harald Welte | b76c7fa | 2019-08-22 13:48:00 +0200 | [diff] [blame^] | 111 | decode_ie(<<?RAT_TYPE, Len, Rat_Type:Len/binary, Tail/binary>>, Map) -> |
| 112 | ?CHECK_LEN(rat_type, Len, 1, 8), |
| 113 | decode_ie(Tail, Map#{rat_type => decode_rat_types(binary_to_list(Rat_Type))}); |
| 114 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 115 | decode_ie(<<?SESSION_ID, Len, SesID:Len/unit:8, Tail/binary>>, Map) -> |
| 116 | ?CHECK_LEN(session_id, Len, 4, 4), |
| 117 | decode_ie(Tail, Map#{session_id => SesID}); |
| 118 | |
| 119 | decode_ie(<<?SESSION_STATE, Len, SesState:Len/unit:8, Tail/binary>>, Map) -> |
| 120 | ?CHECK_LEN(session_state, Len, 1, 1), |
| 121 | decode_ie(Tail, Map#{session_state => SesState}); |
| 122 | |
| 123 | decode_ie(<<?SS_INFO, Len, SesInfo:Len/binary, Tail/binary>>, Map) -> |
| 124 | decode_ie(Tail, Map#{ss_info => SesInfo}); |
| 125 | |
| 126 | decode_ie(<<?SM_RP_MR, Len, MsgRef:Len/unit:8, Tail/binary>>, Map) -> |
| 127 | ?CHECK_LEN(sm_rp_mr, Len, 1, 1), |
| 128 | decode_ie(Tail, Map#{sm_rp_mr => MsgRef}); |
| 129 | |
| 130 | decode_ie(<<?SM_RP_DA, Len, DA:Len/binary, Tail/binary>>, Map) -> |
| 131 | decode_ie(Tail, Map#{sm_rp_da => DA}); |
| 132 | |
| 133 | decode_ie(<<?SM_RP_OA, Len, OA:Len/binary, Tail/binary>>, Map) -> |
| 134 | decode_ie(Tail, Map#{sm_rp_oa => OA}); |
| 135 | |
| 136 | decode_ie(<<?SM_RP_UI, Len, MessageBody:Len/binary, Tail/binary>>, Map) -> |
| 137 | decode_ie(Tail, Map#{sm_rp_ui => MessageBody}); |
| 138 | |
| 139 | decode_ie(<<?SM_RP_CAUSE, Len, RPCause:Len/unit:8, Tail/binary>>, Map) -> |
| 140 | ?CHECK_LEN(sm_rp_cause, Len, 1, 1), |
| 141 | decode_ie(Tail, Map#{sm_rp_cause => RPCause}); |
| 142 | |
| 143 | decode_ie(<<?SM_RP_MMS, Len, RPMMS:Len/unit:8, Tail/binary>>, Map) -> |
| 144 | ?CHECK_LEN(sm_rp_mms, Len, 1, 1), |
| 145 | decode_ie(Tail, Map#{sm_rp_mms => RPMMS}); |
| 146 | |
| 147 | decode_ie(<<?SM_ALERT_REASON, Len, AlertReason:Len/unit:8, Tail/binary>>, Map) -> |
| 148 | decode_ie(Tail, Map#{sm_alert_reason => AlertReason}); |
| 149 | |
| 150 | decode_ie(<<?IMEI, Len, IMEI:Len/binary, Tail/binary>>, Map) -> |
| 151 | ?CHECK_LEN(imei, Len, 9, 9), |
| 152 | decode_ie(Tail, Map#{imei => IMEI}); |
| 153 | |
| 154 | decode_ie(<<?IMEI_CHECK_RESULT, Len, IMEIResult:Len/unit:8, Tail/binary>>, Map) -> |
| 155 | ?CHECK_LEN(imei_check_result, Len, 1, 1), |
| 156 | decode_ie(Tail, Map#{imei_check_result => IMEIResult}); |
| 157 | |
Andrey Velikiy | bb29966 | 2019-06-19 14:19:10 +0300 | [diff] [blame] | 158 | decode_ie(<<?SOURCE_NAME, Len, SourceName:Len/binary, Tail/binary>>, Map) -> |
| 159 | decode_ie(Tail, Map#{source_name => SourceName}); |
| 160 | |
| 161 | decode_ie(<<?DESTINATION_NAME, Len, DestName:Len/binary, Tail/binary>>, Map) -> |
| 162 | decode_ie(Tail, Map#{destination_name => DestName}); |
| 163 | |
| 164 | decode_ie(<<?AN_APDU, Len, AN_APDU:Len/binary, Tail/binary>>, Map) -> |
| 165 | decode_ie(Tail, Map#{an_apdu => AN_APDU}); |
| 166 | |
| 167 | decode_ie(<<?RR_CAUSE, Len, RRCause:Len/unit:8, Tail/binary>>, Map) -> |
| 168 | ?CHECK_LEN(rr_cause, Len, 1, 1), |
| 169 | decode_ie(Tail, Map#{rr_cause => RRCause}); |
| 170 | |
| 171 | decode_ie(<<?BSSAP_CAUSE, Len, BSSAPCause:Len/unit:8, Tail/binary>>, Map) -> |
| 172 | ?CHECK_LEN(bssap_cause, Len, 1, 1), |
| 173 | decode_ie(Tail, Map#{bssap_cause => BSSAPCause}); |
| 174 | |
| 175 | decode_ie(<<?SESSION_MANAGEMENT_CAUSE, Len, SMCause:Len/unit:8, Tail/binary>>, Map) -> |
| 176 | ?CHECK_LEN(session_management_cause, Len, 1, 1), |
| 177 | decode_ie(Tail, Map#{session_management_cause => SMCause}); |
| 178 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 179 | decode_ie(<<_, Len, _:Len/binary, Tail/binary>>, Map) -> %% skip unknown IE |
| 180 | decode_ie(Tail, Map); |
| 181 | |
| 182 | decode_ie(X, Map) -> |
| 183 | error({cannot_decode_ie, X, Map}). |
| 184 | |
| 185 | -spec decode_bcd(binary()) -> binary(). |
| 186 | decode_bcd(BCDNumber) -> decode_bcd(BCDNumber, <<>>). |
| 187 | |
| 188 | decode_bcd(<<>>, Number) -> Number; |
| 189 | |
| 190 | decode_bcd(<<A:4, B:4, Tail/binary>>, Number) when A < 15, B < 15 -> |
| 191 | decode_bcd(Tail, <<Number/binary, (decode_nibble(B)), (decode_nibble(A))>>); |
| 192 | |
| 193 | decode_bcd(<<_:4, B:4, _Tail/binary>>, Number) when B < 15 -> |
| 194 | <<Number/binary, (decode_nibble(B))>>. |
| 195 | |
| 196 | decode_nibble(0) -> $0; |
| 197 | decode_nibble(1) -> $1; |
| 198 | decode_nibble(2) -> $2; |
| 199 | decode_nibble(3) -> $3; |
| 200 | decode_nibble(4) -> $4; |
| 201 | decode_nibble(5) -> $5; |
| 202 | decode_nibble(6) -> $6; |
| 203 | decode_nibble(7) -> $7; |
| 204 | decode_nibble(8) -> $8; |
| 205 | decode_nibble(9) -> $9; |
| 206 | decode_nibble(10) -> $*; |
| 207 | decode_nibble(11) -> $#; |
| 208 | decode_nibble(12) -> $a; |
| 209 | decode_nibble(13) -> $b; |
| 210 | decode_nibble(14) -> $c. |
| 211 | |
| 212 | decode_auth_tuple(<<?RAND, Len, Rand:Len/binary, Tail/binary>>, Map) -> |
| 213 | ?CHECK_LEN(rand, Len, 16, 16), |
| 214 | decode_auth_tuple(Tail, Map#{rand => Rand}); |
| 215 | |
| 216 | decode_auth_tuple(<<?SRES, Len, SRES:Len/binary, Tail/binary>>, Map) -> |
| 217 | ?CHECK_LEN(sres, Len, 4, 4), |
| 218 | decode_auth_tuple(Tail, Map#{sres => SRES}); |
| 219 | |
| 220 | decode_auth_tuple(<<?KC, Len, KC:Len/binary, Tail/binary>>, Map) -> |
| 221 | ?CHECK_LEN(kc, Len, 8, 8), |
| 222 | decode_auth_tuple(Tail, Map#{kc => KC}); |
| 223 | |
| 224 | decode_auth_tuple(<<?IK, Len, IK:Len/binary, Tail/binary>>, Map) -> |
| 225 | ?CHECK_LEN(ik, Len, 16, 16), |
| 226 | decode_auth_tuple(Tail, Map#{ik => IK}); |
| 227 | |
| 228 | decode_auth_tuple(<<?CK, Len, CK:Len/binary, Tail/binary>>, Map) -> |
| 229 | ?CHECK_LEN(ck, Len, 16, 16), |
| 230 | decode_auth_tuple(Tail, Map#{ck => CK}); |
| 231 | |
| 232 | decode_auth_tuple(<<?AUTN, Len, AUTN:Len/binary, Tail/binary>>, Map) -> |
| 233 | ?CHECK_LEN(autn, Len, 16, 16), |
| 234 | decode_auth_tuple(Tail, Map#{autn => AUTN}); |
| 235 | |
| 236 | decode_auth_tuple(<<?RES, Len, Res:Len/binary, Tail/binary>>, Map) -> |
| 237 | ?CHECK_LEN(res, Len, 0, 16), |
| 238 | decode_auth_tuple(Tail, Map#{res => Res}); |
| 239 | |
| 240 | decode_auth_tuple(<<>>, Map) -> Map. |
| 241 | |
| 242 | decode_pdp_info(<<?PDP_CONTEXT_ID, Len, PDPContextId:Len/unit:8, Tail/binary>>, Map) -> |
| 243 | ?CHECK_LEN(pdp_context_id, Len, 1, 1), |
| 244 | decode_pdp_info(Tail, Map#{pdp_context_id => PDPContextId}); |
| 245 | |
| 246 | decode_pdp_info(<<?PDP_TYPE, Len, PDPType:Len/unit:8, Tail/binary>>, Map) -> |
| 247 | ?CHECK_LEN(pdp_type, Len, 2, 2), |
| 248 | decode_pdp_info(Tail, Map#{pdp_type => PDPType}); |
| 249 | |
| 250 | decode_pdp_info(<<?ACCESS_POINT_NAME, Len, APN:Len/binary, Tail/binary>>, Map) -> |
| 251 | ?CHECK_LEN(access_point_name, Len, 1, 100), |
| 252 | decode_pdp_info(Tail, Map#{access_point_name => APN}); |
| 253 | |
| 254 | decode_pdp_info(<<?QUALITY_OF_SERVICE, Len, QOS:Len/binary, Tail/binary>>, Map) -> |
| 255 | ?CHECK_LEN(quality_of_service, Len, 1, 20), |
| 256 | decode_pdp_info(Tail, Map#{quality_of_service => QOS}); |
| 257 | |
| 258 | decode_pdp_info(<<?PDP_CHARGING, Len, PDPCharging:Len/unit:8, Tail/binary>>, Map) -> |
| 259 | ?CHECK_LEN(pdp_charging, Len, 2, 2), |
| 260 | decode_pdp_info(Tail, Map#{pdp_charging => PDPCharging}); |
| 261 | |
| 262 | decode_pdp_info(<<>>, Map) -> Map. |
| 263 | |
Harald Welte | b76c7fa | 2019-08-22 13:48:00 +0200 | [diff] [blame^] | 264 | decode_rat_type(0) -> rat_unknown; |
| 265 | decode_rat_type(1) -> rat_geran_a; |
| 266 | decode_rat_type(2) -> rat_utran_iu; |
| 267 | decode_rat_type(3) -> rat_eutran_sgs. |
| 268 | |
| 269 | decode_rat_types([], Acc) -> lists:reverse(Acc); |
| 270 | decode_rat_types([Head|Tail], Acc) -> |
| 271 | T = decode_rat_type(Head), |
| 272 | decode_rat_types(Tail, [T|Acc]). |
| 273 | decode_rat_types(List) -> decode_rat_types(List, []). |
| 274 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 275 | -spec encode('GSUPMessage'()) -> binary() | no_return(). |
| 276 | encode(GSUPMessage = #{message_type := MsgTypeAtom}) when is_atom(MsgTypeAtom) -> |
| 277 | F = fun |
| 278 | (MsgType_, #{message_type := MsgTypeAtom_}, undefined) when MsgTypeAtom_ == MsgTypeAtom -> MsgType_; |
| 279 | (_, _, Acc) -> Acc |
| 280 | end, |
| 281 | case maps:fold(F, undefined, ?GSUP_MESSAGES()) of |
| 282 | undefined -> error({unknown_message_type, MsgTypeAtom}), MsgType = undefined; |
| 283 | MsgType when is_integer(MsgType) -> ok |
| 284 | end, |
| 285 | encode(MsgType, GSUPMessage). |
| 286 | |
| 287 | encode(MsgType, GSUPMessage) when is_integer(MsgType), is_map(GSUPMessage), MsgType >=0, MsgType =< 255 -> |
| 288 | case ?GSUP_MESSAGES() of |
| 289 | #{MsgType := #{message_type := MsgTypeAtom, mandatory := Mandatory0} = Map} -> |
| 290 | Mandatory = Mandatory0 ++ ?MANDATORY_DEFAULT, |
Andrey Velikiy | c66c8d0 | 2019-06-19 14:16:52 +0300 | [diff] [blame] | 291 | Possible = Mandatory ++ maps:get(optional, Map, []) ++ ?OPTIONAL_DEFAULT, |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 292 | case {maps:size(maps:with(Mandatory, GSUPMessage)) == length(Mandatory), |
| 293 | maps:size(maps:without(Possible, GSUPMessage)) == 0} of |
| 294 | {true, true} -> |
| 295 | Tail = encode_ie(GSUPMessage, <<>>), |
| 296 | <<MsgType, Tail/binary>>; |
| 297 | {false, _} -> error({mandatory_ie_missing, MsgTypeAtom, Mandatory -- maps:keys(GSUPMessage)}); |
| 298 | {_, false} -> error({ie_not_expected, MsgTypeAtom, maps:keys(GSUPMessage) -- Possible}) |
| 299 | end; |
| 300 | _ -> |
| 301 | error({unknown_gsup_msg_type, MsgType}) |
| 302 | end. |
| 303 | |
| 304 | encode_ie(#{imsi := Value0} = GSUPMessage, Head) -> |
| 305 | Value = encode_bcd(Value0, <<>>), |
| 306 | Len = size(Value), |
| 307 | ?CHECK_LEN(imsi, Len, 0, 8), |
| 308 | encode_ie(maps:without([imsi], GSUPMessage), <<Head/binary, ?IMSI, Len, Value/binary>>); |
| 309 | |
| 310 | encode_ie(#{cause := Value} = GSUPMessage, Head) -> |
| 311 | Len = 1, |
| 312 | ?CHECK_SIZE(cause, Len, Value), |
| 313 | encode_ie(maps:without([cause], GSUPMessage), <<Head/binary, ?CAUSE, Len, Value:Len/unit:8>>); |
| 314 | |
| 315 | encode_ie(#{auth_tuples := Tuples0} = GSUPMessage, Head) -> |
| 316 | ?CHECK_LEN(auth_tuples, length(Tuples0), 0, 5), |
| 317 | Tuples = << |
| 318 | begin |
| 319 | check_auth_tuple(Tuple) orelse error({bad_auth_tuple, Tuple}), |
| 320 | Value = encode_auth_tuple(Tuple, <<>>), |
| 321 | Len = size(Value), |
| 322 | <<?AUTH_TUPLE, Len, Value/binary>> |
| 323 | end || Tuple <- Tuples0>>, |
| 324 | encode_ie(maps:without([auth_tuples], GSUPMessage), <<Head/binary, Tuples/binary>>); |
| 325 | |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 326 | encode_ie(#{msisdn := Value} = GSUPMessage, Head) -> |
| 327 | Len = size(Value), |
| 328 | ?CHECK_LEN(msisdn, Len, 0, 8), |
| 329 | encode_ie(maps:without([msisdn], GSUPMessage), <<Head/binary, ?MSISDN, Len, Value/binary>>); |
| 330 | |
| 331 | encode_ie(#{hlr_number := Value} = GSUPMessage, Head) -> |
| 332 | Len = size(Value), |
| 333 | ?CHECK_LEN(hlr_number, Len, 0, 8), |
| 334 | encode_ie(maps:without([hlr_number], GSUPMessage), <<Head/binary, ?HLR_NUMBER, Len, Value/binary>>); |
| 335 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 336 | encode_ie(#{pdp_info_complete := true} = GSUPMessage, Head) -> |
| 337 | encode_ie(maps:without([pdp_info_complete], GSUPMessage), <<Head/binary, ?PDP_INFO_COMPLETE, 0>>); |
| 338 | |
| 339 | encode_ie(#{pdp_info_complete := _} = _GSUPMessage, _Head) -> |
| 340 | error(pdp_info_complete_must_be_true); |
| 341 | |
| 342 | encode_ie(#{pdp_info_list := PDPInfoList0} = GSUPMessage, Head) -> %% PDPInfo |
| 343 | ?CHECK_LEN(pdp_info_list, length(PDPInfoList0), 0, 10), |
| 344 | PDPInfoList = << |
| 345 | begin |
| 346 | check_pdp_info(PDPInfo) orelse error({bad_pdp_info, PDPInfo}), |
| 347 | Value = encode_pdp_info(PDPInfo, <<>>), |
| 348 | Len = size(Value), |
| 349 | <<?PDP_INFO, Len, Value/binary>> |
| 350 | end || PDPInfo <- PDPInfoList0>>, |
| 351 | encode_ie(maps:without([pdp_info_list], GSUPMessage), <<Head/binary, PDPInfoList/binary>>); |
| 352 | |
| 353 | encode_ie(#{cancellation_type := Value} = GSUPMessage, Head) -> |
| 354 | Len = 1, |
| 355 | ?CHECK_SIZE(cancellation_type, Len, Value), |
| 356 | encode_ie(maps:without([cancellation_type], GSUPMessage), <<Head/binary, ?CANCELLATION_TYPE, Len, Value:Len/unit:8>>); |
| 357 | |
| 358 | encode_ie(#{freeze_p_tmsi := true} = GSUPMessage, Head) -> |
| 359 | encode_ie(maps:without([freeze_p_tmsi], GSUPMessage), <<Head/binary, ?FREEZE_P_TMSI, 0>>); |
| 360 | |
| 361 | encode_ie(#{freeze_p_tmsi := _} = _GSUPMessage, _Head) -> |
| 362 | error(freeze_p_tmsi_must_be_true); |
| 363 | |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 364 | encode_ie(#{session_id := Value} = GSUPMessage, Head) -> |
| 365 | Len = 4, |
| 366 | ?CHECK_SIZE(session_id, Len, Value), |
| 367 | encode_ie(maps:without([session_id], GSUPMessage), <<Head/binary, ?SESSION_ID, Len, Value:Len/unit:8>>); |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 368 | |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 369 | encode_ie(#{session_state := Value} = GSUPMessage, Head) -> |
| 370 | Len = 1, |
| 371 | ?CHECK_SIZE(session_state, Len, Value), |
| 372 | encode_ie(maps:without([session_state], GSUPMessage), <<Head/binary, ?SESSION_STATE, Len, Value:Len/unit:8>>); |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 373 | |
Andrey Velikiy | c66c8d0 | 2019-06-19 14:16:52 +0300 | [diff] [blame] | 374 | encode_ie(#{message_class := Value} = GSUPMessage, Head) -> |
| 375 | Len = 1, |
| 376 | ?CHECK_SIZE(message_class, Len, Value), |
| 377 | encode_ie(maps:without([message_class], GSUPMessage), <<Head/binary, ?MESSAGE_CLASS, Len, Value:Len/unit:8>>); |
| 378 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 379 | encode_ie(#{pdp_context_id := PDPCIdList0} = GSUPMessage, Head) -> |
| 380 | Len = 1, |
| 381 | PDPCIdList = << |
| 382 | begin |
| 383 | ?CHECK_SIZE(pdp_context_id, Len, Value), |
| 384 | <<?PDP_CONTEXT_ID, Len, Value:Len/unit:8>> |
| 385 | end || Value <- PDPCIdList0>>, |
| 386 | encode_ie(maps:without([pdp_context_id], GSUPMessage), <<Head/binary, PDPCIdList/binary>>); |
| 387 | |
| 388 | encode_ie(#{pdp_charging := Value} = GSUPMessage, Head) -> |
| 389 | Len = 2, |
| 390 | ?CHECK_SIZE(pdp_charging, Len, Value), |
| 391 | encode_ie(maps:without([pdp_charging], GSUPMessage), <<Head/binary, ?PDP_CHARGING, Len, Value:Len/unit:8>>); |
| 392 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 393 | encode_ie(#{auts := Value} = GSUPMessage, Head) -> |
| 394 | Len = 14, |
| 395 | ?CHECK_LEN(auts, size(Value), Len, Len), |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 396 | encode_ie(maps:without([auts], GSUPMessage), <<Head/binary, ?AUTS, Len, Value:Len/binary>>); |
| 397 | |
| 398 | encode_ie(#{rand := Value} = GSUPMessage, Head) -> |
| 399 | Len = 16, |
| 400 | ?CHECK_LEN(rand, size(Value), Len, Len), |
| 401 | encode_ie(maps:without([rand], GSUPMessage), <<Head/binary, ?RAND, Len, Value:Len/binary>>); |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 402 | |
| 403 | encode_ie(#{cn_domain := Value} = GSUPMessage, Head) -> |
| 404 | Len = 1, |
| 405 | ?CHECK_SIZE(cn_domain, Len, Value), |
| 406 | encode_ie(maps:without([cn_domain], GSUPMessage), <<Head/binary, ?CN_DOMAIN, Len, Value:Len/unit:8>>); |
| 407 | |
Harald Welte | b76c7fa | 2019-08-22 13:48:00 +0200 | [diff] [blame^] | 408 | encode_ie(#{rat_type := Value} = GSUPMessage, Head) when is_list(Value) -> |
| 409 | Len = length(Value), |
| 410 | ?CHECK_LEN(rat_type, Len, 1, 8), |
| 411 | RatList = encode_rat_types(Value), |
| 412 | encode_ie(maps:without([rat_type], GSUPMessage), <<Head/binary, ?RAT_TYPE, Len, RatList/binary>>); |
| 413 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 414 | encode_ie(#{ss_info := Value} = GSUPMessage, Head) -> |
| 415 | Len = size(Value), |
| 416 | encode_ie(maps:without([ss_info], GSUPMessage), <<Head/binary, ?SS_INFO, Len, Value/binary>>); |
| 417 | |
| 418 | encode_ie(#{sm_rp_mr := Value} = GSUPMessage, Head) -> |
| 419 | Len = 1, |
| 420 | ?CHECK_SIZE(sm_rp_mr, Len, Value), |
| 421 | encode_ie(maps:without([sm_rp_mr], GSUPMessage), <<Head/binary, ?SM_RP_MR, Len, Value:Len/unit:8>>); |
| 422 | |
| 423 | encode_ie(#{sm_rp_da := Value} = GSUPMessage, Head) -> |
| 424 | Len = size(Value), |
| 425 | encode_ie(maps:without([sm_rp_da], GSUPMessage), <<Head/binary, ?SM_RP_DA, Len, Value/binary>>); |
| 426 | |
| 427 | encode_ie(#{sm_rp_oa := Value} = GSUPMessage, Head) -> |
| 428 | Len = size(Value), |
| 429 | encode_ie(maps:without([sm_rp_oa], GSUPMessage), <<Head/binary, ?SM_RP_OA, Len, Value/binary>>); |
| 430 | |
| 431 | encode_ie(#{sm_rp_ui := Value} = GSUPMessage, Head) -> |
| 432 | Len = size(Value), |
| 433 | encode_ie(maps:without([sm_rp_ui], GSUPMessage), <<Head/binary, ?SM_RP_UI, Len, Value/binary>>); |
| 434 | |
| 435 | encode_ie(#{sm_rp_cause := Value} = GSUPMessage, Head) -> |
| 436 | Len = 1, |
| 437 | ?CHECK_SIZE(sm_rp_cause, Len, Value), |
| 438 | encode_ie(maps:without([sm_rp_cause], GSUPMessage), <<Head/binary, ?SM_RP_CAUSE, Len, Value:Len/unit:8>>); |
| 439 | |
| 440 | encode_ie(#{sm_rp_mms := Value} = GSUPMessage, Head) -> |
| 441 | Len = 1, |
| 442 | ?CHECK_SIZE(sm_rp_mms, Len, Value), |
| 443 | encode_ie(maps:without([sm_rp_mms], GSUPMessage), <<Head/binary, ?SM_RP_MMS, Len, Value:Len/unit:8>>); |
| 444 | |
| 445 | encode_ie(#{sm_alert_reason := Value} = GSUPMessage, Head) -> |
| 446 | Len = 1, |
| 447 | ?CHECK_SIZE(sm_alert_reason, Len, Value), |
| 448 | encode_ie(maps:without([sm_alert_reason], GSUPMessage), <<Head/binary, ?SM_ALERT_REASON, Len, Value:Len/unit:8>>); |
| 449 | |
| 450 | encode_ie(#{imei := Value} = GSUPMessage, Head) -> |
| 451 | Len = size(Value), |
Andrey Velikiy | 1e80178 | 2019-06-24 14:09:57 +0300 | [diff] [blame] | 452 | ?CHECK_LEN(imei, Len, 9, 9), |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 453 | encode_ie(maps:without([imei], GSUPMessage), <<Head/binary, ?IMEI, Len, Value/binary>>); |
| 454 | |
| 455 | encode_ie(#{imei_check_result := Value} = GSUPMessage, Head) -> |
| 456 | Len = 1, |
| 457 | ?CHECK_SIZE(imei_check_result, Len, Value), |
| 458 | encode_ie(maps:without([imei_check_result], GSUPMessage), <<Head/binary, ?IMEI_CHECK_RESULT, Len, Value:Len/unit:8>>); |
| 459 | |
Andrey Velikiy | bb29966 | 2019-06-19 14:19:10 +0300 | [diff] [blame] | 460 | encode_ie(#{source_name := Value} = GSUPMessage, Head) -> |
| 461 | Len = size(Value), |
| 462 | encode_ie(maps:without([source_name], GSUPMessage), <<Head/binary, ?SOURCE_NAME, Len, Value/binary>>); |
| 463 | |
| 464 | encode_ie(#{destination_name := Value} = GSUPMessage, Head) -> |
| 465 | Len = size(Value), |
| 466 | encode_ie(maps:without([destination_name], GSUPMessage), <<Head/binary, ?DESTINATION_NAME, Len, Value/binary>>); |
| 467 | |
| 468 | encode_ie(#{an_apdu := Value} = GSUPMessage, Head) -> |
| 469 | Len = size(Value), |
| 470 | encode_ie(maps:without([an_apdu], GSUPMessage), <<Head/binary, ?AN_APDU, Len, Value/binary>>); |
| 471 | |
| 472 | encode_ie(#{rr_cause := Value} = GSUPMessage, Head) -> |
| 473 | Len = 1, |
| 474 | ?CHECK_SIZE(rr_cause, Len, Value), |
| 475 | encode_ie(maps:without([rr_cause], GSUPMessage), <<Head/binary, ?RR_CAUSE, Len, Value:Len/unit:8>>); |
| 476 | |
| 477 | encode_ie(#{bssap_cause := Value} = GSUPMessage, Head) -> |
| 478 | Len = 1, |
| 479 | ?CHECK_SIZE(bssap_cause, Len, Value), |
| 480 | encode_ie(maps:without([bssap_cause], GSUPMessage), <<Head/binary, ?BSSAP_CAUSE, Len, Value:Len/unit:8>>); |
| 481 | |
| 482 | encode_ie(#{session_management_cause := Value} = GSUPMessage, Head) -> |
| 483 | Len = 1, |
| 484 | ?CHECK_SIZE(session_management_cause, Len, Value), |
| 485 | encode_ie(maps:without([session_management_cause], GSUPMessage), <<Head/binary, ?SESSION_MANAGEMENT_CAUSE, Len, Value:Len/unit:8>>); |
| 486 | |
Andrey Velikiy | b39df23 | 2019-06-14 21:08:00 +0300 | [diff] [blame] | 487 | encode_ie(_, Head) -> Head. |
| 488 | |
| 489 | encode_bcd(BCDNumber) -> encode_bcd(BCDNumber, <<>>). |
| 490 | |
| 491 | encode_bcd(<<A, B, Tail/binary>>, BCDNumber) -> |
| 492 | encode_bcd(Tail, <<BCDNumber/binary, (encode_nibble(B)):4, (encode_nibble(A)):4>>); |
| 493 | |
| 494 | encode_bcd(<<A>>, BCDNumber) -> |
| 495 | <<BCDNumber/binary, 16#f:4, (encode_nibble(A)):4>>; |
| 496 | |
| 497 | encode_bcd(<<>>, BCDNumber) -> |
| 498 | BCDNumber. |
| 499 | |
| 500 | encode_nibble($0) -> 0; |
| 501 | encode_nibble($1) -> 1; |
| 502 | encode_nibble($2) -> 2; |
| 503 | encode_nibble($3) -> 3; |
| 504 | encode_nibble($4) -> 4; |
| 505 | encode_nibble($5) -> 5; |
| 506 | encode_nibble($6) -> 6; |
| 507 | encode_nibble($7) -> 7; |
| 508 | encode_nibble($8) -> 8; |
| 509 | encode_nibble($9) -> 9; |
| 510 | encode_nibble($*) -> 10; |
| 511 | encode_nibble($#) -> 11; |
| 512 | encode_nibble($a) -> 12; |
| 513 | encode_nibble($b) -> 13; |
| 514 | encode_nibble($c) -> 14; |
| 515 | encode_nibble($A) -> 12; |
| 516 | encode_nibble($B) -> 13; |
| 517 | encode_nibble($C) -> 14; |
| 518 | encode_nibble(X) -> error({bad_bcd_character, X}). |
| 519 | |
| 520 | check_auth_tuple(AuthTuple) -> |
| 521 | Mandatory = ?AUTH_TUPLE_MANDATORY, |
| 522 | Possible = Mandatory ++ ?AUTH_TUPLE_OPTIONAL, |
| 523 | (maps:size(maps:with(Mandatory, AuthTuple)) == length(Mandatory)) |
| 524 | orelse error({mandatory_ie_missing, auth_tuples, Mandatory -- maps:keys(AuthTuple)}), |
| 525 | (maps:size(maps:without(Possible, AuthTuple)) == 0) |
| 526 | orelse error({ie_not_expected, auth_tuples, maps:keys(AuthTuple) -- Possible}). |
| 527 | |
| 528 | check_pdp_info(PDPInfo) -> |
| 529 | Mandatory = ?PDP_INFO_MANDATORY, |
| 530 | Possible = Mandatory ++ ?PDP_INFO_OPTIONAL, |
| 531 | (maps:size(maps:with(Mandatory, PDPInfo)) == length(Mandatory)) |
| 532 | orelse error({mandatory_ie_missing, pdp_info_list, Mandatory -- maps:keys(PDPInfo)}), |
| 533 | (maps:size(maps:without(Possible, PDPInfo)) == 0) |
| 534 | orelse error({ie_not_expected, pdp_info_list, maps:keys(PDPInfo) -- Possible}). |
| 535 | |
| 536 | encode_auth_tuple(#{rand := Value} = Map, Head) -> |
| 537 | Len = 16, |
| 538 | ?CHECK_LEN(rand, size(Value), Len, Len), |
| 539 | encode_auth_tuple(maps:without([rand], Map), <<Head/binary, ?RAND, Len, Value:Len/binary>>); |
| 540 | |
| 541 | encode_auth_tuple(#{sres := Value} = Map, Head) -> |
| 542 | Len = 4, |
| 543 | ?CHECK_LEN(sres, size(Value), Len, Len), |
| 544 | encode_auth_tuple(maps:without([sres], Map), <<Head/binary, ?SRES, Len, Value:Len/binary>>); |
| 545 | |
| 546 | encode_auth_tuple(#{kc := Value} = Map, Head) -> |
| 547 | Len = 8, |
| 548 | ?CHECK_LEN(kc, size(Value), Len, Len), |
| 549 | encode_auth_tuple(maps:without([kc], Map), <<Head/binary, ?KC, Len, Value:Len/binary>>); |
| 550 | |
| 551 | encode_auth_tuple(#{ik := Value} = Map, Head) -> |
| 552 | Len = 16, |
| 553 | ?CHECK_LEN(ik, size(Value), Len, Len), |
| 554 | encode_auth_tuple(maps:without([ik], Map), <<Head/binary, ?IK, Len, Value:Len/binary>>); |
| 555 | |
| 556 | encode_auth_tuple(#{ck := Value} = Map, Head) -> |
| 557 | Len = 16, |
| 558 | ?CHECK_LEN(ck, size(Value), Len, Len), |
| 559 | encode_auth_tuple(maps:without([ck], Map), <<Head/binary, ?CK, Len, Value:Len/binary>>); |
| 560 | |
| 561 | encode_auth_tuple(#{autn := Value} = Map, Head) -> |
| 562 | Len = 16, |
| 563 | ?CHECK_LEN(autn, size(Value), Len, Len), |
| 564 | encode_auth_tuple(maps:without([autn], Map), <<Head/binary, ?AUTN, Len, Value:Len/binary>>); |
| 565 | |
| 566 | encode_auth_tuple(#{res := Value} = Map, Head) -> |
| 567 | Len = size(Value), |
| 568 | ?CHECK_LEN(res, size(Value), Len, Len), |
| 569 | encode_auth_tuple(maps:without([res], Map), <<Head/binary, ?RES, Len, Value/binary>>); |
| 570 | |
| 571 | encode_auth_tuple(#{}, Head) -> Head. |
| 572 | |
| 573 | encode_pdp_info(#{pdp_context_id := Value} = Map, Head) -> |
| 574 | Len = 1, |
| 575 | ?CHECK_SIZE(pdp_context_id, Len, Value), |
| 576 | encode_pdp_info(maps:without([pdp_context_id], Map), <<Head/binary, ?PDP_CONTEXT_ID, Len, Value:Len/unit:8>>); |
| 577 | |
| 578 | encode_pdp_info(#{pdp_type := Value} = Map, Head) -> |
| 579 | Len = 2, |
| 580 | ?CHECK_SIZE(pdp_type, Len, Value), |
| 581 | encode_pdp_info(maps:without([pdp_type], Map), <<Head/binary, ?PDP_TYPE, Len, Value:Len/unit:8>>); |
| 582 | |
| 583 | encode_pdp_info(#{access_point_name := Value} = Map, Head) -> |
| 584 | Len = size(Value), |
| 585 | ?CHECK_LEN(access_point_name, Len, 1, 100), |
| 586 | encode_pdp_info(maps:without([access_point_name], Map), <<Head/binary, ?ACCESS_POINT_NAME, Len, Value/binary>>); |
| 587 | |
| 588 | encode_pdp_info(#{quality_of_service := Value} = Map, Head) -> |
| 589 | Len = size(Value), |
| 590 | ?CHECK_LEN(quality_of_service, Len, 1, 20), |
| 591 | encode_pdp_info(maps:without([quality_of_service], Map), <<Head/binary, ?QUALITY_OF_SERVICE, Len, Value/binary>>); |
| 592 | |
| 593 | encode_pdp_info(#{pdp_charging := Value} = Map, Head) -> |
| 594 | Len = 2, |
| 595 | ?CHECK_SIZE(pdp_charging, Len, Value), |
| 596 | encode_pdp_info(maps:without([pdp_charging], Map), <<Head/binary, ?PDP_CHARGING, Len, Value:Len/unit:8>>); |
| 597 | |
| 598 | encode_pdp_info(#{}, Head) -> Head. |
Harald Welte | b76c7fa | 2019-08-22 13:48:00 +0200 | [diff] [blame^] | 599 | |
| 600 | encode_rat_type(rat_unknown) -> 0; |
| 601 | encode_rat_type(rat_geran_a) -> 1; |
| 602 | encode_rat_type(rat_utran_iu) -> 2; |
| 603 | encode_rat_type(rat_eutran_sgs) -> 3. |
| 604 | |
| 605 | encode_rat_types([], Acc) -> list_to_binary(lists:reverse(Acc)); |
| 606 | encode_rat_types([Head|Tail], Acc) -> |
| 607 | T = encode_rat_type(Head), |
| 608 | encode_rat_types(Tail, [T|Acc]). |
| 609 | encode_rat_types(List) -> encode_rat_types(List, []). |