%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2001-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
%%----------------------------------------------------------------------
%% Purpose: RFC 4566
%%----------------------------------------------------------------------

-module(megaco_sdp).
-moduledoc """
SDP utility module.

This module contains various things related to SDP.

## Version note

This module has existed in the megaco app for long time,
but as of 27.0 its also documented.
""".
-moduledoc(#{since => "OTP 27.0"}).


%%----------------------------------------------------------------------
%% Include files
%%----------------------------------------------------------------------

-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/src/app/megaco_internal.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").
-include_lib("megaco/include/megaco_sdp.hrl").


%%----------------------------------------------------------------------
%% External exports
%%----------------------------------------------------------------------

%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).
-export([
	 decode/1, encode/1, 
	 get_sdp_record_from_PropertyGroup/2
        ]).

-export_type([
              property_parm/0,
              property_group/0,
              property_groups/0,

              sdp/0,
              sdp_v/0,
              sdp_c/0, sdp_o/0, sdp_s/0, sdp_i/0, sdp_u/0,
              sdp_e/0, sdp_p/0, sdp_b/0, sdp_z/0, sdp_k/0,
              sdp_a/0, sdp_a_rtpmap/0, sdp_a_ptime/0,
              sdp_a_quality/0, sdp_a_fmtp/0,
              sdp_t/0, sdp_r/0, sdp_m/0,
              sdp_property_parm/0,
              sdp_property_group/0,
              sdp_property_groups/0
             ]).


%%----------------------------------------------------------------------
%% Internal exports
%%----------------------------------------------------------------------


%%----------------------------------------------------------------------
%% Types
%%----------------------------------------------------------------------

-type property_parm()   :: #'PropertyParm'{}.
-type property_group()  :: [property_parm()].
-type property_groups() :: [property_group()].

-type sdp() :: sdp_o() | sdp_s() | sdp_i() | sdp_u() | sdp_e() |
               sdp_p() | sdp_c() | sdp_b() | sdp_k() | sdp_a() |
               sdp_a_rtpmap() | sdp_a_ptime() |
               sdp_z() | sdp_t() | sdp_r() | sdp_m().

-doc "Protocol version.".
-type sdp_v()         :: #megaco_sdp_v{}. % Protocol version
-doc "Owner/creator and session identifier.".
-type sdp_o()         :: #megaco_sdp_o{}. % Owner/creator and session identifier
-doc "Session name.".
-type sdp_s()         :: #megaco_sdp_s{}. % Session name
-doc "Session information.".
-type sdp_i()         :: #megaco_sdp_i{}. % Session information
-doc "URI of description.".
-type sdp_u()         :: #megaco_sdp_u{}. % URI of description
-doc "Email address.".
-type sdp_e()         :: #megaco_sdp_e{}. % Email address
-doc "Phone number.".
-type sdp_p()         :: #megaco_sdp_p{}. % Phone number
-doc "Connection information.".
-type sdp_c()         :: #megaco_sdp_c{}. % Connection information
-doc "Bandwidth information.".
-type sdp_b()         :: #megaco_sdp_b{}. % Bandwidth information
-doc "Encryption key.".
-type sdp_k()         :: #megaco_sdp_k{}. % Encryption key
-doc "Session attribute.".
-type sdp_a()         :: #megaco_sdp_a{}. % Session attribute
-type sdp_a_rtpmap()  :: #megaco_sdp_a_rtpmap{}.
-type sdp_a_ptime()   :: #megaco_sdp_a_ptime{}.
-type sdp_a_quality() :: #megaco_sdp_a_quality{}.
-type sdp_a_fmtp()    :: #megaco_sdp_a_fmtp{}.
-doc "Time zone adjustment.".
-type sdp_z()         :: #megaco_sdp_z{}. % Time zone adjustment
-type sdp_t()         :: #megaco_sdp_t{}. % Time the session is active
-doc "Repeat times.".
-type sdp_r()         :: #megaco_sdp_r{}. % Repeat times
-doc "Media name and transport address.".
-type sdp_m()         :: #megaco_sdp_m{}. % Media name and transport address
-type sdp_property_parm()   :: sdp() | property_parm().
-type sdp_property_group()  :: [sdp_property_parm()].
-type sdp_property_groups() :: [sdp_property_group()].


%%----------------------------------------------------------------------
%% Macros
%%----------------------------------------------------------------------


%%----------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------


%%======================================================================
%% External functions
%%======================================================================

%% ---------------------------------------------------------------------
%% decode(PP) -> {ok, SDP} | {error, Reason}
%% 
%% This function performs the following conversion:
%% property_parm()   -> sdp()
%% property_group()  -> sdp_property_group()
%% property_groups() -> sdp_property_groups()
%% 
%% ---------------------------------------------------------------------

-doc false.
decode(SDP) ->
    case (catch do_decode(SDP)) of
	{ok, _} = OK ->
	    OK;
	{error, _} = ERR ->
	    ERR;
	{'EXIT', Reason} ->
	    {error, {exit, Reason}};
	CRAP ->
	    {error, {crap, CRAP}}
    end.

do_decode(PP) when is_record(PP, 'PropertyParm') ->
    decode_PropertyParm(PP);
do_decode([PP|_] = PG) when is_record(PP, 'PropertyParm') ->
    decode_PropertyGroup(PG);
do_decode([H|_] = PGs) when is_list(H) ->
    decode_PropertyGroups(PGs);
do_decode(asn1_NOVALUE = V) ->
    {ok, V};
do_decode(Bad) ->
    {error, {bad_sdp, Bad}}.


%% ---------------------------------------------------------------------
%% encode(SDPs) -> {ok, PP} | {error, Reason}
%% 
%% This function performs the following conversion:
%% sdp()                 -> property_parm()
%% sdp_property_group()  -> property_group()
%% sdp_property_groups() -> property_groups()
%% 
%% ---------------------------------------------------------------------

-doc false.
encode(SDP) ->
    case (catch do_encode(SDP)) of
	{ok, _} = OK ->
	    OK;
	{error, _} = ERR ->
	    ERR;
	{'EXIT', Reason} ->
	    {error, {exit, Reason}};
	CRAP ->
	    {error, {crap, CRAP}}
    end.

do_encode(SDP) when is_tuple(SDP) ->
    {ok, encode_PropertyParm(SDP)};
do_encode([SDP|_] = PG) when is_tuple(SDP) ->
    encode_PropertyGroup(PG);
do_encode([H|_] = PGs) when is_list(H) ->
    encode_PropertyGroups(PGs);
do_encode(asn1_NOVALUE = V) ->
    {ok, V}.


%%-----------------------------------------------------------------
%% Generate sdp records from PropertyParm records
%%-----------------------------------------------------------------

decode_PropertyGroups(PGs) ->
    decode_PropertyGroups(PGs, []).

decode_PropertyGroups([], DecodedPGs) ->
    {ok, lists:reverse(DecodedPGs)};

decode_PropertyGroups([PG | PGs], DecodedPGs) ->
    {ok, DecodedPG} = decode_PropertyGroup(PG),
    decode_PropertyGroups(PGs, [DecodedPG | DecodedPGs]).


decode_PropertyGroup(PG) ->
    {ok, decode_PropertyGroup(PG, [])}.

decode_PropertyGroup([], Acc) ->
    lists:reverse(Acc);

decode_PropertyGroup([PP | PG], Acc) ->
    case (catch decode_PropertyParm(PP)) of
	{ok, PP2} ->
	    decode_PropertyGroup(PG, [PP2 | Acc]);
	{error, Reason} ->
	    decode_PropertyGroup(PG, [{PP, Reason} | Acc]);
	Error ->
	    decode_PropertyGroup(PG, [{PP, Error} | Acc])
    end.


%% ===== Protocol Version =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "v", 
				    value     = [V],
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_protocol_version(V);


%% ===== Origin =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "o", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_origin(V);


%% ===== Session Name =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "s", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_session_name(V);


%% ===== Session and Media Information =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "i", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_session_media_id(V);


%% ===== URI =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "u", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_uri(V);


%% ===== Email Address and Phone Number =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "e", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_email(V);

