| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ########################################################################### | 
| 2 |  |  |  |  |  |  | # Net::SIP::SDP | 
| 3 |  |  |  |  |  |  | # parse and manipulation of SDP packets in the context relevant for SIP | 
| 4 |  |  |  |  |  |  | # Spec: | 
| 5 |  |  |  |  |  |  | # RFC2327 - base RFC for SDP | 
| 6 |  |  |  |  |  |  | # RFC3264 - offer/answer model with SDP (used in SIP RFC3261) | 
| 7 |  |  |  |  |  |  | # RFC3266 - IP6 in SDP | 
| 8 |  |  |  |  |  |  | # RFC3605 - "a=rtcp:port" attribute UNSUPPORTED!!!! | 
| 9 |  |  |  |  |  |  | ########################################################################### | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 44 |  |  | 44 |  | 270 | use strict; | 
|  | 44 |  |  |  |  | 112 |  | 
|  | 44 |  |  |  |  | 1254 |  | 
| 12 | 44 |  |  | 44 |  | 192 | use warnings; | 
|  | 44 |  |  |  |  | 79 |  | 
|  | 44 |  |  |  |  | 1595 |  | 
| 13 |  |  |  |  |  |  | package Net::SIP::SDP; | 
| 14 | 44 |  |  | 44 |  | 19718 | use Hash::Util qw(lock_keys); | 
|  | 44 |  |  |  |  | 109483 |  | 
|  | 44 |  |  |  |  | 261 |  | 
| 15 | 44 |  |  | 44 |  | 3273 | use Net::SIP::Debug; | 
|  | 44 |  |  |  |  | 98 |  | 
|  | 44 |  |  |  |  | 290 |  | 
| 16 | 44 |  |  | 44 |  | 675 | use Net::SIP::Util qw(ip_is_v4 ip_is_v6); | 
|  | 44 |  |  |  |  | 109 |  | 
|  | 44 |  |  |  |  | 1999 |  | 
| 17 | 44 |  |  | 44 |  | 229 | use Socket; | 
|  | 44 |  |  |  |  | 76 |  | 
|  | 44 |  |  |  |  | 19006 |  | 
| 18 | 44 |  |  | 44 |  | 278 | use Scalar::Util 'looks_like_number'; | 
|  | 44 |  |  |  |  | 112 |  | 
|  | 44 |  |  |  |  | 112578 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | ########################################################################### | 
| 22 |  |  |  |  |  |  | # create new Net::SIP::SDP packet from string or parts | 
| 23 |  |  |  |  |  |  | # Args: see new_from_parts|new_from_string | 
| 24 |  |  |  |  |  |  | # Returns: $self | 
| 25 |  |  |  |  |  |  | ########################################################################### | 
| 26 |  |  |  |  |  |  | sub new { | 
| 27 | 100 |  |  | 100 | 1 | 287 | my $class = shift; | 
| 28 | 100 | 100 |  |  |  | 706 | return $class->new_from_parts(@_) if @_>1; | 
| 29 | 48 |  |  |  |  | 131 | my $data = shift; | 
| 30 | 48 | 50 | 33 |  |  | 502 | return ( !ref($data) || UNIVERSAL::isa( $data,'ARRAY' )) | 
| 31 |  |  |  |  |  |  | ?  $class->new_from_string( $data ) | 
| 32 |  |  |  |  |  |  | : $class->new_from_parts( $data ); | 
| 33 |  |  |  |  |  |  | } | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | ########################################################################### | 
| 36 |  |  |  |  |  |  | # create new Net::SIP::SDP packet from parts | 
| 37 |  |  |  |  |  |  | # Args: ($class,$global,@media) | 
| 38 |  |  |  |  |  |  | #   $global: \%hash of (key,val) for global section, val can be | 
| 39 |  |  |  |  |  |  | #       scalar or array-ref (for multiple val). keys can be the | 
| 40 |  |  |  |  |  |  | #       on-letter SDP keys and the special key 'addr' for constructing | 
| 41 |  |  |  |  |  |  | #       a connection-field | 
| 42 |  |  |  |  |  |  | #   @media: list of \%hashes. val in hash can be scalar or array-ref | 
| 43 |  |  |  |  |  |  | #       (for multiple val), keys can be on-letter SDP keys or the special | 
| 44 |  |  |  |  |  |  | #       keys addr (for connection-field), port,range,proto,media,fmt (for | 
| 45 |  |  |  |  |  |  | #       media description) | 
| 46 |  |  |  |  |  |  | # Returns: $self | 
| 47 |  |  |  |  |  |  | ########################################################################### | 
| 48 |  |  |  |  |  |  | sub new_from_parts { | 
| 49 | 52 |  |  | 52 | 1 | 184 | my ($class,$global,@media) = @_; | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 52 |  |  |  |  | 264 | my %g = %$global; | 
| 52 | 52 |  |  |  |  | 947 | my $g_addr = delete $g{addr}; | 
| 53 | 52 | 50 |  |  |  | 356 | die "no support for time rates" if $g{r}; | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 52 |  |  |  |  | 112 | my $atyp; | 
| 56 | 52 | 50 | 33 |  |  | 696 | if ($g_addr && !$g{c}) { | 
| 57 | 52 | 50 |  |  |  | 325 | $atyp = ip_is_v4($g_addr) ? 'IP4':'IP6'; | 
| 58 | 52 |  |  |  |  | 512 | $g{c} = "IN $atyp $g_addr"; | 
| 59 |  |  |  |  |  |  | } | 
| 60 | 52 | 50 |  |  |  | 609 | $g{t} = "0 0" if !$g{t}; | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 52 |  |  |  |  | 242 | my @gl; | 
| 63 | 52 |  |  |  |  | 254 | my %global_self = ( lines => \@gl, addr => $g_addr ); | 
| 64 | 52 |  |  |  |  | 236 | lock_keys(%global_self); | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 52 |  |  |  |  | 585 | my @media_self; | 
| 67 | 52 |  |  |  |  | 674 | my $self = bless { | 
| 68 |  |  |  |  |  |  | global => \%global_self, | 
| 69 |  |  |  |  |  |  | addr => $g_addr, | 
| 70 |  |  |  |  |  |  | media => \@media_self | 
| 71 |  |  |  |  |  |  | },$class; | 
| 72 | 52 |  |  |  |  | 305 | lock_keys(%$self); | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | # first comes the version | 
| 75 | 52 |  | 50 |  |  | 1448 | push @gl,[ 'v',delete($g{v}) || 0 ]; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # then the origin | 
| 78 | 52 |  |  |  |  | 152 | my $o = delete($g{o}); | 
| 79 | 52 | 50 |  |  |  | 133 | if ( !$o ) { | 
| 80 | 52 |  |  |  |  | 113 | my $t = time(); | 
| 81 | 52 |  | 0 |  |  | 128 | $atyp ||= $g{c} =~m{^IN (IP4|IP6) } && $1; | 
|  |  |  | 33 |  |  |  |  | 
| 82 | 52 |  | 33 |  |  | 347 | $o = "anonymous $t $t IN $atyp ".( $g_addr | 
| 83 |  |  |  |  |  |  | || ($atyp eq 'IP4' ? '127.0.0.1' : '::1') ); | 
| 84 |  |  |  |  |  |  | } | 
| 85 | 52 |  |  |  |  | 177 | push @gl,[ 'o',$o ]; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | # session name | 
| 88 | 52 |  | 50 |  |  | 946 | push @gl,[ 's', delete($g{s}) || 'session' ]; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # various headers in the right order | 
| 91 | 52 |  |  |  |  | 218 | foreach my $key (qw( i u e p c b t z k a )) { | 
| 92 | 520 |  |  |  |  | 722 | my $v = delete $g{$key}; | 
| 93 | 520 | 100 |  |  |  | 987 | defined($v) || next; | 
| 94 | 104 | 50 |  |  |  | 260 | foreach ( ref($v) ? @$v:($v) ) { | 
| 95 | 104 |  |  |  |  | 446 | push @gl, [ $key,$_ ]; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | # die on unknown keys | 
| 100 | 52 | 50 |  |  |  | 178 | die "bad keys in global: ".join( ' ',keys(%g)) if %g; | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | # media descriptions | 
| 103 | 52 |  |  |  |  | 212 | foreach my $m (@media) { | 
| 104 | 52 |  |  |  |  | 597 | DEBUG_DUMP( 100,$m ); | 
| 105 | 52 |  |  |  |  | 450 | my %m = %$m; | 
| 106 | 52 |  |  |  |  | 139 | delete $m{lines}; | 
| 107 | 52 |  |  |  |  | 89 | my @lines; | 
| 108 | 52 |  |  |  |  | 202 | my %m_self = ( lines => \@lines ); | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | # extract from 'm' line or from other args | 
| 111 | 52 | 50 |  |  |  | 191 | if ( my $mline = delete $m{m} ) { | 
| 112 | 0 |  |  |  |  | 0 | push @lines,[ 'm',$mline ]; | 
| 113 | 0 |  |  |  |  | 0 | @m_self{qw(media port range proto fmt)} = _split_m( $mline ); | 
| 114 |  |  |  |  |  |  | } else { | 
| 115 | 52 |  |  |  |  | 186 | foreach (qw( port media proto )) { | 
| 116 | 156 | 50 |  |  |  | 471 | defined( $m_self{$_} = delete $m{$_} ) | 
| 117 |  |  |  |  |  |  | || die "no $_ in media description"; | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  | $m_self{range} = delete($m{range}) | 
| 120 | 52 |  | 66 |  |  | 1160 | || ( $m_self{proto} =~m{^RTP/} ? 2:1 ); | 
| 121 |  |  |  |  |  |  | defined( my $fmt = $m_self{fmt} = delete $m{fmt} ) | 
| 122 | 52 | 50 |  |  |  | 284 | || die "no fmt in media description"; | 
| 123 | 52 |  |  |  |  | 717 | my $mline = _join_m( @m_self{qw(media port range proto)},$fmt ); | 
| 124 | 52 |  |  |  |  | 223 | push @lines, [ 'm',$mline ]; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | # if no connection line given construct one, if addr ne g_addr | 
| 128 | 52 | 50 |  |  |  | 201 | if ( !$m{c} ) { | 
| 129 | 52 | 50 |  |  |  | 237 | if ( my $addr = delete $m{addr} ) { | 
|  |  | 50 |  |  |  |  |  | 
| 130 | 0 |  |  |  |  | 0 | $m_self{addr} = $addr; | 
| 131 | 0 | 0 |  |  |  | 0 | $m{c} = _join_c($addr) if $addr ne $g_addr; | 
| 132 |  |  |  |  |  |  | } elsif ( $g_addr ) { | 
| 133 | 52 |  |  |  |  | 154 | $m_self{addr} = $g_addr; | 
| 134 |  |  |  |  |  |  | } else { | 
| 135 | 0 |  |  |  |  | 0 | die "neither local nor global address for media"; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  | } else { | 
| 138 | 0 |  |  |  |  | 0 | $m_self{addr} = _split_c($m{c}); | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | # various headers in the right order | 
| 142 | 52 |  |  |  |  | 150 | foreach my $key (qw( i c b k a )) { | 
| 143 | 260 |  |  |  |  | 378 | my $v = delete $m{$key}; | 
| 144 | 260 | 100 |  |  |  | 488 | defined($v) || next; | 
| 145 | 52 | 50 |  |  |  | 210 | foreach ( ref($v) ? @$v:($v) ) { | 
| 146 | 104 |  |  |  |  | 490 | push @lines, [ $key,$_ ]; | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  | # die on unknown keys | 
| 150 | 52 | 50 |  |  |  | 195 | die "bad keys in media: ".join( ' ',keys(%m)) if %m; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 52 |  |  |  |  | 216 | lock_keys(%m_self); | 
| 153 | 52 |  |  |  |  | 929 | push @media_self,\%m_self; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 52 |  |  |  |  | 650 | return $self; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | ########################################################################### | 
| 161 |  |  |  |  |  |  | # create new Net::SIP::SDP packet from string or lines | 
| 162 |  |  |  |  |  |  | # Args: ($class,$string) | 
| 163 |  |  |  |  |  |  | #    $string: either scalar or \@list_of_lines_in_string | 
| 164 |  |  |  |  |  |  | # Returns: $self | 
| 165 |  |  |  |  |  |  | ########################################################################### | 
| 166 |  |  |  |  |  |  | sub new_from_string { | 
| 167 | 48 |  |  | 48 | 1 | 150 | my ($class,$string) = @_; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | # split into lines | 
| 170 | 48 | 50 | 33 |  |  | 211 | Carp::confess('expected string or ARRAY ref' ) | 
| 171 |  |  |  |  |  |  | if ref($string) && ref( $string ) ne 'ARRAY'; | 
| 172 | 48 | 50 |  |  |  | 959 | my @lines = ref($string) | 
| 173 |  |  |  |  |  |  | ? @$string | 
| 174 |  |  |  |  |  |  | : split( m{\r?\n}, $string ); | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | # split lines into key,val | 
| 177 | 48 |  |  |  |  | 352 | foreach my $l (@lines) { | 
| 178 | 384 | 50 |  |  |  | 1588 | my ($key,$val) = $l=~m{^([a-z])=(.*)} | 
| 179 |  |  |  |  |  |  | or die "bad SDP line '$l'"; | 
| 180 | 384 |  |  |  |  | 1031 | $l = [ $key,$val ]; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | # SELF: | 
| 184 |  |  |  |  |  |  | # global { | 
| 185 |  |  |  |  |  |  | #   lines => [], | 
| 186 |  |  |  |  |  |  | #   addr     # globally defined addr (if any) | 
| 187 |  |  |  |  |  |  | # } | 
| 188 |  |  |  |  |  |  | # media [ | 
| 189 |  |  |  |  |  |  | #   { | 
| 190 |  |  |  |  |  |  | #     lines => [], | 
| 191 |  |  |  |  |  |  | #     addr   # addr for ports | 
| 192 |  |  |  |  |  |  | #     port   # starting port | 
| 193 |  |  |  |  |  |  | #     range  # range of ports (1..) | 
| 194 |  |  |  |  |  |  | #     proto  # udp, RTP/AVP,.. | 
| 195 |  |  |  |  |  |  | #     media  # audio|video|data... | 
| 196 |  |  |  |  |  |  | #   } | 
| 197 |  |  |  |  |  |  | # ] | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 48 |  |  |  |  | 157 | my (%global,@media); | 
| 200 | 48 |  |  |  |  | 606 | my $self = bless { | 
| 201 |  |  |  |  |  |  | global => \%global, | 
| 202 |  |  |  |  |  |  | addr => undef, | 
| 203 |  |  |  |  |  |  | session_id => undef, | 
| 204 |  |  |  |  |  |  | session_version => undef, | 
| 205 |  |  |  |  |  |  | media => \@media | 
| 206 |  |  |  |  |  |  | }, $class; | 
| 207 | 48 |  |  |  |  | 328 | lock_keys(%$self); | 
| 208 | 48 |  |  |  |  | 590 | my $gl = $global{lines} = []; | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | # first line must be version | 
| 211 | 48 |  |  |  |  | 139 | my $line = shift(@lines); | 
| 212 | 48 | 50 |  |  |  | 205 | $line->[0] eq 'v' || die "missing version"; | 
| 213 | 48 | 50 |  |  |  | 163 | $line->[1] eq '0' || die "bad SDP version $line->[1]"; | 
| 214 | 48 |  |  |  |  | 122 | push @$gl,$line; | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | # second line must be origin | 
| 217 |  |  |  |  |  |  | # "o=" username sess-id sess-version nettype addrtype addr | 
| 218 | 48 |  |  |  |  | 75 | $line = shift(@lines); | 
| 219 | 48 | 50 |  |  |  | 187 | $line->[0] eq 'o' || die "missing origin"; | 
| 220 |  |  |  |  |  |  | (undef,$self->{session_id},$self->{session_version}) | 
| 221 | 48 |  |  |  |  | 273 | = split( ' ',$line->[1] ); | 
| 222 | 48 |  |  |  |  | 126 | push @$gl,$line; | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | # skip until c or m line | 
| 225 | 48 |  |  |  |  | 106 | my $have_c =0; | 
| 226 | 48 |  |  |  |  | 175 | while ( $line = shift(@lines) ) { | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | # end of global section, beginning of media section | 
| 229 | 192 | 100 |  |  |  | 457 | last if $line->[0] eq 'm'; | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 144 |  |  |  |  | 358 | push @$gl,$line; | 
| 232 | 144 | 100 |  |  |  | 338 | if ( $line->[0] eq 'c' ) { | 
| 233 |  |  |  |  |  |  | # "c=" nettype addrtype connection-address | 
| 234 | 48 | 50 |  |  |  | 124 | $have_c++ && die "multiple global [c]onnection fields"; | 
| 235 | 48 |  |  |  |  | 439 | $global{addr} = _split_c( $line->[1] ); | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | # parse media section(s) | 
| 240 |  |  |  |  |  |  | # $line has already first m-Element in it | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 48 |  |  |  |  | 150 | while ($line) { | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 48 | 50 |  |  |  | 288 | $line->[0] eq 'm' || die "expected [m]edia line"; | 
| 245 |  |  |  |  |  |  | # "m=" media port ["/" integer] proto 1*fmt | 
| 246 | 48 |  |  |  |  | 254 | my ($media,$port,$range,$proto,$fmt) = _split_m( $line->[1] ); | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 48 |  |  |  |  | 125 | my $ml = [ $line ]; | 
| 249 |  |  |  |  |  |  | my %m = ( | 
| 250 |  |  |  |  |  |  | lines => $ml, | 
| 251 |  |  |  |  |  |  | addr  => $global{addr}, | 
| 252 | 48 |  | 50 |  |  | 543 | port  => $port, | 
| 253 |  |  |  |  |  |  | range => $range || 1, | 
| 254 |  |  |  |  |  |  | media => $media, | 
| 255 |  |  |  |  |  |  | proto => $proto, | 
| 256 |  |  |  |  |  |  | fmt   => $fmt, | 
| 257 |  |  |  |  |  |  | ); | 
| 258 | 48 |  |  |  |  | 296 | lock_keys(%m); | 
| 259 | 48 |  |  |  |  | 530 | push @media,\%m; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | # find out connection | 
| 262 | 48 |  |  |  |  | 4170 | my $have_c = 0; | 
| 263 | 48 |  |  |  |  | 266 | while ( $line = shift(@lines) ) { | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | # next media section | 
| 266 | 96 | 50 |  |  |  | 237 | last if $line->[0] eq 'm'; | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 96 |  |  |  |  | 178 | push @$ml,$line; | 
| 269 | 96 | 50 |  |  |  | 379 | if ( $line->[0] eq 'c' ) { | 
| 270 |  |  |  |  |  |  | # connection-field | 
| 271 | 0 | 0 |  |  |  | 0 | $have_c++ && die "multiple [c]onnection fields in media section $#media"; | 
| 272 | 0 |  |  |  |  | 0 | $m{addr} = _split_c( $line->[1] ); | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 48 |  |  |  |  | 461 | return $self; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | ########################################################################### | 
| 282 |  |  |  |  |  |  | # get SDP data as string | 
| 283 |  |  |  |  |  |  | # Args: $self | 
| 284 |  |  |  |  |  |  | # Returns: $string | 
| 285 |  |  |  |  |  |  | ########################################################################### | 
| 286 |  |  |  |  |  |  | sub as_string { | 
| 287 | 74 |  |  | 74 | 1 | 133 | my $self = shift; | 
| 288 | 74 |  |  |  |  | 358 | my $data = ''; | 
| 289 | 74 |  |  |  |  | 148 | foreach (@{ $self->{global}{lines}} ) { | 
|  | 74 |  |  |  |  | 360 |  | 
| 290 | 370 |  |  |  |  | 907 | $data .= $_->[0].'='.$_->[1]."\r\n"; | 
| 291 |  |  |  |  |  |  | } | 
| 292 | 74 | 50 |  |  |  | 243 | if ( my $media = $self->{media} ) { | 
| 293 | 74 |  |  |  |  | 442 | foreach my $m (@$media) { | 
| 294 | 74 |  |  |  |  | 154 | foreach (@{ $m->{lines} }) { | 
|  | 74 |  |  |  |  | 173 |  | 
| 295 | 222 |  |  |  |  | 647 | $data .= $_->[0].'='.$_->[1]."\r\n"; | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  | } | 
| 299 | 74 |  |  |  |  | 297 | return $data; | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 56 |  |  | 56 | 1 | 1106 | sub content_type { return 'application/sdp' }; | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | ########################################################################### | 
| 305 |  |  |  |  |  |  | # extracts media infos | 
| 306 |  |  |  |  |  |  | # Args: $self | 
| 307 |  |  |  |  |  |  | # Returns: @media|$media | 
| 308 |  |  |  |  |  |  | #  @media: list of hashes with the following keys: | 
| 309 |  |  |  |  |  |  | #     addr:  IP4/IP6 addr | 
| 310 |  |  |  |  |  |  | #     port:  the starting port number | 
| 311 |  |  |  |  |  |  | #     range: number, how many ports starting with port should be allocated | 
| 312 |  |  |  |  |  |  | #     proto: media proto, e.g. udp or RTP/AVP | 
| 313 |  |  |  |  |  |  | #     media: audio|video|data|... from the media description | 
| 314 |  |  |  |  |  |  | #     fmt:   format(s) from media line | 
| 315 |  |  |  |  |  |  | #     lines: \@list with all lines from media description as [ key,value ] | 
| 316 |  |  |  |  |  |  | #            useful to access [a]ttributes or encryption [k]eys | 
| 317 |  |  |  |  |  |  | #  $media: \@media if in scalar context | 
| 318 |  |  |  |  |  |  | # Comment: do not manipulate the result!!! | 
| 319 |  |  |  |  |  |  | ########################################################################### | 
| 320 |  |  |  |  |  |  | sub get_media { | 
| 321 | 64 |  |  | 64 | 1 | 120 | my $self = shift; | 
| 322 | 64 |  | 50 |  |  | 200 | my $m = $self->{media} || []; | 
| 323 | 64 | 50 |  |  |  | 601 | return wantarray ? @$m : $m; | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | ########################################################################### | 
| 327 |  |  |  |  |  |  | # returns type number to RTP codec name, e.g. 'telephone-event/8000' -> 101 | 
| 328 |  |  |  |  |  |  | # Args: ($self,$name,[$index]) | 
| 329 |  |  |  |  |  |  | #  $name: name of codec | 
| 330 |  |  |  |  |  |  | #  $index: index or type of media description, default 0, e.g. the first | 
| 331 |  |  |  |  |  |  | #   channel. 'audio' would specify the first audio channel | 
| 332 |  |  |  |  |  |  | # Returns: type number|undef | 
| 333 |  |  |  |  |  |  | ########################################################################### | 
| 334 |  |  |  |  |  |  | sub name2int { | 
| 335 | 12 |  |  | 12 | 1 | 157 | my ($self,$name,$index) = @_; | 
| 336 | 12 | 50 |  |  |  | 46 | $index = 0 if ! defined $index; | 
| 337 | 12 |  |  |  |  | 31 | my $m = $self->{media}; | 
| 338 | 12 | 50 |  |  |  | 66 | if ( ! looks_like_number($index)) { | 
| 339 |  |  |  |  |  |  | # look for media type | 
| 340 | 12 | 50 |  |  |  | 40 | my @i = grep { $m->[$_]{media} eq $index } (0..$#$m) or return; | 
|  | 12 |  |  |  |  | 87 |  | 
| 341 | 12 |  |  |  |  | 24 | $index = $i[0]; | 
| 342 |  |  |  |  |  |  | } | 
| 343 | 12 | 50 |  |  |  | 35 | $m = $m->[$index] or return; | 
| 344 | 12 |  |  |  |  | 24 | for my $l (@{$m->{lines}}) { | 
|  | 12 |  |  |  |  | 85 |  | 
| 345 | 30 | 100 |  |  |  | 89 | $l->[0] eq 'a' or next; | 
| 346 | 18 | 100 |  |  |  | 127 | $l->[1] =~m{^rtpmap:(\d+)\s+(\S+)} or next; | 
| 347 | 12 | 100 |  |  |  | 101 | return $1 if $2 eq $name; | 
| 348 |  |  |  |  |  |  | } | 
| 349 | 6 |  |  |  |  | 99 | return; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | ########################################################################### | 
| 353 |  |  |  |  |  |  | # replace the addr and port (eg where it will listen) from the media in | 
| 354 |  |  |  |  |  |  | # the SDP packet | 
| 355 |  |  |  |  |  |  | # used for remapping by a proxy for NAT or inspection etc. | 
| 356 |  |  |  |  |  |  | # Args: ($self,@replace) | 
| 357 |  |  |  |  |  |  | #   @replace: @list of [ addr,port ] or list with single array-ref to such list | 
| 358 |  |  |  |  |  |  | #      size of list must be the same like one gets from get_media, e.g. | 
| 359 |  |  |  |  |  |  | #      there must be a mapping for each media | 
| 360 |  |  |  |  |  |  | # Comment: die() on error | 
| 361 |  |  |  |  |  |  | ########################################################################### | 
| 362 |  |  |  |  |  |  | sub replace_media_listen { | 
| 363 | 1 |  |  | 1 | 1 | 4 | my ($self,@replace) = @_; | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 1 | 50 |  |  |  | 4 | if (@replace == 1) { | 
| 366 |  |  |  |  |  |  | # check if [ $pair1,$pair2,.. ] instead of ( $pair1,.. ) | 
| 367 | 1 | 50 |  |  |  | 4 | @replace = @{$replace[0]} if ref($replace[0][0]); | 
|  | 0 |  |  |  |  | 0 |  | 
| 368 |  |  |  |  |  |  | } | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 1 |  | 50 |  |  | 5 | my $media = $self->{media} || []; | 
| 371 | 1 | 50 |  |  |  | 3 | die "media count mismatch in replace_media_listen" if @replace != @$media; | 
| 372 |  |  |  |  |  |  |  | 
| 373 | 1 |  |  |  |  | 3 | my $global = $self->{global}; | 
| 374 | 1 |  |  |  |  | 2 | my $g_addr = $global->{addr}; | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | # try to remap global connection-field | 
| 377 | 1 | 50 |  |  |  | 3 | if ( $g_addr ) { | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | # find mappings old -> new | 
| 380 | 1 |  |  |  |  | 2 | my %addr_old2new; | 
| 381 | 1 |  |  |  |  | 4 | for( my $i=0;$i<@$media;$i++ ) { | 
| 382 | 1 |  |  |  |  | 5 | $addr_old2new{ $media->[$i]{addr} }{ $replace[$i][0] }++ | 
| 383 |  |  |  |  |  |  | } | 
| 384 | 1 |  |  |  |  | 3 | my $h = $addr_old2new{ $g_addr }; | 
| 385 |  |  |  |  |  |  |  | 
| 386 | 1 | 50 | 33 |  |  | 14 | if ( $h && keys(%$h) == 1 ) { | 
| 387 |  |  |  |  |  |  | # there is a uniq mapping from old to new address | 
| 388 | 1 |  |  |  |  | 5 | my $new_addr = (keys(%$h))[0]; | 
| 389 | 1 | 50 |  |  |  | 5 | if ( $g_addr ne $new_addr ) { | 
| 390 | 1 |  |  |  |  | 3 | $g_addr = $global->{addr} = $new_addr; | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | # find connection-field and replace address | 
| 393 | 1 |  |  |  |  | 2 | foreach my $line (@{ $global->{lines} }) { | 
|  | 1 |  |  |  |  | 13 |  | 
| 394 | 4 | 100 |  |  |  | 11 | if ( $line->[0] eq 'c' ) { | 
| 395 | 1 |  |  |  |  | 3 | $line->[1] = _join_c( $new_addr ); | 
| 396 | 1 |  |  |  |  | 4 | last; # there is only one connection-field | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  | } | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | } else { | 
| 402 |  |  |  |  |  |  | # the is no uniq mapping from old to new | 
| 403 |  |  |  |  |  |  | # this can be because old connection-field was never used | 
| 404 |  |  |  |  |  |  | # (because each media section had it's own) or that | 
| 405 |  |  |  |  |  |  | # different new addr gets used for the same old addr | 
| 406 |  |  |  |  |  |  | # -> remove global connection line | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 0 |  |  |  |  | 0 | $g_addr = $global->{addr} = undef; | 
| 409 | 0 |  |  |  |  | 0 | my $l = $global->{lines}; | 
| 410 | 0 |  |  |  |  | 0 | @$l = grep { $_->[0] ne 'c' } @$l; | 
|  | 0 |  |  |  |  | 0 |  | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  | } | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | # remap addr,port in each media section | 
| 415 |  |  |  |  |  |  | # if new addr is != $g_addr and I had no connection-field | 
| 416 |  |  |  |  |  |  | # before I need to add one | 
| 417 | 1 |  |  |  |  | 4 | for( my $i=0;$i<@$media;$i++ ) { | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 1 |  |  |  |  | 2 | my $m = $media->[$i]; | 
| 420 | 1 |  |  |  |  | 3 | my $r = $replace[$i]; | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | # replace port in media line | 
| 423 | 1 | 50 |  |  |  | 4 | if ( $r->[1] != $m->{port} ) { | 
| 424 | 0 |  |  |  |  | 0 | $m->{port} = $r->[1]; | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | # [m]edia line should be the first | 
| 427 | 0 |  |  |  |  | 0 | my $line = $m->{lines}[0]; | 
| 428 | 0 | 0 |  |  |  | 0 | $line->[0] eq 'm' || die "[m]edia line is not first"; | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | # media port(/range)... | 
| 431 | 0 | 0 |  |  |  | 0 | if ( $r->[1] ) { | 
| 432 |  |  |  |  |  |  | # port!=0: replace port only | 
| 433 | 0 |  |  |  |  | 0 | $line->[1] =~s{^(\S+\s+)\d+}{$1$r->[1]}; | 
| 434 |  |  |  |  |  |  | } else { | 
| 435 |  |  |  |  |  |  | # port == 0: replace port and range with '0' | 
| 436 | 0 |  |  |  |  | 0 | $line->[1] =~s{^(\S+\s+)\S+}{${1}0}; | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | # replace addr in connection line | 
| 441 | 1 | 50 |  |  |  | 3 | if ( $r->[0] ne $m->{addr} ) { | 
| 442 | 1 |  |  |  |  | 2 | $m->{addr} = $r->[0]; | 
| 443 | 1 |  |  |  |  | 2 | my $have_c = 0; | 
| 444 | 1 |  |  |  |  | 3 | foreach my $line (@{ $m->{lines} }) { | 
|  | 1 |  |  |  |  | 2 |  | 
| 445 | 3 | 50 |  |  |  | 7 | if ( $line->[0] eq 'c' ) { | 
| 446 | 0 |  |  |  |  | 0 | $have_c++; | 
| 447 | 0 |  |  |  |  | 0 | $line->[1] = _join_c($r->[0]); | 
| 448 | 0 |  |  |  |  | 0 | last; # there is only one connection-field | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  | } | 
| 451 | 1 | 50 | 33 |  |  | 18 | if ( !$have_c && ( ! $g_addr || $r->[0] ne $g_addr )) { | 
|  |  |  | 33 |  |  |  |  | 
| 452 |  |  |  |  |  |  | # there was no connection-field before | 
| 453 |  |  |  |  |  |  | # and the media addr is different from the global | 
| 454 | 0 |  |  |  |  | 0 | push @{ $m->{lines} },[ 'c', _join_c( $r->[0] ) ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 455 |  |  |  |  |  |  | } | 
| 456 |  |  |  |  |  |  | } | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  | } | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | ########################################################################### | 
| 462 |  |  |  |  |  |  | # extract addr from [c]connection field and back | 
| 463 |  |  |  |  |  |  | ########################################################################### | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | sub _split_c { | 
| 466 | 48 |  |  | 48 |  | 327 | my ($ntyp,$atyp,$addr) = split( ' ',shift,3 ); | 
| 467 | 48 | 50 |  |  |  | 208 | $ntyp eq 'IN'  or die "nettype $ntyp not supported"; | 
| 468 | 48 | 50 |  |  |  | 146 | if ( $atyp eq 'IP4' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 469 | 48 | 50 |  |  |  | 391 | die "bad IP4 address: '$addr'" if ! ip_is_v4($addr); | 
| 470 |  |  |  |  |  |  | } elsif ( $atyp eq 'IP6' ) { | 
| 471 | 0 | 0 |  |  |  | 0 | die "bad IP6 address: '$addr'" if ! ip_is_v6($addr); | 
| 472 |  |  |  |  |  |  | } else { | 
| 473 | 0 |  |  |  |  | 0 | die "addrtype $atyp not supported" | 
| 474 |  |  |  |  |  |  | } | 
| 475 | 48 |  |  |  |  | 236 | return $addr; | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  | sub _join_c { | 
| 478 | 1 |  |  | 1 |  | 3 | my $addr = shift; | 
| 479 | 1 | 50 |  |  |  | 11 | my $atyp = $addr =~m{:} ? 'IP6':'IP4'; | 
| 480 | 1 |  |  |  |  | 5 | return "IN $atyp $addr"; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | ########################################################################### | 
| 485 |  |  |  |  |  |  | # extract data from [m]edia field and back | 
| 486 |  |  |  |  |  |  | ########################################################################### | 
| 487 |  |  |  |  |  |  | sub _split_m { | 
| 488 | 48 |  |  | 48 |  | 111 | my $mline = shift; | 
| 489 | 48 | 50 |  |  |  | 690 | my ($media,$port,$range,$proto,$fmt) = | 
| 490 |  |  |  |  |  |  | $mline =~m{^(\w+)\s+(\d+)(?:/(\d+))?\s+(\S+)((?:\s+\S+)+)} | 
| 491 |  |  |  |  |  |  | or die "bad [m]edia: '$mline'"; | 
| 492 | 48 |  | 50 |  |  | 325 | $range ||= 1; | 
| 493 | 48 | 50 |  |  |  | 308 | $range *=2 if $proto =~m{^RTP/}; # RTP+RTCP | 
| 494 | 48 |  |  |  |  | 311 | return ($media,$port,$range,$proto, [ split( ' ',$fmt) ]); | 
| 495 |  |  |  |  |  |  | } | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | sub _join_m { | 
| 498 | 52 |  |  | 52 |  | 283 | my ($media,$port,$range,$proto,@fmt) = @_; | 
| 499 | 52 | 50 | 33 |  |  | 719 | @fmt = @{$fmt[0]} if @fmt == 1 && ref($fmt[0]); | 
|  | 52 |  |  |  |  | 226 |  | 
| 500 | 52 | 50 |  |  |  | 721 | $range /= 2 if $proto =~m{^RTP/}; | 
| 501 | 52 | 50 |  |  |  | 194 | $port .= "/$range" if $range>1; | 
| 502 | 52 |  |  |  |  | 351 | return join( ' ',$media,$port,$proto,@fmt ); | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | 1; |