File Coverage

blib/lib/Net/SIP/Endpoint/Context.pm
Criterion Covered Total %
statement 183 229 79.9
branch 63 104 60.5
condition 43 76 56.5
subroutine 19 21 90.4
pod 9 9 100.0
total 317 439 72.2


line stmt bran cond sub pod time code
1              
2             ############################################################################
3             # Net::SIP::Endpoint::Context
4             # the calling context for a call managed by the endpoint
5             ############################################################################
6              
7 41     41   273 use strict;
  41         83  
  41         1292  
8 41     41   228 use warnings;
  41         81  
  41         2716  
9              
10             package Net::SIP::Endpoint::Context;
11              
12             use fields (
13              
14             # ===== can be set with new()
15 41         268 'method', # initiated by which method
16             'from', # from where
17             'to', # to where
18             'auth', # [ user,pass ] or { realm1 => [ user1,pass1 ], realm2 => [ user2,pass2 ],... }
19             # or callback(realm,user)->pass
20             # if given, handle_response might automatically try to authorize requests
21             'contact', # optional local contact
22             'remote_contact', # remote contact from response
23             'callid', # call-id value
24             'cseq', # number in cseq header
25             'route', # for 'route' header, comes usually from 'record-route' info in response
26             'via', # for 'via' header in created responses, comes from incoming request
27             'incoming', # flag if call is incoming, e.g. 'to' is myself
28             'local_tag', # local tag which gets assigned to either from or to depending on incoming
29              
30             # ===== Internals
31             # \@array of hashrefs for infos about pending transactions
32             '_transactions',
33             # arrayref specifying a user defined callback for request success or failure
34             '_callback',
35             # cseq counter for incoming requests
36             '_cseq_incoming',
37             # last request in current incoming transaction
38             '_last_transreq',
39              
40 41     41   270 );
  41         92  
41              
42              
43 41     41   6472 use Digest::MD5 'md5_hex';
  41         97  
  41         2436  
44 41     41   274 use Net::SIP::Request;
  41         109  
  41         1111  
45 41     41   260 use Net::SIP::Response;
  41         86  
  41         1047  
46 41     41   197 use Net::SIP::Debug;
  41         139  
  41         271  
47 41     41   314 use Errno qw( EINVAL EPERM EFAULT );
  41         88  
  41         2308  
48 41     41   263 use Hash::Util 'lock_keys';
  41         91  
  41         231  
49 41     41   2414 use List::Util 'first';
  41         90  
  41         2242  
50 41     41   245 use Net::SIP::Util ':all';
  41         81  
  41         116932  