decode_PropertyParm(#'PropertyParm'{name      = "p", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_phone(V);


%% ===== Connection Data =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "c", 
				    value     = [V],
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_connection_data(V);


%% ===== Bandwidth =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "b", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_bandwidth(V);


%% ===== Times, Repeat Times and Time Zones =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "t", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE }) ->
    decode_pp_times(V);
decode_PropertyParm(#'PropertyParm'{name      = "r", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_rtimes(V);
decode_PropertyParm(#'PropertyParm'{name      = "z", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_tzones(V);


%% ===== Encryption Keys =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "k", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_encryption_keys(V);


%% ===== Attributes =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "a", 
				    value     = [V], 
				    extraInfo =  asn1_NOVALUE}) ->
    decode_pp_attribute(V);


%% ===== Media Announcements =====
%% 
decode_PropertyParm(#'PropertyParm'{name      = "m", 
				    value     = [V], 
				    extraInfo = asn1_NOVALUE}) ->
    decode_pp_media_announcement(V);


decode_PropertyParm(_PP) ->
    ?d("decode_PropertyParm -> entry with"
       "~n   _PP: ~p", [_PP]),
    {error, undefined_PropertyParm}.


%%-----------------------------------------------------------------
%% Generate PropertyParm records from sdp records
%%-----------------------------------------------------------------

encode_PropertyGroups(PGs) ->
    encode_PropertyGroups(PGs, []).


encode_PropertyGroups([], Acc) ->
    {ok, lists:reverse(Acc)};
encode_PropertyGroups([PG | PGs], Acc) ->
    {ok, EncodedPG} = encode_PropertyGroup(PG), 
    encode_PropertyGroups(PGs, [EncodedPG | Acc]).


encode_PropertyGroup(PG) ->
    encode_PropertyGroup(PG, []).

encode_PropertyGroup([], Acc) ->
    {ok, lists:reverse(Acc)};
encode_PropertyGroup([PP | PG], Acc) ->
    EncodedPP = encode_PropertyParm(PP),
    encode_PropertyGroup(PG, [EncodedPP | Acc]).
    

%% ===== Protocol Version =====
%% 
encode_PropertyParm(#megaco_sdp_v{version = Version}) ->
    encode_pp_protocol_version(Version);


%% ===== Origin =====
%% 
encode_PropertyParm(#megaco_sdp_o{user_name    = User,
				  session_id   = SessionId,
				  version      = Version,
				  network_type = Network,
				  address_type = AddrType,
				  address      = Addr}) ->
    encode_pp_origin(User, SessionId, Version, Network, AddrType, Addr);    


