aboutsummaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2024-04-02 12:56:26 +0200
committerPau Espin Pedrol <pespin@sysmocom.de>2024-04-08 17:36:18 +0200
commit05eaa1a531e6d45dc2cc7c52b59f667183d53fc0 (patch)
tree23421aaa44a97f38cd8727f998ddacafb9362fed /library
parent95058fd76c0bea7eb2b9d3b13b103b365bab3acb (diff)
asterisk: Implement and test SIP Digest Authorization
Diffstat (limited to 'library')
-rw-r--r--library/SIP_Templates.ttcn519
1 files changed, 513 insertions, 6 deletions
diff --git a/library/SIP_Templates.ttcn b/library/SIP_Templates.ttcn
index fc8e23f6..7cb9d68d 100644
--- a/library/SIP_Templates.ttcn
+++ b/library/SIP_Templates.ttcn
@@ -1,7 +1,11 @@
module SIP_Templates {
import from SIPmsg_Types all;
+import from TCCConversion_Functions all;
+import from TCCOpenSecurity_Functions all;
+import from Native_Functions all;
import from Osmocom_Types all;
+import from Misc_Helpers all;
/* wrapper type to encapsulate the Addr_Union + parameter list used in From, To. ... */
type record SipAddr {
@@ -11,6 +15,26 @@ type record SipAddr {
const charstring c_SIP_VERSION := "SIP/2.0";
+template (value) GenericParam ts_Param(template (value) charstring id,
+ template (omit) charstring paramValue := omit) := {
+ id := id,
+ paramValue := paramValue
+}
+template (present) GenericParam tr_Param(template (present) charstring id := ?,
+ template charstring paramValue := *) := {
+ id := id,
+ paramValue := paramValue
+}
+function f_ts_Param_omit(template (value) charstring id,
+ template (omit) charstring paramValue := omit)
+ return template (omit) GenericParam
+{
+ if (istemplatekind(paramValue, "omit")) {
+ return omit;
+ }
+ return ts_Param(id, paramValue);
+}
+
template (value) SipUrl ts_SipUrl(template (value) HostPort host_port,
template (omit) UserInfo user_info := omit) := {
scheme := "sip",
@@ -31,6 +55,49 @@ template (present) SipUrl tr_SipUrl(template (present) HostPort host_port := ?,
template (value) SipUrl ts_SipUrlHost(template (value) charstring host)
:= ts_SipUrl(ts_HostPort(host));
+template (value) Credentials ts_Credentials_DigestResponse(template (value) CommaParam_List digestResponse) := {
+ digestResponse := digestResponse
+}
+
+template (value) Credentials ts_Credentials_DigestResponseMD5(
+ template (value) charstring username,
+ template (value) charstring realm,
+ template (value) charstring nonce,
+ template (value) charstring uri,
+ template (value) charstring response,
+ template (value) charstring opaque,
+ template (value) charstring algorithm := "MD5",
+ template (value) charstring qop := "auth",
+ template (omit) charstring cnonce := omit,
+ template (omit) charstring nc := omit
+ ) := {
+ digestResponse := {
+ // Already added by digestResponse automatically:
+ //ts_Param("Digest", omit),
+ ts_Param("username", f_sip_str_quote(username)),
+ ts_Param("realm", f_sip_str_quote(realm)),
+ ts_Param("nonce", f_sip_str_quote(nonce)),
+ ts_Param("uri", f_sip_str_quote(uri)),
+ ts_Param("response", f_sip_str_quote(response)),
+ ts_Param("opaque", f_sip_str_quote(opaque)),
+ ts_Param("algorithm", algorithm),
+ ts_Param("qop", qop),
+ // FIXME: If "omit" is passed, these below end up in;
+ // "Dynamic test case error: Performing a valueof or send operation on a non-specific template of type @SIPmsg_Types.GenericParam"
+ f_ts_Param_omit("cnonce", f_sip_str_quote(cnonce)),
+ f_ts_Param_omit("nc", nc)
+ }
+}
+
+template (value) Credentials ts_Credentials_OtherAuth(template (value) OtherAuth otherResponse) := {
+ otherResponse := otherResponse
+}
+
+template (value) Authorization ts_Authorization(template (value) Credentials body) := {
+ fieldName := AUTHORIZATION_E,
+ body := body
+}
+
// [20.10]
template (present) NameAddr tr_NameAddr(template (present) SipUrl addrSpec := ?,
template charstring displayName := *) := {
@@ -262,7 +329,53 @@ template (present) Via tr_Via_from(template (present) HostPort host_port := ?,
viaParams := viaParams
}
}
- }
+}
+
+template (present) OtherAuth
+tr_OtherAuth(template (present) charstring authScheme := ?,
+ template (present) CommaParam_List authParams := ?) := {
+ authScheme := authScheme,
+ authParams := authParams
+}
+
+template (value) OtherAuth
+ts_OtherAuth(template (value) charstring authScheme,
+ template (value) CommaParam_List authParams) := {
+ authScheme := authScheme,
+ authParams := authParams
+}
+
+template (present) Challenge
+tr_Challenge_digestCln(template (present) CommaParam_List digestCln := ?) := {
+ digestCln := digestCln
+}
+
+template (value) Challenge
+ts_Challenge_digestCln(template (value) CommaParam_List digestCln) := {
+ digestCln := digestCln
+}
+
+template (present) Challenge
+tr_Challenge_otherChallenge(template (present) OtherAuth otherChallenge := ?) := {
+ otherChallenge := otherChallenge
+}
+
+template (value) Challenge
+ts_Challenge_otherChallenge(template (value) OtherAuth otherChallenge) := {
+ otherChallenge := otherChallenge
+}
+
+template (present) WwwAuthenticate
+tr_WwwAuthenticate(template (present) Challenge_list challenge := ?) := {
+ fieldName := WWW_AUTHENTICATE_E,
+ challenge := challenge
+}
+
+template (value) WwwAuthenticate
+ts_WwwAuthenticate(template (value) Challenge_list challenge) := {
+ fieldName := WWW_AUTHENTICATE_E,
+ challenge := challenge
+}
template (value) MessageHeader ts_SIP_msgHeader_empty := c_SIP_msgHeader_empty;
template (value) MessageHeader
@@ -274,6 +387,7 @@ ts_SIP_msgh_std(template (value) CallidString call_id,
template (value) integer seq_nr,
template (value) Via via,
template (omit) ContentType content_type := omit,
+ template (omit)Authorization authorization := omit,
template (value) Method_List allow_methods := c_SIP_defaultMethods,
template (omit) Expires expires := omit
) modifies ts_SIP_msgHeader_empty := {
@@ -281,6 +395,7 @@ ts_SIP_msgh_std(template (value) CallidString call_id,
fieldName := ALLOW_E,
methods := allow_methods
},
+ authorization := authorization,
callId := {
fieldName := CALL_ID_E,
callid := call_id
@@ -337,7 +452,8 @@ tr_SIP_msgh_std(template CallidString call_id,
template ContentType content_type := *,
template integer seq_nr := ?,
template Method_List allow_methods := *,
- template Expires expires := *
+ template Expires expires := *,
+ template WwwAuthenticate wwwAuthenticate := *
) modifies t_SIP_msgHeader_any := {
allow := tr_AllowMethods(allow_methods),
callId := {
@@ -363,7 +479,8 @@ tr_SIP_msgh_std(template CallidString call_id,
toParams := to_addr.params
},
userAgent := *,
- via := via
+ via := via,
+ wwwAuthenticate := wwwAuthenticate
}
@@ -376,11 +493,13 @@ ts_SIP_REGISTER(template (value) SipUrl sip_url_host_port,
integer seq_nr,
template (omit) Contact contact,
template (omit) Expires expires,
+ template (omit) Authorization authorization := omit,
template (omit) charstring body := omit) := {
requestLine := ts_SIP_ReqLine(REGISTER_E, sip_url_host_port),
msgHeader := ts_SIP_msgh_std(call_id, from_addr, to_addr, contact,
"REGISTER", seq_nr, via,
f_ContentTypeOrOmit(ts_CT_SDP, body),
+ authorization := authorization,
expires := expires),
messageBody := body,
payload := omit
@@ -511,15 +630,16 @@ template (present) PDU_SIP_Response
tr_SIP_Response(template CallidString call_id,
template SipAddr from_addr,
template SipAddr to_addr,
+ template (present) Via via := tr_Via_from(?),
template Contact contact,
template charstring method,
template integer status_code,
template integer seq_nr := ?,
template charstring reason := ?,
- template charstring body := ?) := {
+ template charstring body := *) := {
statusLine := tr_SIP_StatusLine(status_code, reason),
msgHeader := tr_SIP_msgh_std(call_id, from_addr, to_addr, contact,
- tr_Via_from(tr_HostPort(from_addr.addr.nameAddr.addrSpec.hostPort)),
+ via,
method, *, seq_nr),
messageBody := body,
payload := omit
@@ -533,6 +653,7 @@ tr_SIP_Response_REGISTER_Unauthorized(
template SipAddr to_addr,
template (present) Via via := tr_Via_from(?),
template Contact contact := *,
+ template (present) WwwAuthenticate wwwAuthenticate := ?,
template integer seq_nr := ?,
template charstring method := "REGISTER",
template integer status_code := 401,
@@ -541,11 +662,333 @@ tr_SIP_Response_REGISTER_Unauthorized(
statusLine := tr_SIP_StatusLine(status_code, reason),
msgHeader := tr_SIP_msgh_std(call_id, from_addr, to_addr, contact,
via,
- method, *, seq_nr),
+ method, *, seq_nr,
+ wwwAuthenticate := wwwAuthenticate),
messageBody := body,
payload := omit
}
+function f_sip_param_find(GenericParam_List li,
+ template (present) charstring id := ?)
+return template (omit) GenericParam {
+ var integer i;
+
+ for (i := 0; i < lengthof(li); i := i + 1) {
+ if (not ispresent(li[i])) {
+ continue;
+ }
+ if (match(li[i].id, id)) {
+ return li[i];
+ }
+ }
+ return omit;
+}
+
+function f_sip_param_find_or_fail(GenericParam_List li,
+ template (present) charstring id := ?)
+return GenericParam {
+ var template (omit) GenericParam parameter;
+ parameter := f_sip_param_find(li, id);
+ if (istemplatekind(parameter, "omit")) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Param ", id, " not found in ", li));
+ }
+ return valueof(parameter);
+}
+
+function f_sip_param_get_value(GenericParam_List li,
+ template (present) charstring id := ?)
+return template (omit) charstring {
+ var template (omit) GenericParam parameter;
+ parameter := f_sip_param_find(li, id);
+ if (istemplatekind(parameter, "omit")) {
+ return omit;
+ }
+ return parameter.paramValue;
+}
+
+function f_sip_param_get_value_or_fail(GenericParam_List li,
+ template (present) charstring id := ?)
+return template (omit) charstring {
+ var GenericParam parameter;
+ parameter := f_sip_param_find_or_fail(li, id);
+ return parameter.paramValue;
+}
+
+function f_sip_param_get_value_present_or_fail(GenericParam_List li,
+ template (present) charstring id := ?)
+return charstring {
+ var GenericParam parameter;
+ parameter := f_sip_param_find_or_fail(li, id);
+ if (not ispresent(parameter.paramValue)) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Param ", id, " value not present in ", li));
+ }
+ return parameter.paramValue;
+}
+
+function f_sip_param_match_value(GenericParam_List li,
+ template (present) charstring id := ?,
+ template charstring exp_paramValue := *)
+return boolean {
+ var template (omit) charstring val;
+ val := f_sip_param_get_value_or_fail(li, id);
+ if (istemplatekind(val, "omit")) {
+ return istemplatekind(val, "omit") or istemplatekind(val, "*");
+ }
+ return match(valueof(val), exp_paramValue);
+}
+
+function f_sip_param_match_value_or_fail(GenericParam_List li,
+ template (present) charstring id := ?,
+ template charstring exp_paramValue := *)
+{
+ var template (omit) charstring val := f_sip_param_get_value_or_fail(li, id);
+ if (istemplatekind(val, "omit")) {
+ if (istemplatekind(val, "omit") or istemplatekind(val, "*")) {
+ return;
+ } else {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Param ", id, " match failed: val ", val,
+ " vs exp ", exp_paramValue));
+ }
+ }
+ if (not match(valueof(val), exp_paramValue)) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Param ", id, " match failed: val ", val,
+ " vs exp ", exp_paramValue));
+ }
+}
+
+function f_sip_param_remove(template (omit) GenericParam_List li_tpl, charstring id)
+return GenericParam_List {
+ var integer i;
+ var GenericParam_List li;
+ var GenericParam_List new_li := {};
+
+ if (istemplatekind(li_tpl, "omit")) {
+ return {};
+ }
+
+ li := valueof(li_tpl);
+ for (i := 0; i < lengthof(li); i := i + 1) {
+ if (not ispresent(li[i]) or
+ not match(li[i].id, id)) {
+ new_li := new_li & {li[i]};
+ }
+ }
+ return new_li;
+}
+
+function f_sip_param_set(template (omit) GenericParam_List li_tpl, charstring id, charstring val)
+return GenericParam_List {
+ var integer i;
+ var GenericParam_List li;
+ var GenericParam_List new_li := {};
+ var boolean found := false;
+
+ if (istemplatekind(li_tpl, "omit")) {
+ return { valueof(ts_Param(id, val)) };
+ }
+
+ li := valueof(li_tpl);
+ for (i := 0; i < lengthof(li); i := i + 1) {
+ if (not ispresent(li[i]) or
+ not match(li[i].id, id)) {
+ new_li := new_li & {li[i]};
+ continue;
+ }
+ new_li := new_li & { valueof(ts_Param(li[i].id, val)) };
+ found := true;
+ }
+
+ if (not found) {
+ new_li := new_li & { valueof(ts_Param(id, val)) };
+ }
+ return new_li;
+}
+
+/* Make sure string is quoted. */
+function f_sip_str_quote(template (value) charstring val) return charstring {
+ var charstring str := valueof(val);
+ if (lengthof(str) == 0) {
+ return "";
+ }
+
+ if (str[0] != "\"") {
+ return "\"" & str & "\"";
+ }
+ return str;
+}
+
+/* Make sure string is unquoted.
+ * Similar to unq() in RFC 2617 */
+function f_sip_str_unquote(template (value) charstring val) return charstring {
+ var charstring str := valueof(val);
+ var integer len := lengthof(str);
+
+ if (len <= 1) {
+ return str;
+ }
+
+ if (str[0] == "\"" and str[len - 1] == "\"") {
+ return substr(str, 1, len - 2);
+ }
+ return str;
+}
+
+/* RFC 2617 3.2.2.2 A1 */
+function f_sip_digest_A1(charstring user, charstring realm, charstring password) return charstring {
+
+ /* RFC 2617 3.2.2.2 A1 */
+ var charstring A1 := f_sip_str_unquote(user) & ":" &
+ f_sip_str_unquote(realm) & ":" &
+ password;
+ var charstring digestA1 := f_str_tolower(f_calculateMD5(A1));
+ log("A1: md5('", A1, "') = ", digestA1);
+ return digestA1;
+}
+
+/* RFC 2617 3.2.2.2 A2 */
+function f_sip_digest_A2(charstring method, charstring uri) return charstring {
+
+ var charstring A2 := method & ":" & uri
+ var charstring digestA2 := f_str_tolower(f_calculateMD5(A2));
+ log("A2: md5('", A2, "') = ", digestA2);
+ return digestA2;
+}
+
+/* RFC 2617 3.2.2.1 Request-Digest */
+function f_sip_digest_RequestDigest(charstring digestA1, charstring nonce,
+ charstring nc, charstring cnonce,
+ charstring qop, charstring digestA2) return charstring {
+ var charstring digest_data := f_sip_str_unquote(nonce) & ":" &
+ nc & ":" &
+ cnonce & ":" &
+ f_sip_str_unquote(qop) & ":" &
+ digestA2;
+ var charstring req_digest := f_sip_digest_KD(digestA1, digest_data);
+ log("Request-Digest: md5('", digestA1, ":", digest_data ,"') = ", req_digest);
+ return req_digest;
+}
+
+/* RFC 2617 3.2.1 The WWW-Authenticate Response Header
+ * KD(secret, data) = H(concat(secret, ":", data))
+ */
+function f_sip_digest_KD(charstring secret, charstring data) return charstring {
+ return f_str_tolower(f_calculateMD5(secret & ":" & data));
+}
+
+/* Digest Auth: RFC 2617 */
+function f_sip_digest_gen_Authorization(WwwAuthenticate www_authenticate,
+ charstring user, charstring password,
+ charstring method, charstring uri,
+ charstring cnonce := "0a4f113b", integer nc_int := 1) return Authorization {
+ var CommaParam_List digestCln;
+ var template (value) Authorization authorization;
+ var template (value) Credentials cred;
+ var template (omit) GenericParam rx_param;
+
+ digestCln := www_authenticate.challenge[0].digestCln;
+
+ var charstring algorithm;
+ rx_param := f_sip_param_find(digestCln, "algorithm");
+ if (istemplatekind(rx_param, "omit")) {
+ /* Assume MD5 if not set */
+ algorithm := "MD5"
+ } else {
+ algorithm := valueof(rx_param.paramValue);
+ if (f_strstr(algorithm, "MD5") == -1) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Unexpected algorithm: ", algorithm));
+ }
+ }
+
+ var charstring realm := f_sip_param_get_value_present_or_fail(digestCln, "realm");
+ var charstring nonce := f_sip_param_get_value_present_or_fail(digestCln, "nonce");
+ var charstring opaque := f_sip_param_get_value_present_or_fail(digestCln, "opaque");
+ var charstring qop := f_sip_param_get_value_present_or_fail(digestCln, "qop");
+
+ if (f_strstr(qop, "auth") == -1) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected qop: ", qop));
+ }
+ var charstring selected_qop := "auth";
+
+ /* RFC 2617 3.2.2.2 A1 */
+ var charstring digestA1 := f_sip_digest_A1(user, realm, password);
+ /* RFC 2617 3.2.2.3 A2 */
+ var charstring digestA2 := f_sip_digest_A2(method, uri);
+
+ /* RFC 2617 3.2.2.1 Request-Digest */
+ var charstring nc := f_str_tolower(hex2str(int2hex(nc_int, 8)));
+ var charstring req_digest := f_sip_digest_RequestDigest(digestA1, nonce,
+ nc, cnonce,
+ selected_qop, digestA2);
+
+ cred := ts_Credentials_DigestResponseMD5(user, realm, nonce,
+ uri, req_digest,
+ opaque, algorithm, selected_qop, cnonce, nc);
+
+ authorization := ts_Authorization(cred);
+ return valueof(authorization);
+}
+
+/* RFC 2617 3.5 Example */
+function f_sip_digest_selftest() {
+/*
+The following example assumes that an access-protected document is
+being requested from the server via a GET request. The URI of the
+document is "http://www.nowhere.org/dir/index.html". Both client and
+server know that the username for this document is "Mufasa", and the
+password is "Circle Of Life" (with one space between each of the
+three words).
+
+HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Digest
+ realm="testrealm@host.com",
+ qop="auth,auth-int",
+ nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ opaque="5ccc069c403ebaf9f0171e9517f40e41"
+
+Authorization: Digest username="Mufasa",
+ realm="testrealm@host.com",
+ nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ uri="/dir/index.html",
+ qop=auth,
+ nc=00000001,
+ cnonce="0a4f113b",
+ response="6629fae49393a05397450978507c4ef1",
+ opaque="5ccc069c403ebaf9f0171e9517f40e41"
+*/
+ var template (value) CommaParam_List digestCln := {
+ ts_Param("realm", f_sip_str_quote("testrealm@host.com")),
+ ts_Param("qop", f_sip_str_quote("auth,auth-int")),
+ ts_Param("nonce", f_sip_str_quote("dcd98b7102dd2f0e8b11d0f600bfb0c093")),
+ ts_Param("opaque", f_sip_str_quote("5ccc069c403ebaf9f0171e9517f40e41"))
+ };
+ var template (value) WwwAuthenticate www_authenticate :=
+ ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
+
+ var Authorization authorization :=
+ f_sip_digest_gen_Authorization(valueof(www_authenticate),
+ "Mufasa",
+ "Circle Of Life",
+ "GET",
+ "/dir/index.html",
+ cnonce := "0a4f113b",
+ nc_int := 1);
+
+ var CommaParam_List digestResp := authorization.body.digestResponse;
+ f_sip_param_match_value_or_fail(digestResp, "realm", f_sip_str_quote("testrealm@host.com"));
+ f_sip_param_match_value_or_fail(digestResp, "nonce", f_sip_str_quote("dcd98b7102dd2f0e8b11d0f600bfb0c093"));
+ f_sip_param_match_value_or_fail(digestResp, "uri", f_sip_str_quote("/dir/index.html"));
+ f_sip_param_match_value_or_fail(digestResp, "qop", "auth");
+ f_sip_param_match_value_or_fail(digestResp, "nc", "00000001");
+ f_sip_param_match_value_or_fail(digestResp, "cnonce", f_sip_str_quote("0a4f113b"));
+ f_sip_param_match_value_or_fail(digestResp, "response", f_sip_str_quote("6629fae49393a05397450978507c4ef1"));
+ f_sip_param_match_value_or_fail(digestResp, "opaque", f_sip_str_quote("5ccc069c403ebaf9f0171e9517f40e41"));
+}
+
/* RFC 3261 8.1.1.5:
* "The sequence number value MUST be expressible as a 32-bit unsigned integer
* and MUST be less than 2**31."
@@ -555,4 +998,68 @@ function f_sip_rand_seq_nr() return integer {
return f_rnd_int(2147483648)
}
+/* Tags shall have at least 32 bit of randomness */
+function f_sip_rand_tag() return charstring {
+ var integer rnd_int := f_rnd_int(4294967296);
+ return hex2str(int2hex(rnd_int, 8));
+}
+
+/* Generate a "branch" tag value.
+ * RFC 3261 p.105 section 8:
+ * "A common way to create this value is to compute a
+ * cryptographic hash of the To tag, From tag, Call-ID header
+ * field, the Request-URI of the request received (before
+ * translation), the topmost Via header, and the sequence number
+ * from the CSeq header field, in addition to any Proxy-Require
+ * and Proxy-Authorization header fields that may be present. The
+ * algorithm used to compute the hash is implementation-dependent,
+ * but MD5 (RFC 1321 [35]),expressed in hexadecimal, is a reasonable
+ * choice."
+ * See also Section 8.1.1.7:
+ * "The branch ID inserted by an element compliant with this
+ * specification MUST always begin with the characters "z9hG4bK"."
+ */
+const charstring sip_magic_cookie := "z9hG4bK";
+function f_sip_gen_branch(charstring tag_to,
+ charstring tag_from,
+ charstring tag_call_id,
+ integer cseq) return charstring {
+ var charstring str := tag_to & tag_from & tag_call_id & int2str(cseq);
+ var charstring hash := f_calculateMD5(str);
+ var charstring branch := sip_magic_cookie & hash;
+ return branch;
+}
+
+function f_sip_HostPort_to_str(HostPort host_port) return charstring {
+ var charstring str := "";
+ if (ispresent(host_port.host)) {
+ str := host_port.host;
+ }
+ if (ispresent(host_port.portField)) {
+ str := str & ":" & int2str(host_port.portField);
+ }
+ return str;
+}
+
+function f_sip_SipUrl_to_str(SipUrl uri) return charstring {
+ var charstring str := uri.scheme & f_sip_HostPort_to_str(uri.hostPort);
+ return str;
+}
+
+function f_sip_NameAddr_to_str(NameAddr naddr) return charstring {
+ if (ispresent(naddr.displayName)) {
+ return naddr.displayName & " <" & f_sip_SipUrl_to_str(naddr.addrSpec) & ">";
+ } else {
+ return f_sip_SipUrl_to_str(naddr.addrSpec);
+ }
+}
+
+function f_sip_SipAddr_to_str(SipAddr sip_addr) return charstring {
+ if (ischosen(sip_addr.addr.nameAddr)) {
+ return f_sip_NameAddr_to_str(sip_addr.addr.nameAddr);
+ } else {
+ return f_sip_SipUrl_to_str(sip_addr.addr.addrSpecUnion);
+ }
+}
+
}