51              
52             ############################################################################
53             # Creates new context
54             # Args: ($class,@args)
55             # @args: either single \%args (hash-ref) or %args (hash) with at least
56             # values for from and to
57             # callid,cseq will be generated if not given
58             # routes will default to undef and usually set from record-route header
59             # in response packets
60             # Returns: $self
61             ############################################################################
62             sub new {
63 51     51 1 232 my $class = shift;
64 51 50       912 my %args = @_ == 1 ? %{ shift(@_) } : @_;
  0         0  
65 51         286 my $self = fields::new( $class );
66 51         8577 %$self = %args;
67 51   66     1245 $self->{callid} ||= md5_hex( time(), rand(2**32) );
68 51   50     516 $self->{cseq} ||= 0;
69 51         148 $self->{_transactions} = [];
70 51         123 $self->{_cseq_incoming} = undef;
71              
72             # create tag on my side (to|from)
73 51 100       475 my $side = $self->{incoming} ? 'to':'from';
74 51         293 my ($data,$param) = sip_hdrval2parts( $side => $self->{$side} );
75 51 50       262 if ( my $tag = $param->{tag} ) {
76             # FIXME: what to do if local_tag was already set to different value?
77 0         0 $self->{local_tag} = $tag;
78             } else {
79             $self->{$side}.=";tag=".(
80 51         1400 $self->{local_tag} = md5_hex( time(), rand(2**32), $self->{$side} )
81             );
82             }
83              
84 51         561 DEBUG( 100,"CREATE context $self callid=$self->{callid}" );
85 51         293 return $self
86             }
87              
88             # destroying of fields in perl5.8 cleanup can cause strange errors, where
89             # it complains, that it cannot coerce array into hash. So use this function
90             # on your own risks and rename it to DETSTROY if you want to have debugging
91             # info
92             sub _DESTROY {
93 0     0   0 DEBUG( 100,"DESTROY context $_[0] callid=$_[0]->{callid}" );
94             }
95              
96             ############################################################################
97             # returns callid for context
98             # Args: $self
99             # Returns: $id
100             ############################################################################
101             sub callid {
102 295     295 1 658 my Net::SIP::Endpoint::Context $self = shift;
103 295         1576 return $self->{callid};
104             }
105              
106             ############################################################################
107             # get peer
108             # Args: $self
109             # Returns: $peer
110             # $peer: for incoming calls this is 'from', for outgoing 'to'
111             ############################################################################
112             sub peer {
113 0     0 1 0 my Net::SIP::Endpoint::Context $self = shift;
114 0 0       0 my $peer = $self->{incoming} ? $self->{from} : $self->{to};
115 0         0 my ($data) = sip_hdrval2parts( from => $peer ); # strip parameters like tag etc
116 0         0 return $data;
117             }
118              
119             ############################################################################
120             # return list of outstanding requests matching filter, if no filter is given
121             # returns all requests
122             # Args: ($self,%filter)
123             # %filter
124             # method => name: filter for requests with given method
125             # request => packet: filter for packet, e.g. finds if packet is outstanding
126             # Returns: @requests
127             # returns all matching requests (Net::SIP::Request objects), newest
128             # requests first
129             ############################################################################
130             sub find_outstanding_requests {
131 6     6 1 12 my Net::SIP::Endpoint::Context $self = shift;
132 6         110 my %filter = @_;
133 6 50       15 my @trans = @{$self->{_transactions}} or return;
  6         41  
134 6 50       50 if ( my $pkt = $filter{request} ) {
135 0 0       0 @trans = grep { $pkt == $_->{request} } @trans or return;
  0         0  
136             }
137 6 50       24 if ( my $method = $filter{method} ) {
138 6 50       18 @trans = grep { $method eq $_->{request}->method } @trans or return;
  6         50  
139             }
140 6         21 return map { $_->{request} } @trans;
  6         44  
141             }
142              
143             ############################################################################
144             # creates a new SIP request packet within this context
145             # Args: ($self,$method;$body,%args)
146             # $method: method for request, eg 'INVITE','BYE'...
147             # or already a Net::SIP::Request object
148             # $body: (optional) body for SIP packet
149             # %args: (optional) additional args given to Net::SIP::Request->new
150             # Returns: $request
151             # $request: Net::SIP::Request object
152             ############################################################################
153             sub new_request {
154 109     109 1 257 my Net::SIP::Endpoint::Context $self = shift;
155 109         384 my ($method,$body,%args) = @_;
156              
157 109         544 my ($leg,$dst_addr,$rsp40x) = delete @args{qw(leg dst_addr resp40x)};
158              
159 109         199 my $request;
160 109 100       402 if ( ref($method)) {
161             # already a request object
162 46         141 $request = $method;
163 46         184 $method = $request->method;
164              
165             } else {
166              
167             # increase cseq unless its explicitly specified
168             # the latter case is useful for ACK and CANCEL
169             # which need the same sequence number as the INVITE
170             # they belong to
171 63   33     715 my $cseq = delete $args{cseq} || ++$self->{cseq};
172              
173 63         378 $method = uc($method);
174 63         152 my $uri = delete $args{uri};
175             my ($to,$from) = $self->{incoming} ? ($self->{from},$self->{to})
176 63 50       365 : ($self->{to},$self->{from});
177 63 50       280 if ( !$uri ) {
178             $uri = $self->{remote_contact}
179 63   66     610 || (sip_hdrval2parts(to => $to))[0];
180             # XXX handle quotes right, e.g ""
181 63 100       476 $uri = $1 if $uri =~m{<(\S+)>$};
182             }
183              
184             # contact is mandatory for INVITE
185             # will be added within Leg
186              
187             $request = Net::SIP::Request->new(
188             $method, # Method
189             $uri, # URI
190             {
191             from => $from,
192             to => $to,
193             $self->{contact} ? ( contact => $self->{contact} ):(),
194             cseq => "$cseq $method",
195             'call-id' => $self->{callid},
196 63 50       2581 'max-forwards' => 70,
197             %args,
198             },
199             $body
200             );
201             }
202              
203             # overwrite any route header in request if we already learned a route
204 109 50       487 $request->set_header( route => $self->{route} ) if $self->{route};
205              
206 109 0 33     329 if ( $rsp40x and $self->{auth} and $request->authorize( $rsp40x, $self->{auth} )) {
      0        
207             # update local cseq
208 0         0 ($self->{cseq}) = $request->cseq =~m{(\d+)};
209             }
210              
211             # create new transaction
212             my %trans = (
213             tid => $request->tid,
214             request => $request,
215             callback => $self->{_callback},
216             # we need this to resent the request with authentication the same way
217 109         566 leg => $leg,
218             dst_addr => $dst_addr,
219             );
220 109         557 lock_keys(%trans);
221 109         1318 unshift @{ $self->{_transactions} }, \%trans; # put as first
  109         426  
222              
223 109         698 return $request;
224             }
225              
226             ############################################################################
227             # set callback for context
228             # Args: ($self,$cb)
229             # $cb: [ \&sub,@arg ]
230             # Returns: NONE
231             ############################################################################
232             sub set_callback {
233 101     101 1 311 my Net::SIP::Endpoint::Context $self = shift;
234 101         393 $self->{_callback} = shift;
235             }
236              
237             ############################################################################
238             # notify context that current delivery is permanently done (e.g successful
239             # or failed). On failure call current callback to notify upper layer about
240             # permanent failure of request
241             # This is used for errors from the transport layer, errors from the SIP
242             # layer (e.g response with 400 Bad request) are handled by handle_response()
243             # Args: ($self,$tid;$error)
244             # $tid: Transaction ID
245             # $error: errno if error occurred
246             # Returns: NONE
247             ############################################################################
248             sub request_delivery_done {
249 45     45 1 102 my Net::SIP::Endpoint::Context $self = shift;
250 45         127 my ($endpoint,$tid,$error) = @_;
251 45 50       203 return if ! $error; # notify of success once I get response
252              
253 0         0 my $trans = $self->{_transactions};
254 0         0 my @ntrans;
255 0         0 foreach my $tr (@$trans) {
256 0 0       0 if ( $tr->{tid} eq $tid ) {
257 0         0 $self->{_transactions} = \@ntrans;
258 0 0       0 if ( my $cb = $tr->{callback} ) {
259             # permanently failed
260 0         0 invoke_callback( $cb,$endpoint,$self,$error );
261             }
262             } else {
263 0         0 push @ntrans,$tr
264             }
265             }
266             }
267              
268             ############################################################################
269             # handle response packet for this context
270             # cseq of response must match the cseq of the current delivery!
271             # if there is no current delivery or the cseq does not match the response
272             # gets dropped
273             # Args: ($self,$response,$leg,$from,$endpoint)
274             # $response: incoming Net::SIP::Response packet
275             # $leg: Net::SIP::Leg through which the response came in
276             # $from: hash with information where response came in
277             # $endpoint: endpoint responsable for this context, used for redeliveries...
278             # Returns: NONE
279             ############################################################################
280             sub handle_response {
281 139     139 1 326 my Net::SIP::Endpoint::Context $self = shift;
282 139         429 my ($response,$leg,$from,$endpoint) = @_;
283              
284             # find and remove transaction because I got response for it
285             # if response does not terminates transaction one need to add
286             # it again
287 139         401 my $tid = $response->tid;
288 139         689 my $method = $response->method;
289 139         456 my $trans = $self->{_transactions};
290 139         330 my (@ntrans,$tr);
291 139         678 foreach my $t (@$trans) {
292 201 100 100     1854 if ( !$tr and $t->{tid} eq $tid and $method eq $t->{request}->method) {
      100        
293 137         356 $tr = $t;
294             } else {
295 64         189 push @ntrans,$t
296             }
297             }
298 139 100       366 $tr || do {
299             # no delivery pending
300 2         9 DEBUG( 10,"got response for unkown transaction. DROP" );
301 2         8 return;
302             };
303 137         320 $self->{_transactions} = \@ntrans;
304              
305 137         902 DEBUG( 10,"got response for transaction ".$tr->{request}->dump );
306              
307             # match response to client transaction, RFC3261 17.1.3
308             # check if the response came in through the same leg, where the
309             # request was send, e.g that the branch tag is the same
310 137 50       572 $leg->check_via( $response ) || do {
311 0         0 DEBUG( 10,"response came in through the wrong leg" );
312 0         0 return;
313             };
314              
315 137         343 my $cb = $tr->{callback};
316 137         503 my @arg = ($endpoint,$self);
317 137         480 my $code = $response->code;
318              
319             # for 300-699 an ACK must be created (RFC3261, 17.1.1.2)
320             # notification of upper layer will be done down in the method
321             # XXXXXXXXXXXXXX do we need to wait that the ACK was accepted
322             # XXXXXXXXXXXXXX before sending new request??
323             # XXXXXXXXXXXXXX (e.g for 401,407,302..)
324 137 100 100     1203 if ( $method eq 'INVITE' && $code>=300 ) {
325             # must create ACK
326 9         52 DEBUG( 50,"code=$code, must generate ACK" );
327 9         74 my $ack = $tr->{request}->create_ack( $response );
328 9         60 $endpoint->new_request( $ack,$self,undef,undef,leg => $leg);
329             }
330              
331             # transaction is not done
332 137 100       859 if ( $code =~m{^1\d\d} ) {
333 66         214 push @ntrans,$tr;
334              
335             # forward preliminary responses to INVITE to app
336             # ignore all other preliminary responses
337 66 50       223 if ( $method eq 'INVITE' ) {
338 66         430 invoke_callback($cb,@arg,0,$code,$response,$leg,$from);
339             }
340 66         445 return;
341             }
342              
343             # Authorization required
344 71 100 66     1017 if ( $code == 401 || $code == 407 ) {
345 3         9 my $r = $tr->{request};
346 3         9 my $auth = $self->{auth};
347 3 50 33     68 if ( $auth && $r->authorize( $response, $auth )) {
348 3         16 DEBUG(10,"retrying with authorization");
349             # found something to authorize
350             # redo request
351             # update local cseq from cseq in request
352 3         11 ($self->{cseq}) = $r->cseq =~m{(\d+)};
353             $endpoint->new_request($r, $self, undef, undef,
354 3         26 leg => $tr->{leg}, dst_addr => $tr->{dst_addr});
355             } else {
356             # need user feedback
357 0         0 DEBUG(10,"no (usable) authorization data available");
358 0         0 invoke_callback($cb,@arg,EPERM,$code,$response,$leg,$from);
359             }
360 3         23 return;
361             }
362              
363             # Don't care about the response for a CANCEL or a BYE
364             # because this connection close is issued by this side
365             # and no matter what the peer wants the call be will closed
366             # But invoke callback to notify upper layer
367 68 100 100     751 if ( $method eq 'CANCEL' or $method eq 'BYE' ) {
368 33         195 invoke_callback($cb,@arg,0,$code,$response,$leg,$from);
369             # close context only for BYE,
370             # for CANCEL we will close the context on receiving the
371             # response and sending the ACK
372 33 100       239 $endpoint->close_context( $self ) if $method eq 'BYE';
373 33         1632 return;
374             }
375              
376             # final response in non-dialog (only INVITE can create dialog)
377 35 0 0     206 if ( $self->{method} ne 'INVITE' and
      33        
378             ($code>=200 and $code<300 or $code>=400)) {
379 0         0 $endpoint->close_context($self);
380             }
381              
382 35 100 33     403 if ( $code =~m{^2\d\d} ) {
    50          
    50          
    50          
383             # 2xx OK
384              
385 28 50       109 if ( $method eq 'INVITE' ) {
386             # is response to INVITE, create ACK
387             # and propagate to upper layer
388 28         120 my $req = $tr->{request};
389              
390             # extract route information on INVITE, but not on re-INVITE
391             # we assume, that it is a re-INVITE, if we have a remote_contact
392             # already
393 28 50 66     456 if ( ! $self->{remote_contact}
394             and my @route = $response->get_header( 'record-route' )) {
395 0         0 $self->{route} = [ reverse @route ];
396             }
397              
398             # 12.1.2 - set URI for dialog to contact given in response which
399             # establishes the dialog
400 28 50       118 if ( my $contact = $response->get_header( 'contact' )) {
401 28 50       142 $contact = $1 if $contact =~m{<(\w+:[^>\s]+)>};
402 28         86 $self->{remote_contact} = $contact;
403 28         168 $req->set_uri( $contact );
404              
405             }
406              
407             # use to-tag from this request to update 'to'
408             # FIXME: this should probably be better done by the upper layer
409             # which decides, which call to accept (in case of call-forking with
410             # multiple 2xx responses)
411 28 50       258 $self->{to} = $response->get_header( 'to' ) if ! $self->{incoming};
412              
413             # create ACK
414             # if 2xx response changed contact use it as the new URI
415 28         362 my $ack = $req->create_ack( $response );
416 28         186 invoke_callback($cb,@arg,0,$code,$response,$leg,$from,$ack);
417 28         252 $endpoint->new_request( $ack,$self,undef,undef,leg => $leg);
418              
419              
420             } else {
421             # response to ACK, REGISTER...
422             # simply propagate to upper layer, only INVITE needs
423             # special handling
424 0         0 invoke_callback($cb,@arg,0,$code,$response,$leg,$from);
425             }
426              
427             } elsif ( $code == 300 || $code == 301 ) {
428             # need user feedback in these cases
429             # 21.3.1 300 multiple choices
430             # 21.3.2 301 moved permanently
431 0         0 invoke_callback($cb,@arg,EFAULT,$code,$response,$leg,$from);
432              
433             } elsif ( $code == 302 ) {
434             # 21.3.3 302 moved temporarily
435             # redo request and insert request again
436 0         0 my $contact = $self->{to} = $response->get_header( 'contact' );
437 0 0       0 $contact = $1 if $contact =~m{<(\w+:[^>\s]+)>};
438 0         0 $self->{remote_contact} = $contact;
439 0         0 ( my $r = $tr->{request} )->set_uri( $contact );
440 0         0 $r->set_cseq( ++$self->{cseq} );
441 0         0 $endpoint->new_request( $r,$self );
442              
443             } elsif ( $code == 305 ) {
444             # 21.3.4 305 use proxy
445             # set proxy as the first route and insert request again
446 0   0     0 my $route = $self->{route} ||= [];
447 0         0 unshift @$route,$response->get_header( 'contact' );
448 0         0 ( my $r = $tr->{request} )->set_header( route => $route );
449 0         0 $r->set_cseq( ++$self->{cseq} );
450 0         0 $endpoint->new_request( $r,$self );
451              
452             } else {
453             # some kind of unrecoverable error
454 7         84 invoke_callback($cb,@arg,EINVAL,$code,$response,$leg,$from);
455             }
456             }
457              
458             ############################################################################
459             # handle incoming request
460             # Args: ($self,$request,$leg,$endpoint)
461             # $request: incoming Net::SIP::Request packet
462             # $leg: Net::SIP::Leg through which the request came in
463             # $from: ip:port where request came in
464             # $endpoint: endpoint responsable for this context, used for responses...
465             # Returns: NONE
466             # Comment: only new requests will be delivered to this method, because the dispatcher
467             # cares about retransmits, eg requests for which I issued already a response
468             # within the last 64*T1
469             ############################################################################
470             sub handle_request {
471 51     51 1 157 my Net::SIP::Endpoint::Context $self = shift;
472 51         180 my ($request,$leg,$from,$endpoint) = @_;
473              
474 51         312 my $cseq = $request->cseq;
475 51         583 my ($cseq_num) = $cseq=~m{^(\d+)};
476              
477             DEBUG( 100,"method=%s cseq=%s/%s inc=%s", $request->method, $cseq_num,$cseq,
478 51 100       278 defined($self->{_cseq_incoming}) ? $self->{_cseq_incoming} : '' );
479 51 50 66     635 if ( defined $self->{_cseq_incoming}
480             and $cseq_num < $self->{_cseq_incoming} ) {
481             # must be an retransmit of an really old request, drop
482 0         0 DEBUG( 10,"retransmit of really old request? Dropping" );
483 0         0 return;
484             }
485              
486             # check with last request in transaction
487 51         145 my $ctx_is_new;
488 51 100       256 if ( my $trans = $self->{_last_transreq} ) {
489 33         173 my $last_cseq = $trans->cseq;
490 33 50       150 if ( $last_cseq eq $cseq ) {
491 0         0 DEBUG( 10,"retransmit of last request. DROP" );
492 0         0 return;
493             }
494             } else {
495 18         73 $ctx_is_new = 1;
496             }
497 51         148 $self->{_last_transreq} = $request;
498              
499 51         201 my $method = $request->method;
500              
501 51 100 100     555 if ( $method eq 'ACK' || $method eq 'CANCEL' ) {
502             # must be have same cseq_num as last request, otherwise drop
503 19 50 33     451 if ( defined $self->{_cseq_incoming}
504             and $cseq_num != $self->{_cseq_incoming} ) {
505 0         0 DEBUG( 10,"received $method for unreceived INVITE: $cseq_num|$self->{_cseq_incoming}" );
506 0         0 return;
507             }
508             } else {
509             # cannot have the same cseq_num as last request
510 32 50 66     303 if ( defined $self->{_cseq_incoming}
511             and $cseq_num == $self->{_cseq_incoming} ) {
512 0         0 DEBUG( 10,"reused cseq for $method. DROP" );
513 0         0 return;
514             }
515             }
516 51         178 $self->{_cseq_incoming} = $cseq_num;
517              
518 51   33     233 my $cb = $self->{_callback} || do {
519             DEBUG( 50,"no callback at context!" );
520             return;
521             };
522 51         187 my @arg = ($endpoint,$self);
523              
524             # extract route information for future requests to the UAC (re-invites)
525             # only for INVITE (rfc3261,12.1.1)
526 51 50 66     593 if ( $ctx_is_new and $method eq 'INVITE' and
      66        
527             my @route = $request->get_header( 'record-route' )) {
528 0         0 $self->{route} = \@route;
529             }
530              
531             {
532             # check if to has already a (my) tag, if not add it to request,
533             # so that it gets added to responses
534 51         121 my $to = $request->get_header( 'to' );
  51         188  
535 51         246 my ($data,$param) = sip_hdrval2parts( to => $to );
536 51 100       252 if ( ! $param->{tag} ) {
537 19         95 DEBUG( 50,"added my tag to to header in request" );
538 19         168 $param->{tag} = $self->{local_tag};
539 19         205 $to = sip_parts2hdrval( 'to',$data,$param );
540 19         195 $request->set_header( to => $to );
541             }
542             }
543              
544 51 100 100     596 if ( $method eq 'BYE' || $method eq 'CANCEL' ) {
545             # if the peer wants to hangup we must confirm
546 16         112 my $response = $request->create_response( '200','Closing' );
547 16         135 $endpoint->new_response( $self,$response,$leg,$from );
548              
549             # invoke callback before closing context, so that we have more
550             # information about the current call
551 16         108 invoke_callback($cb,@arg,0,0,$request,$leg,$from);
552              
553 16 100       1938 if ( $method eq 'CANCEL' ) {
554             # must create 487 Request canceled
555 3         28 my $response = $request->create_response( '487','Request canceled' );
556 3   33     23 $response->set_header(
557             cseq => $response->cseq =~m{(\d+)} && "$1 INVITE" );
558 3         51 DEBUG(10,"send response: ".$response->dump(1));
559 3         14 $endpoint->new_response($self,$response,$leg,$from);
560             }
561              
562 16         132 $endpoint->close_context($self);
563 16         495 return;
564             }
565              
566             # If new INVITE, send 100 Trying
567 35 100       176 if ( $method eq 'INVITE' ) {
568 19         285 my $response = $request->create_response( '100','Trying' );
569 19         205 $endpoint->new_response( $self,$response,$leg,$from );
570             }
571              
572              
573             # propagate to upper layer, which needs
574             # - for INVITE send 180 Ringing periodically and after some time a final response
575             # - for ACK to establish the call
576             # - BYE|CANCEL is already handled above
577             # - for everything else to handle the Option fully, eg issue final response..
578              
579 35         212 invoke_callback($cb,@arg,0,0,$request,$leg,$from);
580             }
581              
582             1;