%% ===== Session Name =====
%% 
encode_PropertyParm(#megaco_sdp_s{name = Name}) ->
    encode_pp_session_name(Name);    


%% ===== Session and Media Information =====
%% 
encode_PropertyParm(#megaco_sdp_i{session_descriptor = SD}) ->
    encode_pp_session_media_id(SD);


%% ===== URI =====
%% 
encode_PropertyParm(#megaco_sdp_u{uri = URI}) ->
    encode_pp_uri(URI);


%% ===== Email Address and Phone Number =====
%% 
encode_PropertyParm(#megaco_sdp_e{email = Email}) ->
    encode_pp_email(Email);

encode_PropertyParm(#megaco_sdp_p{phone_number = Num}) ->
    encode_pp_phone(Num);


%% ===== Connection Data =====
%% 
encode_PropertyParm(#megaco_sdp_c{network_type    = NetType,
				  address_type    = AddressType,
				  connection_addr = ConnectionAddr}) ->
    encode_pp_connection_data(NetType, AddressType, ConnectionAddr);


%% ===== Bandwidth =====
%% 
encode_PropertyParm(#megaco_sdp_b{bwtype    = BwType,
				  bandwidth = Bandwidth}) ->
    encode_pp_bandwidth(BwType, Bandwidth);


%% ===== Times, Repeat Times and Time Zones =====
%% 
encode_PropertyParm(#megaco_sdp_t{start = Start, stop = Stop}) ->
    encode_pp_times(Start, Stop);

encode_PropertyParm(#megaco_sdp_r{repeat_interval = Repeat,
				  active_duration = Duration, 
				  list_of_offsets = ListOfOffsets}) ->
    encode_pp_rtimes(Repeat, Duration, ListOfOffsets);

encode_PropertyParm(#megaco_sdp_z{list_of_adjustments = LOA}) ->
    encode_pp_tzones(LOA);


%% ===== Encryption Keys =====
%% 
encode_PropertyParm(#megaco_sdp_k{method         = Method, 
				  encryption_key = EncryptionKey}) ->
    encode_pp_encryption_keys(Method, EncryptionKey);


%% ===== Attributes =====
%% 
encode_PropertyParm(#megaco_sdp_a_cat{category = Category}) ->
    encode_pp_attribute_cat(Category);

encode_PropertyParm(#megaco_sdp_a_keywds{keywords = Keywords}) ->
    encode_pp_attribute_keywds(Keywords);

encode_PropertyParm(#megaco_sdp_a_tool{name_and_version = NameAndVersion}) ->
    encode_pp_attribute_tool(NameAndVersion);

encode_PropertyParm(#megaco_sdp_a_ptime{packet_time = PacketTime}) ->
    encode_pp_attribute_ptime(PacketTime);

encode_PropertyParm(
  #megaco_sdp_a_maxptime{maximum_packet_time = MaxPacketTime}) ->
    encode_pp_attribute_maxptime(MaxPacketTime);

encode_PropertyParm(#megaco_sdp_a_rtpmap{payload_type   = Payload, 
					 encoding_name  = EncName,
					 clock_rate     = ClockRate, 
					 encoding_parms = EncPar}) ->
    encode_pp_attribute_rtpmap(Payload, EncName, ClockRate, EncPar);

encode_PropertyParm(#megaco_sdp_a_orient{orientation = Orientation}) ->
    encode_pp_attribute_orient(Orientation);

encode_PropertyParm(#megaco_sdp_a_type{conf_type = CType}) ->
    encode_pp_attribute_type(CType);

encode_PropertyParm(#megaco_sdp_a_charset{char_set = CharSet}) ->
    encode_pp_attribute_charset(CharSet);

encode_PropertyParm(#megaco_sdp_a_sdplang{tag = Tag}) ->
    encode_pp_attribute_sdplang(Tag);

encode_PropertyParm(#megaco_sdp_a_lang{tag = Tag}) ->
    encode_pp_attribute_lang(Tag);

encode_PropertyParm(#megaco_sdp_a_framerate{frame_rate = FrameRate}) ->
    encode_pp_attribute_framerate(FrameRate);

encode_PropertyParm(#megaco_sdp_a_quality{quality = Quality}) ->
    encode_pp_attribute_quality(Quality);

encode_PropertyParm(#megaco_sdp_a_fmtp{format = Fmt, param = Params}) ->
    encode_pp_attribute_fmtp(Fmt, Params);

encode_PropertyParm(#megaco_sdp_a{attribute = Attr, value = Value}) ->
    encode_pp_attribute(Attr, Value);


%% ===== Media Announcements =====
%% 
encode_PropertyParm(#megaco_sdp_m{media     = Media,
				  port      = Port,
				  num_ports = NOP,
				  transport = Transport,
				  fmt_list  = FMT}) ->
    encode_pp_media_announcement(Media, Port, NOP, Transport, FMT);


%% This is a "manually" encoded PropertyParm, leave it as is.
%% 
encode_PropertyParm(PP) when is_record(PP, 'PropertyParm') ->
    PP;


%% Bad data
encode_PropertyParm(SDP) ->
    error({unknown_sdp, SDP}).


%%-----------------------------------------------------------------
%% Func: get_sdp_record_from_PropertGroup/2
%% Description: Get all sdp records of a certain type from a 
%%              property group
%%-----------------------------------------------------------------

-doc false.
get_sdp_record_from_PropertyGroup(Type, PG) 
  when is_atom(Type) and is_list(PG) ->
    F = fun(R) -> not is_pg_record(Type, R) end,
    lists:filter(F, PG).

is_pg_record(v, R) when is_record(R, megaco_sdp_v)        -> true;
is_pg_record(c, R) when is_record(R, megaco_sdp_c)        -> true;
is_pg_record(m, R) when is_record(R, megaco_sdp_m)        -> true;
is_pg_record(o, R) when is_record(R, megaco_sdp_o)        -> true;
is_pg_record(a, R) when is_record(R, megaco_sdp_a)        -> true;
is_pg_record(a, R) when is_record(R, megaco_sdp_a_ptime)  -> true;
is_pg_record(a, R) when is_record(R, megaco_sdp_a_rtpmap) -> true;
is_pg_record(b, R) when is_record(R, megaco_sdp_b)        -> true;
is_pg_record(t, R) when is_record(R, megaco_sdp_t)        -> true;
is_pg_record(r, R) when is_record(R, megaco_sdp_r)        -> true;
is_pg_record(z, R) when is_record(R, megaco_sdp_z)        -> true;
is_pg_record(k, R) when is_record(R, megaco_sdp_k)        -> true;
is_pg_record(s, R) when is_record(R, megaco_sdp_s)        -> true;
is_pg_record(i, R) when is_record(R, megaco_sdp_i)        -> true;
is_pg_record(u, R) when is_record(R, megaco_sdp_u)        -> true;
is_pg_record(e, R) when is_record(R, megaco_sdp_e)        -> true;
is_pg_record(p, R) when is_record(R, megaco_sdp_p)        -> true;
is_pg_record(_, _)                                        -> false.

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

%% ===== Protocol Version =====
%% 
decode_pp_protocol_version(Value) when is_list(Value) ->
    ?d("decode_pp_protocol_version -> entry with"
       "~n   Value: ~p", [Value]),
    Version = s2i(Value, invalid_protocol_version),
    ?d("decode_pp_protocol_version -> entry with"
       "~n   Version: ~w", [Version]),
    decode_pp_protocol_version(Version);
decode_pp_protocol_version(Version) when is_integer(Version) ->
    ?d("decode_pp_protocol_version -> entry with"
       "~n   Version: ~w", [Version]),
    {ok, #megaco_sdp_v{version = Version}}.

encode_pp_protocol_version(Version) when is_integer(Version) ->
    ?d("encode_pp_protocol_version -> entry with"
       "~n   Version: ~w", [Version]),
    #'PropertyParm'{name  =  "v", 
		    value = [integer_to_list(Version)]};
encode_pp_protocol_version(Version) ->
    error({invalid_protocol_version, Version}).


%% ===== Origin =====
%% 
decode_pp_origin(Value) ->
    ?d("decode_pp_origin -> entry with"
       "~n   Value: ~p", [Value]),
    case string:tokens(Value, " \t") of
	[User, SessId, SessVersion, NetType, AddrType, UnicastAddress] ->
	    ?d("decode_pp_origin -> entry with"
	       "~n   User:           ~p"
	       "~n   SessionId:      ~p"
	       "~n   Version:        ~p"
	       "~n   NetType:        ~p"
	       "~n   AddrType:       ~p"
	       "~n   UnicastAddress: ~p", 
	       [User, SessId, SessVersion, NetType, AddrType, UnicastAddress]),
	    F = fun(X, R) -> 
			case (catch list_to_integer(X)) of
			    I when is_integer(I) ->
				I;
			    _ ->
				error({invalid_origin, {R, X}})
			end
		end,
	    SID = F(SessId,      sess_id), 
	    V   = F(SessVersion, sess_version), 
	    SDP = #megaco_sdp_o{user_name    = User,
				session_id   = SID,
				version      = V,
				network_type = decode_network_type(NetType),
				address_type = decode_address_type(AddrType),
				address      = UnicastAddress},
	    {ok, SDP};
	Err ->
	    ?d("decode_pp_origin -> "
	       "~n   Err: ~p", [Err]),
	    invalid_pp(origin, Value, Err)
    end.
    
encode_pp_origin(User0, SessionId0, SessVersion0, 
		 NetType0, AddrType0, UnicastAddr0) ->
    ?d("do_encode_pp_origin -> entry with"
       "~n   User0:        ~p"
       "~n   SessionId0:   ~p"
       "~n   SessVersion0: ~p"
       "~n   NetType0:     ~p"
       "~n   AddrType0:    ~p"
       "~n   UnicastAddr0: ~p", 
       [User0, SessionId0, SessVersion0, NetType0, AddrType0, UnicastAddr0]),
    User        = encode_origin_user(User0),
    SessionId   = encode_origin_session_id(SessionId0),
    SessVersion = encode_origin_sess_version(SessVersion0),
    NetType     = encode_origin_network_type(NetType0), 
    AddrType    = encode_origin_address_type(AddrType0), 
    UnicastAddr = encode_origin_unicast_address(UnicastAddr0), 
    #'PropertyParm'{name  = "o", 
		    value = [
			     User        ++ " " ++
			     SessionId   ++ " " ++
			     SessVersion ++ " " ++
			     NetType     ++ " " ++
			     AddrType    ++ " " ++
			     UnicastAddr
			    ]}.

encode_origin_user(User) when is_list(User) ->
    User;
encode_origin_user(BadUser) ->
    error({invalid_origin_user, BadUser}).

encode_origin_session_id(SID) when is_integer(SID) ->
    integer_to_list(SID);
encode_origin_session_id(BadSID) ->
    error({invalid_origin_session_id, BadSID}).

encode_origin_sess_version(SessVersion) when is_integer(SessVersion) ->
    integer_to_list(SessVersion);
encode_origin_sess_version(BadSessVersion) ->
    error({invalid_origin_sess_version, BadSessVersion}).

encode_origin_network_type(NT) ->
    case (catch encode_network_type(NT)) of
	{error, _} ->
	    error({invalid_origin_network_type, NT});
	Val ->
	    Val
    end.

encode_origin_address_type(AT) ->
    case (catch encode_address_type(AT)) of
	{error, _} ->
	    error({invalid_origin_address_type, AT});
	Val ->
	    Val
    end.

encode_origin_unicast_address(UnicastAddr) when is_list(UnicastAddr) ->
    UnicastAddr;
encode_origin_unicast_address(BadUnicastAddr) ->
    error({invalid_origin_unicast_address, BadUnicastAddr}).


%% ===== Session Name =====
%% 
decode_pp_session_name(Value) ->
    ?d("decode_pp_session_name -> entry with"
       "~n   Value: ~p", [Value]),
    {ok, #megaco_sdp_s{name = Value}}.

encode_pp_session_name(Name) when is_list(Name) ->
    ?d("encode_pp_session_name -> entry with"
       "~n   Name: '~s'", [Name]),
    #'PropertyParm'{name  = "s", 
		    value = [Name]};
encode_pp_session_name(BadName) ->
    error({invalid_session_name, BadName}).

    
%% ===== Session and Media Information =====
%% 
decode_pp_session_media_id(Value) ->
    ?d("decode_pp_session_media_id -> entry with"
       "~n   Value: ~p", [Value]),
    {ok, #megaco_sdp_i{session_descriptor = Value}}.

encode_pp_session_media_id(SD) when is_list(SD) ->
    ?d("encode_pp_session_media_id -> entry with"
       "~n   SD: '~s'", [SD]),
    #'PropertyParm'{name  = "i", 
		    value = [SD]};
encode_pp_session_media_id(BadSD) ->
    error({invalid_session_media_id, BadSD}).


%% ===== URI =====
%% 
decode_pp_uri(Value) ->
    ?d("decode_pp_uri -> entry with"
       "~n   Value: ~p", [Value]),
    {ok, #megaco_sdp_u{uri = Value}}.

encode_pp_uri(URI) when is_list(URI) ->
    ?d("encode_pp_uri -> entry with"
       "~n   URI: ~p", [URI]),
    #'PropertyParm'{name  = "u", 
		    value = [URI]};
encode_pp_uri(BadUri) ->
    error({invalid_uri, BadUri}).


%% ===== Email Address and Phone Number =====
%% 
decode_pp_email(Value) ->
    ?d("decode_pp_email -> entry with"
       "~n   Value: ~p", [Value]),
    {ok, #megaco_sdp_e{email = Value}}.

encode_pp_email(Email) when is_list(Email) ->
    ?d("encode_pp_email -> entry with"
       "~n   Email: ~p", [Email]),
    #'PropertyParm'{name  = "e", 
		    value = [Email]};
encode_pp_email(BadEmail) ->
    error({invalid_email, BadEmail}).
   
decode_pp_phone(Value) ->
    ?d("decode_pp_phone -> entry with"
       "~n   Value: ~p", [Value]),
    {ok, #megaco_sdp_p{phone_number = Value}}.

encode_pp_phone(Phone) when is_list(Phone) ->
    ?d("encode_pp_phone -> entry with"
       "~n   Phone: ~p", [Phone]),
    #'PropertyParm'{name  = "p", 
		    value = [Phone]};
encode_pp_phone(BadPhone) ->
    error({invalid_phone, BadPhone}).


%% ===== Connection Data =====
%% 
decode_pp_connection_data(Value) ->
    ?d("decode_pp_connection_data -> entry with"
       "~n   Value: ~p", [Value]),
    case string:tokens(Value, " \t") of
	[NetType, AddrType, ConnectionAddr] ->
	    ?d("decode_pp_connection_data -> "
	       "~n   NetType:        ~p"
	       "~n   AddrType:       ~p"
	       "~n   ConnectionAddr: ~p", [NetType, AddrType, ConnectionAddr]),
	    NT = decode_network_type(NetType), 
	    AT = decode_address_type(AddrType), 
	    case AT of
		ip4 ->
		    ?d("decode_pp_connection_data -> ip4", []),
		    ConnAddr = 
			case string:tokens(ConnectionAddr, "\/") of
			    [Base, TtlStr, NumOfAddrs] ->
				?d("decode_pp_connection_data -> "
				   "~n   Base:      ~p"
				   "~n   TtlStr:    ~p"
				   "~n   NumOfAddrs:~p", 
				   [Base, TtlStr, NumOfAddrs]),
				TTL = s2i(TtlStr, 
					  invalid_connection_data_ttl),
				?d("decode_pp_connection_data -> TTL: ~p", 
				   [TTL]),
				NOA = 
				    s2i(NumOfAddrs, 
					invalid_connection_data_conn_addr_num_of),
				?d("decode_pp_connection_data -> NOA: ~p", 
				   [NOA]),
				#megaco_sdp_c_conn_addr{base   = Base,
							ttl    = TTL,
							num_of = NOA};
			    [Base, TtlStr] ->
				?d("decode_pp_connection_data -> "
				   "~n   Base:   ~p"
				   "~n   TtlStr: ~p", 
				   [Base, TtlStr]),
				TTL = 
				    s2i(TtlStr, 
					invalid_connection_data_conn_addr_ttl),
				?d("decode_pp_connection_data -> TTL: ~p", 
				   [TTL]),
				#megaco_sdp_c_conn_addr{base = Base,
							ttl  = TTL};
			    [Base] ->
				Base
			end,
		    ?d("decode_pp_connection_data -> "
		       "~n   ConnAddr: ~p", [ConnAddr]),
		    SDP = #megaco_sdp_c{network_type    = NT,
					address_type    = AT,
					connection_addr = ConnAddr},
		    {ok, SDP};
		ip6 ->
		    ?d("decode_pp_connection_data -> ip6", []),
		    SDP = #megaco_sdp_c{network_type    = NT,
					address_type    = AT,
					connection_addr = ConnectionAddr},
		    {ok, SDP};
		_ ->
		    SDP = #megaco_sdp_c{network_type    = NT,
					address_type    = AT,
					connection_addr = ConnectionAddr},
		    {ok, SDP}
	    end;
	Err ->
	    invalid_pp(connection_data, Value, Err)
    end.

encode_pp_connection_data(NetType0, AddrType0, ConnAddr0) ->
    ?d("encode_pp_connection_data -> entry with"
       "~n   NetType0:  ~p"
       "~n   AddrType0: ~p"
       "~n   ConnAddr0: ~p", [NetType0, AddrType0, ConnAddr0]),
    NetType  = encode_conn_data_network_type(NetType0),
    AddrType = encode_conn_data_address_type(AddrType0),
    ConnAddr = encode_conn_data_conn_addr(AddrType0, ConnAddr0),
    Val      = NetType ++ " " ++ AddrType ++ " " ++ ConnAddr,
    #'PropertyParm'{name  = "c", 
		    value = [Val]}.

encode_conn_data_network_type(NT) ->
    case (catch encode_network_type(NT)) of
	{error, _} ->
	    error({invalid_connection_data_network_type, NT});
	Val ->
	    Val
    end.

encode_conn_data_address_type(AT) ->
    case (catch encode_address_type(AT)) of
	{error, _} ->
	    error({invalid_connection_data_address_type, AT});
	Val ->
	    Val
    end.

encode_conn_data_conn_addr(_, CA) when is_list(CA) ->
    CA;
encode_conn_data_conn_addr(ip4, CA) 
  when is_record(CA, megaco_sdp_c_conn_addr) ->
    encode_conn_data_conn_addr(CA);
encode_conn_data_conn_addr(AT, CA) 
  when is_list(AT) and is_record(CA, megaco_sdp_c_conn_addr) ->
    case tolower(AT) of
	"ip4" ->
	    encode_conn_data_conn_addr(CA);
	_ ->
	    error({invalid_connection_data_conn_addr, {AT, CA}})
    end;
encode_conn_data_conn_addr(_, BadCA) ->
    error({invalid_connection_data_conn_addr, BadCA}).

encode_conn_data_conn_addr(#megaco_sdp_c_conn_addr{base   = Base0,
						   ttl    = TTL0,
						   num_of = undefined}) ->
    Base = encode_conn_data_conn_addr_base(Base0),
    TTL  = encode_conn_data_conn_addr_ttl(TTL0),
    Base ++ "/" ++ TTL;
encode_conn_data_conn_addr(#megaco_sdp_c_conn_addr{base   = Base0,
						   ttl    = TTL0,
						   num_of = NumOf0}) ->
    Base  = encode_conn_data_conn_addr_base(Base0),
    TTL   = encode_conn_data_conn_addr_ttl(TTL0),
    NumOf = encode_conn_data_conn_addr_num_of(NumOf0),
    Base ++ "/" ++ TTL ++ "/" ++ NumOf.
    
encode_conn_data_conn_addr_base(Base) when is_list(Base) -> 
    Base;
encode_conn_data_conn_addr_base(BadBase) ->
    error({invalid_connection_data_conn_addr_base, BadBase}).

encode_conn_data_conn_addr_ttl(TTL) when is_integer(TTL) -> 
    integer_to_list(TTL);
encode_conn_data_conn_addr_ttl(BadTTL) ->
    error({invalid_connection_data_conn_addr_ttl, BadTTL}).

encode_conn_data_conn_addr_num_of(NumOf) when is_integer(NumOf) -> 
    integer_to_list(NumOf);
encode_conn_data_conn_addr_num_of(BadNumOf) ->
    error({invalid_connection_data_conn_addr_num_of, BadNumOf}).


%% ===== Bandwidth =====
%% 
decode_pp_bandwidth(Value) ->
    ?d("decode_pp_bandwidth -> entry with"
       "~n   Value: ~p", [Value]),
    case string:tokens(Value, ":") of
	[BwTypeStr, BandwidthStr] ->
	    ?d("decode_pp_bandwidth -> "
	       "~n   BwTypeStr:    ~p"
	       "~n   BandwidthStr: ~p", [BwTypeStr, BandwidthStr]),
	    BwType    = decode_bandwidth_bwt(BwTypeStr),
	    ?d("decode_pp_bandwidth -> "
	       "~n   BwType: ~w", [BwType]),
	    Bandwidth = decode_bandwidth_bw(BandwidthStr),
	    ?d("decode_pp_bandwidth -> "
	       "~n   Bandwidth: ~w", [Bandwidth]),
	    SDP = #megaco_sdp_b{bwtype    = BwType,
				bandwidth = Bandwidth},
	    {ok, SDP};
	Err ->
	    invalid_pp(bandwidth_info, Value, Err)
    end.
    
encode_pp_bandwidth(BwType0, Bandwidth0) ->
    ?d("encode_pp_bandwidth -> entry with"
       "~n   BwType0:    ~p"
       "~n   Bandwidth0: ~p", [BwType0, Bandwidth0]),
    BwType    = encode_bandwidth_bwt(BwType0),
    Bandwidth = encode_bandwidth_bw(Bandwidth0), 
    Val       = BwType ++ ":" ++ Bandwidth, 
    #'PropertyParm'{name  = "b", 
		    value = [Val]}.

decode_bandwidth_bwt("CT") ->
    ct;
decode_bandwidth_bwt("AS") ->
    as;
decode_bandwidth_bwt(BwType) when is_list(BwType) ->
    BwType.

encode_bandwidth_bwt(ct) ->
    "CT";
encode_bandwidth_bwt(as) ->
    "AS";
encode_bandwidth_bwt(BwType) when is_list(BwType) ->
    BwType;
encode_bandwidth_bwt(BadBwType) ->
    error({invalid_bandwidth_bwtype, BadBwType}).

decode_bandwidth_bw(Bandwidth) ->
    s2i(Bandwidth, invalid_bandwidth_bandwidth).

encode_bandwidth_bw(Bandwidth) when is_integer(Bandwidth) ->
    integer_to_list(Bandwidth);
encode_bandwidth_bw(BadBandwidth) ->
    error({invalid_bandwidth_bandwidth, BadBandwidth}).


%% ===== Times =====
%% 
decode_pp_times(Value) ->
    ?d("decode_pp_times -> entry with"
       "~n   Value: ~p", [Value]),
    case string:tokens(Value, " \t") of
	[StartStr, StopStr] ->    
	    ?d("decode_pp_times -> "
	       "~n   StartStr: ~p"
	       "~n   StopStr:  ~p", [StartStr, StopStr]),
	    Start = decode_times_start(StartStr),
	    ?d("decode_pp_times -> entry with"
	       "~n   Stop: ~w", [Start]),
	    Stop  = decode_times_stop(StopStr),
	    ?d("decode_pp_times -> entry with"
	       "~n   Stop:  ~w", [Stop]),
	    SDP = #megaco_sdp_t{start = Start, 
				stop  = Stop},
	    {ok, SDP};
	Err ->
	    invalid_pp(times, Value, Err)
    end.

encode_pp_times(Start0, Stop0) ->
    ?d("encode_pp_times -> entry with"
       "~n   Start0:  ~p"
       "~n   Stop0:   ~p", [Start0, Stop0]),
    Start = encode_times_start(Start0), 
    Stop  = encode_times_stop(Stop0), 
    Val = Start ++ " " ++ Stop, 
    #'PropertyParm'{name  = "t", 
		    value = [Val]}.

decode_times_start(Time) ->
    s2i(Time, invalid_times_start).

encode_times_start(Time) when is_integer(Time) ->
    integer_to_list(Time);
encode_times_start(BadTime) ->
    error({invalid_times_start, BadTime}).
    
decode_times_stop(Time) ->
    s2i(Time, invalid_times_stop).

encode_times_stop(Time) when is_integer(Time) ->
    integer_to_list(Time);
encode_times_stop(BadTime) ->
    error({invalid_times_stop, BadTime}).
    

%% ===== Repeat Times =====
%% 
decode_pp_rtimes(Value) ->
    ?d("decode_pp_rtimes -> entry with"
       "~n   Value: ~p", [Value]),
    case string:tokens(Value, " \t") of
	[Repeat, Duration | ListOfOffsets] ->    
	    ?d("decode_pp_rtimes -> "
	       "~n   Repeat:        ~p"
	       "~n   Duration:      ~p"
	       "~n   ListOfOffsets: ~p", [Repeat, Duration, ListOfOffsets]),
	    SDP = #megaco_sdp_r{repeat_interval = Repeat,
				active_duration = Duration, 
				list_of_offsets = ListOfOffsets},
	    {ok, SDP};
	Err ->
	    invalid_pp(repeat_times, Value, Err)
    end.

encode_pp_rtimes(Repeat0, Duration0, ListOfOffsets0) ->
    ?d("encode_pp_rtimes -> entry with"
       "~n   Repeat0:        ~p"
       "~n   Duration0:      ~p"
       "~n   ListOfOffsets0: ~p", [Repeat0, Duration0, ListOfOffsets0]),
    Repeat        = encode_rtimes_repeat(Repeat0), 
    Duration      = encode_rtimes_duration(Duration0), 
    ListOfOffsets = encode_rtimes_list_of_offsets(ListOfOffsets0), 
    Val = Repeat ++ " " ++ Duration ++ ListOfOffsets,
    #'PropertyParm'{name  = "r", 
		    value = [Val]}.

encode_rtimes_repeat(Repeat) when is_list(Repeat) ->
    Repeat;
encode_rtimes_repeat(BadRepeat) ->
    error({invalid_rtimes_repeat, BadRepeat}).

encode_rtimes_duration(Duration) when is_list(Duration) ->
    Duration;
encode_rtimes_duration(BadDuration) ->
    error({invalid_rtimes_duration, BadDuration}).

encode_rtimes_list_of_offsets(LOO) when is_list(LOO) ->
    F = fun(Off, Acc) when is_list(Off) -> 
		Acc ++ " " ++ Off;
	   (BadOff, Acc) -> 
		error({invalid_rtimes_list_of_offsets, {BadOff, Acc}})
	end,
    lists:foldl(F, [], LOO);
encode_rtimes_list_of_offsets(BadLoo) ->
    error({invalid_rtimes_list_of_offsets, BadLoo}).
    
	    
%% ===== Time Zones =====
%% 
decode_pp_tzones(Value) when is_list(Value) and (length(Value) > 0) ->
    ?d("decode_pp_ztimes -> entry with"
       "~n   Value: ~p", [Value]),
    LOA = decode_tzones_list_of_adjustments(string:tokens(Value, " \t"), []),
    {ok, #megaco_sdp_z{list_of_adjustments = LOA}};
decode_pp_tzones(BadValue) ->
    error({invalid_tzones_list_of_adjustments, BadValue}).

encode_pp_tzones(LOA) ->
    ?d("encode_pp_ztimes -> entry with"
       "~n   LOA: ~p", [LOA]),
    Val = encode_tzones_list_of_adjustments(LOA),
    #'PropertyParm'{name  = "z", 
		    value = [Val]}.

decode_tzones_list_of_adjustments([], Acc) ->
    lists:reverse(Acc);
decode_tzones_list_of_adjustments([Adj], Acc) ->
    error({invalid_tzones_list_of_adjustments, Adj, lists:reverse(Acc)});
decode_tzones_list_of_adjustments([Time, Offset | LOA], Acc) ->
    Adj = #megaco_sdp_z_adjustement{time = Time, offset = Offset},
    decode_tzones_list_of_adjustments(LOA, [Adj | Acc]).

encode_tzones_list_of_adjustments([H|_] = LOA) 
  when is_record(H, megaco_sdp_z_adjustement) ->
    F = fun(#megaco_sdp_z_adjustement{time = T, offset = O}, Acc) -> 
		Acc ++ " " ++ T ++ " " ++ O;
	   (BadAdjustment, Acc) ->
		error({invalid_tzones_list_of_adjustments, 
		       {BadAdjustment, Acc}})
	end, 
    lists:foldl(F, [], LOA);
encode_tzones_list_of_adjustments(LOA) ->
    error({invalid_tzones_list_of_adjustments, LOA}).


%% ===== Encryption Keys =====
%% 
decode_pp_encryption_keys(Value) ->
    ?d("decode_pp_encryption_keys -> entry with"
       "~n   Value: ~p", [Value]),
    {M, E} = 
	case string:tokens(Value, ":") of
	    [Method, EncryptionKey] -> 
		?d("decode_pp_encryption_keys -> "
		   "~n   Method:        ~p"
		   "~n   EncryptionKey: ~p", [Method, EncryptionKey]),
		{Method, EncryptionKey};
	    [Method] -> 
		?d("decode_pp_encryption_keys -> "
		   "~n   Method: ~p", [Method]),
		{Method, undefined};
	    Err ->
		invalid_pp(encryption_key, Value, Err)
	end,
    M2 = 
	case tolower(M) of
	    "clear" ->
		clear;
	    "base64" ->
		base64;
	    "uri" ->
		uri;
	    "prompt" ->
		prompt;
	    _ ->
		M
	end,
    ?d("decode_pp_encryption_keys -> "
       "~n   M2: ~p", [M2]),
    SDP = #megaco_sdp_k{method         = M2, 
			encryption_key = E},
    {ok, SDP}.
    
encode_pp_encryption_keys(prompt = _Method, undefined) ->
    ?d("encode_pp_encryption_keys(prompt) -> entry", []),
    #'PropertyParm'{name  = "k", 
		    value = ["prompt"]};
encode_pp_encryption_keys(clear = _Method, EncryptionKey) 
  when is_list(EncryptionKey) ->
    ?d("encode_pp_encryption_keys(clear) -> entry with"
       "~n   EncryptionKey: ~p", [EncryptionKey]),
    #'PropertyParm'{name  = "k", 
		    value = ["clear:" ++ EncryptionKey]};
encode_pp_encryption_keys(base64 = _Method, EncryptionKey) 
  when is_list(EncryptionKey) ->
    ?d("encode_pp_encryption_keys(base64) -> entry with"
       "~n   EncryptionKey: ~p", [EncryptionKey]),
    #'PropertyParm'{name  = "k", 
		    value = ["base64:" ++ EncryptionKey]};
encode_pp_encryption_keys(uri = _Method, EncryptionKey) 
  when is_list(EncryptionKey) ->
    ?d("encode_pp_encryption_keys(uri) -> entry with"
       "~n   EncryptionKey: ~p", [EncryptionKey]),
    #'PropertyParm'{name  = "k", 
		    value = ["uri:" ++ EncryptionKey]};
encode_pp_encryption_keys(Method, EncryptionKey) 
  when is_list(Method) and is_list(EncryptionKey) ->
    ?d("encode_pp_encryption_keys -> entry with"
       "~n   Method:        ~p"
       "~n   EncryptionKey: ~p", [Method, EncryptionKey]),
    #'PropertyParm'{name  = "k", 
		    value = [Method ++ ":" ++ EncryptionKey]};
encode_pp_encryption_keys(BadMethod, BadEK) ->
    error({invalid_encryption_keys, {BadMethod, BadEK}}).

    
%% ===== Attributes =====
%% 
decode_pp_attribute(Value) ->
    ?d("decode_pp_attribute -> entry with"
       "~n   Value: ~p", [Value]),
    First = string:chr(Value, $:),
    if 
	(First > 0) and (First < length(Value)) ->
	    ?d("decode_pp_attribute -> value attribute", []),
	    Attr      = string:substr(Value, 1, First -1),
	    AttrValue = string:substr(Value, First + 1),
	    ?d("decode_pp_attribute -> "
	       "~n   Attr:      ~p"
	       "~n   AttrValue: ~p", [Attr, AttrValue]),
	    decode_pp_attribute_value(Attr, AttrValue);
	
	First > 0 ->
	    ?d("decode_pp_attribute -> value attribute (empty)", []),
	    Attr = string:substr(Value, 1, First -1),
	    ?d("decode_pp_attribute -> "
	       "~n   Attr: ~p", [Attr]),
	    decode_pp_attribute_value(Attr, []);
	    
	true ->
	    ?d("decode_pp_attribute -> binary attribute", []),
	    {ok, #megaco_sdp_a{attribute = Value}}

    end.

decode_pp_attribute_value("cat", AttrValue) ->
    ?d("decode_pp_attribute -> cat", []),
    SDP = #megaco_sdp_a_cat{category = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("keywds", AttrValue) ->
    ?d("decode_pp_attribute -> keywds", []),
    SDP = #megaco_sdp_a_keywds{keywords = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("tool", AttrValue) ->
    ?d("decode_pp_attribute -> tool", []),
    SDP = #megaco_sdp_a_tool{name_and_version = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("ptime", AttrValue) ->
    ?d("decode_pp_attribute -> ptime", []),
    PacketTimeStr = string:strip(AttrValue, both, $ ), 
    PacketTime = 
	s2i(PacketTimeStr, invalid_ptime_packet_time), 
    ?d("decode_pp_attribute -> PacketTime: ~w", [PacketTime]),
    SDP = #megaco_sdp_a_ptime{packet_time = PacketTime}, 
    {ok, SDP};

decode_pp_attribute_value("maxptime", AttrValue) ->
    ?d("decode_pp_attribute -> maxptime", []),
    MaxPacketTimeStr = string:strip(AttrValue, both, $ ), 
    MaxPacketTime = 
	s2i(MaxPacketTimeStr, invalid_maxptime_maximum_packet_time), 
    ?d("decode_pp_attribute -> MaxPacketTime: ~w", [MaxPacketTime]),
    SDP = #megaco_sdp_a_maxptime{maximum_packet_time = MaxPacketTime}, 
    {ok, SDP};

decode_pp_attribute_value("rtpmap", AttrValue) ->
    ?d("decode_pp_attribute -> rtpmap", []),
    case string:tokens(AttrValue, "\/ \t") of
	[PayloadStr, EncName, ClockRateStr | EncPar] ->
	    ?d("decode_pp_attribute -> "
	       "~n   PayloadStr:   ~p"
	       "~n   EncName:      ~p"
	       "~n   ClockRateStr: ~p"
	       "~n   EncPar:       ~p", 
	       [PayloadStr, EncName, ClockRateStr, EncPar]),
		    Payload   = 
		s2i(PayloadStr, invalid_rtpmap_payload),
	    ?d("decode_pp_attribute -> Payload: ~w", 
	       [Payload]),
	    ClockRate = 
		s2i(ClockRateStr, invalid_rtpmap_payload), 
	    ?d("decode_pp_attribute -> ClockRate: ~w", 
	       [ClockRate]),
	    SDP = 
		#megaco_sdp_a_rtpmap{payload_type   = Payload, 
				     encoding_name  = EncName,
				     clock_rate     = ClockRate, 
				     encoding_parms = EncPar},
	    {ok, SDP};
	_ ->
	    error({invalid_rtpmap, AttrValue})
    end;

decode_pp_attribute_value("orient", AttrValue) ->
    ?d("decode_pp_attribute -> orient", []),
    Orientation = decode_attribute_orientation(AttrValue),
    SDP = #megaco_sdp_a_orient{orientation = Orientation}, 
    {ok, SDP};

decode_pp_attribute_value("type", AttrValue) ->
    ?d("decode_pp_attribute -> type", []),
    SDP = #megaco_sdp_a_type{conf_type = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("charset", AttrValue) ->
    ?d("decode_pp_attribute -> charset", []),
    SDP = #megaco_sdp_a_charset{char_set = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("sdplang", AttrValue) ->
    ?d("decode_pp_attribute -> sdplang", []),
    SDP = #megaco_sdp_a_sdplang{tag = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("lang", AttrValue) ->
    ?d("decode_pp_attribute -> lang", []),
    SDP = #megaco_sdp_a_lang{tag = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("framerate", AttrValue) ->
    ?d("decode_pp_attribute -> framerate", []),
    SDP = #megaco_sdp_a_framerate{frame_rate = AttrValue}, 
    {ok, SDP};

decode_pp_attribute_value("quality", AttrValue) ->
    ?d("decode_pp_attribute -> quality", []),
    QualityStr = AttrValue, 
    Quality = s2i(QualityStr, invalid_quality_quality), 
    ?d("decode_pp_attribute -> Quality: ~w", [Quality]),
    SDP = #megaco_sdp_a_quality{quality = Quality}, 
    {ok, SDP};

decode_pp_attribute_value("fmtp", AttrValue) ->
    ?d("decode_pp_attribute -> fmtp", []),
    FMTP = AttrValue, 
    First = string:chr(FMTP, $ ),
    if 
	(First > 0) and (First < length(FMTP)) ->
	    ?d("decode_pp_attribute_value -> valid fmtp with params", []),
	    Format = string:substr(FMTP, 1, First - 1),
	    Params = string:substr(FMTP, First + 1),
	    ?d("decode_pp_attribute_value -> "
	       "~n   Format: ~p"
	       "~n   Params: ~p", [Format, Params]),
	    SDP = #megaco_sdp_a_fmtp{format = Format,
				     param  = Params}, 
	    {ok, SDP};
	
	First > 0 ->
	    ?d("decode_pp_attribute -> valid fmtp", []),
	    Format = string:substr(FMTP, 1, First - 1),
	    ?d("decode_pp_attribute -> "
	       "~n   Format: ~p", [Format]),
	    {ok, #megaco_sdp_a_fmtp{format = Format, param = []}};
	    
	true ->
	    ?d("decode_pp_attribute_value -> no params", []),
	    {ok, #megaco_sdp_a_fmtp{format = FMTP, param = []}}

    end;

decode_pp_attribute_value(Attr, AttrValue) ->
    ?d("decode_pp_attribute -> unknown value attribute", []),
    {ok, #megaco_sdp_a{attribute = Attr, value = AttrValue}}.

decode_attribute_orientation("portrait") -> 
    portrait;
decode_attribute_orientation("landscape") ->
    landscape;
decode_attribute_orientation("seascape") ->
    seascape;
decode_attribute_orientation(BadOrientation) ->
    error({invalid_orient_orientation, BadOrientation}).
    
encode_attribute_orientation(portrait) -> 
    "portrait";
encode_attribute_orientation(landscape) ->
    "landscape";
encode_attribute_orientation(seascape) ->
    "seascape";
encode_attribute_orientation(BadOrientation) ->
    error({invalid_orient_orientation, BadOrientation}).
    

encode_pp_attribute_cat(Cat) when is_list(Cat) ->
    ?d("encode_pp_attribute_cat -> entry with"
       "~n   Cat: ~p", [Cat]),
    #'PropertyParm'{name  = "a", 
		    value = ["cat:" ++ Cat]};
encode_pp_attribute_cat(BadCat) ->
    error({invalid_cat_category, BadCat}).


encode_pp_attribute_keywds(Keywords) when is_list(Keywords) ->
    ?d("encode_pp_attribute_keywds -> entry with"
       "~n   Keywords: ~p", [Keywords]),
    #'PropertyParm'{name  = "a", 
		    value = ["keywds:" ++ Keywords]};
encode_pp_attribute_keywds(BadKeywords) ->
    error({invalid_keywds_keywords, BadKeywords}).


encode_pp_attribute_tool(NameAndVersion) when is_list(NameAndVersion) ->
    ?d("encode_pp_attribute_tool -> entry with"
       "~n   NameAndVersion: ~p", [NameAndVersion]),
    #'PropertyParm'{name  = "a", 
		    value = ["tool:" ++ NameAndVersion]};
encode_pp_attribute_tool(BadNameAndVersion) ->
    error({invalid_tool_name_and_version, BadNameAndVersion}).


encode_pp_attribute_ptime(PacketTime) when is_integer(PacketTime) ->
    ?d("encode_pp_attribute_ptime -> entry with"
       "~n   PacketTime: ~w", [PacketTime]),
    #'PropertyParm'{name  = "a", 
		    value = ["ptime:" ++ integer_to_list(PacketTime)]};
encode_pp_attribute_ptime(BadPT) ->
    error({invalid_ptime_packet_time, BadPT}).


encode_pp_attribute_maxptime(MaxPacketTime) when is_integer(MaxPacketTime) ->
    ?d("encode_pp_attribute_maxptime -> entry with"
       "~n   MaxPacketTime: ~w", [MaxPacketTime]),
    #'PropertyParm'{name  = "a", 
		    value = ["maxptime:" ++ integer_to_list(MaxPacketTime)]};
encode_pp_attribute_maxptime(BadMPT) ->
    error({invalid_maxptime_maximum_packet_time, BadMPT}).


encode_pp_attribute_rtpmap(Payload0, EncName0, ClockRate0, EncPar0) ->
    ?d("encode_pp_attribute_rtpmap -> entry with"
       "~n   Payload0:   ~p"
       "~n   EncName0:   ~p"
       "~n   ClockRate0: ~p"
       "~n   EncPar0:    ~p", [Payload0, EncName0, ClockRate0, EncPar0]),
    Payload   = encode_rtpmap_payload(Payload0), 
    EncName   = encode_rtpmap_encoding_name(EncName0), 
    ClockRate = encode_rtpmap_clockrate(ClockRate0), 
    EncPar    = encode_rtpmap_encoding_parms(EncPar0), 
    Val = "rtpmap:" ++ Payload ++ " " ++ 
	EncName ++ "/" ++ ClockRate ++ EncPar, 
    #'PropertyParm'{name  = "a", 
		    value = [Val]}.

encode_rtpmap_payload(Payload) when is_integer(Payload) ->
    integer_to_list(Payload);
encode_rtpmap_payload(BadPayload) ->
    error({invalid_rtpmap_payload, BadPayload}).

encode_rtpmap_encoding_name(EncName) when is_list(EncName) ->
    EncName;
encode_rtpmap_encoding_name(BadEncName) ->
    error({invalid_rtpmap_encoding_name, BadEncName}).

encode_rtpmap_clockrate(ClockRate) when is_integer(ClockRate) ->
    integer_to_list(ClockRate);
encode_rtpmap_clockrate(BadClockRate) ->
    error({invalid_rtpmap_clockrate, BadClockRate}).

encode_rtpmap_encoding_parms(EncPar) when is_list(EncPar) ->
    F = fun(EP, Acc) when is_list(EP) -> 
		Acc ++ "/" ++ EP;
	   (BadEP, Acc) ->
		error({invalid_rtpmap_encoding_parms, {BadEP, Acc}})
	end,
    lists:foldl(F, [], EncPar);
encode_rtpmap_encoding_parms(BadEncPar) ->
    error({invalid_rtpmap_encoding_parms, BadEncPar}).
		

encode_pp_attribute_orient(Orientation0) ->
    ?d("encode_pp_attribute_orient -> entry with"
       "~n   Orientation0: ~w", [Orientation0]),
    Orientation = encode_attribute_orientation(Orientation0), 
    #'PropertyParm'{name  = "a", 
		    value = ["orient:" ++ Orientation]}.


encode_pp_attribute_type(CType) when is_list(CType) ->
    ?d("encode_pp_attribute_type -> entry with"
       "~n   CType: ~p", [CType]),
    #'PropertyParm'{name  = "a", 
		    value = ["type:" ++ CType]};
encode_pp_attribute_type(BadCType) ->
    error({invalid_type_conf_type, BadCType}).


encode_pp_attribute_charset(CharSet) when is_list(CharSet) ->
    ?d("encode_pp_attribute_charset -> entry with"
       "~n   CharSet: ~p", [CharSet]),
    #'PropertyParm'{name  = "a", 
		    value = ["charset:" ++ CharSet]};
encode_pp_attribute_charset(BadCharSet) ->
    error({invalid_charset_char_set, BadCharSet}).


encode_pp_attribute_sdplang(SdpLang) when is_list(SdpLang) ->
    ?d("encode_pp_attribute_sdplang -> entry with"
       "~n   SdpLang: ~p", [SdpLang]),
    #'PropertyParm'{name  = "a", 
		    value = ["sdplang:" ++ SdpLang]};
encode_pp_attribute_sdplang(BadSdpLang) ->
    error({invalid_sdplang_tag, BadSdpLang}).


encode_pp_attribute_lang(Lang) when is_list(Lang) ->
    ?d("encode_pp_attribute_lang -> entry with"
       "~n   Lang: ~p", [Lang]),
    #'PropertyParm'{name  = "a", 
		    value = ["lang:" ++ Lang]};
encode_pp_attribute_lang(BadLang) ->
    error({invalid_lang_tag, BadLang}).


encode_pp_attribute_framerate(FrameRate) when is_list(FrameRate) ->
    ?d("encode_pp_attribute_framerate -> entry with"
       "~n   FrameRate: ~p", [FrameRate]),
    #'PropertyParm'{name  = "a", 
		    value = ["framerate:" ++ FrameRate]};
encode_pp_attribute_framerate(BadFrameRate) ->
    error({invalid_framerate_frame_rate, BadFrameRate}).


encode_pp_attribute_quality(Quality) when is_integer(Quality) ->
    ?d("encode_pp_attribute_quality -> entry with"
       "~n   Quality: ~w", [Quality]),
    #'PropertyParm'{name  = "a", 
		    value = ["quality:" ++ integer_to_list(Quality)]};
encode_pp_attribute_quality(BadQ) ->
    error({invalid_quality_quality, BadQ}).


encode_pp_attribute_fmtp(Fmt0, Params0) ->
    ?d("encode_pp_attribute_rtpmap -> entry with"
       "~n   Fmt0:    ~p"
       "~n   Params0: ~p", [Fmt0, Params0]),
    Fmt    = encode_fmtp_format(Fmt0),
    Params = encode_fmtp_param(Params0),
    Val    = "fmtp:" ++ Fmt ++ " " ++ Params, 
    #'PropertyParm'{name  = "a", 
		    value = [Val]}.

encode_fmtp_format(Fmt) when is_list(Fmt) ->
    Fmt;
encode_fmtp_format(BadFmt) ->
    error({invalid_fmtp_format, BadFmt}).

encode_fmtp_param(Param) when is_list(Param) ->
    Param;
encode_fmtp_param(BadParam) ->
    error({invalid_fmtp_param, BadParam}).


encode_pp_attribute(Attr, undefined) when is_list(Attr) ->
    ?d("encode_pp_attribute_rtpmap -> entry with"
       "~n   Attr: ~p", [Attr]),
    #'PropertyParm'{name  = "a", 
		    value = [Attr]};
encode_pp_attribute(Attr, Value) when is_list(Attr) and is_list(Value) ->
    ?d("encode_pp_attribute_rtpmap -> entry with"
       "~n   Attr:  ~p"
       "~n   Value: ~p", [Attr, Value]),
    #'PropertyParm'{name  = "a", 
		    value = [Attr ++ ":" ++ Value]};
encode_pp_attribute(BadAttr, BadAttrValue) ->
    error({invalid_attribute, {BadAttr, BadAttrValue}}).


%% ===== Media Announcements =====
%% 
decode_pp_media_announcement(Value) ->
    case string:tokens(Value, " \t") of
	[Media0, PortInfo, Transport | FMT] ->
	    Media = 
		case tolower(Media0) of
		    "audio"       -> audio;
		    "video"       -> video;
		    "application" -> application;
		    "data"        -> data;
		    "control"     -> control;
		    _             -> Media0
		end,
	    {Port, NoOfPorts} = 
		case string:tokens(PortInfo, "/") of
		    [P1, NP] ->
			{s2i(P1, invalid_media_announcement_port),
			 s2i(NP, invalid_media_announcement_nof_ports)};
		    [P2] ->
			{s2i(P2, invalid_media_announcement_port),
			 undefined};
		    Err ->
			invalid_pp(mnta_port_info, Value, Err)
		end,
	    SDP = #megaco_sdp_m{media     = Media,
				port      = Port,
				num_ports = NoOfPorts,
				transport = Transport,
				fmt_list  = FMT},
	    {ok, SDP};
	Err ->
	    invalid_pp(media_name_transp_addr, Value, Err)
    end.


encode_pp_media_announcement(Media0, Port0, undefined, Transport0, FMT0) ->
    ?d("encode_pp_media_announcement -> entry with"
       "~n   Media0:     ~p"
       "~n   Port0:      ~p"
       "~n   Transport0: ~p"
       "~n   FMT0:       ~p", [Media0, Port0, Transport0, FMT0]),
    Media     = encode_media_announcement_media(Media0),
    Port      = encode_media_announcement_port(Port0),
    Transport = encode_media_announcement_transport(Transport0),
    FMT       = encode_media_announcement_fmt_list(FMT0),
    do_encode_pp_media_announcement(Media, Port, "", Transport, FMT);
encode_pp_media_announcement(Media0, Port0, NumPorts0, Transport0, FMT0) ->
    ?d("encode_pp_media_announcement -> entry with"
       "~n   Media0:     ~p"
       "~n   Port0:      ~p"
       "~n   NumPorts0:  ~p"
       "~n   Transport0: ~p"
       "~n   FMT0:       ~p", [Media0, Port0, NumPorts0, Transport0, FMT0]),
    Media     = encode_media_announcement_media(Media0),
    Port      = encode_media_announcement_port(Port0),
    NumPorts  = encode_media_announcement_num_port(NumPorts0), 
    Transport = encode_media_announcement_transport(Transport0),
    FMT       = encode_media_announcement_fmt_list(FMT0),
    do_encode_pp_media_announcement(Media, Port, NumPorts, Transport, FMT).

do_encode_pp_media_announcement(Media, Port, NumOfPorts, Transport, FMT) ->
    Val = Media ++ " " ++ Port ++ NumOfPorts ++ " " ++ Transport ++ FMT,
    #'PropertyParm'{name  = "m", 
		    value = [Val]}.

encode_media_announcement_media(Media) when is_atom(Media) ->
    MaMedia = [audio, video, application, data, control], 
    case lists:member(Media, MaMedia) of
	true ->
	    atom_to_list(Media);
	false ->
	    error({invalid_media_announcement_media, Media})
    end;
encode_media_announcement_media(Media) when is_list(Media) ->
    Media;
encode_media_announcement_media(BadMedia) ->
    error({invalid_media_announcement_media, BadMedia}).

encode_media_announcement_port(Port) when is_integer(Port) ->
    integer_to_list(Port);
encode_media_announcement_port(BadPort) ->
    error({invalid_media_announcement_port, BadPort}).

encode_media_announcement_num_port(NumPort) when is_integer(NumPort) ->
    "/" ++ integer_to_list(NumPort);
encode_media_announcement_num_port(BadNumPort) ->
    error({invalid_media_announcement_num_port, BadNumPort}).

encode_media_announcement_transport(Transport) when is_list(Transport) ->
    Transport;
encode_media_announcement_transport(BadTransport) ->
    error({invalid_media_announcement_transport, BadTransport}).

encode_media_announcement_fmt_list(FmtList) when is_list(FmtList) ->
    F = fun(FMT, Acc) when is_list(FMT) -> 
		Acc ++ " " ++ FMT;
	   (BadFMT, Acc) ->
		error({invalid_media_announcement_fmt_list, {BadFMT, Acc}})
	end,
    lists:foldl(F, [], FmtList);
encode_media_announcement_fmt_list(BadFmtList) ->
    error({invalid_media_announcement_fmt_list, BadFmtList}).


decode_network_type(NT) when is_list(NT) ->
    case tolower(NT) of
	"in" -> in;
	_    -> NT
    end.

encode_network_type(in)                  -> "IN";
encode_network_type(NT) when is_list(NT) -> NT;
encode_network_type(Bad) ->
    {error, {invalid_network_type, Bad}}.


decode_address_type(AT) when is_list(AT) ->
    case tolower(AT) of
	"ip4" -> ip4;
	"ip6" -> ip6;
	_     -> AT
    end.
	     
encode_address_type(ip4)                 -> "IP4";
encode_address_type(ip6)                 -> "IP6";
encode_address_type(AT) when is_list(AT) -> 
    case toupper(AT) of
	"IP4" -> "IP4";
	"IP6" -> "IP6";
	_     -> AT
    end;
encode_address_type(Crap) ->
    {error, {invalid_address_type, Crap}}.
    

s2i(S, E) ->
    case (catch list_to_integer(S)) of
	I when is_integer(I) ->
	    I;
	_ ->
	    error({E, S})
    end.

-define(LOWER(Char),
        if
            Char >= $A, Char =< $Z ->
                Char - ($A - $a);
            true ->
                Char
        end).
tolower(Chars) ->
    [?LOWER(Char) || Char <- Chars].

-define(UPPER(Char),
        if
            Char >= $a, Char =< $z ->
                Char + ($A - $a);
            true ->
                Char
        end).
toupper(Chars) ->
    [?UPPER(Char) || Char <- Chars].
    
invalid_pp(What, Value, Error) ->
    {error, {invalid_PropertyParm, {What, Value, Error}}}.

error(Reason) ->
    throw({error, Reason}).

