package Net::RTP;

################
#
# Net::RTP: Pure Perl Real-time Transport Protocol (RFC3550)
#
# Nicholas J Humfrey
# njh@cpan.org
#

use Net::RTP::Packet;
use Socket;
use strict;
use Carp;



# Use whatever Superclass we can find first
# we would prefer to have a multicast socket...
BEGIN {
	my @superclasses = (
		'IO::Socket::Multicast6 0.02',
		'IO::Socket::Multicast 1.00',
		'IO::Socket::INET6 2.51',
		'IO::Socket::INET 1.20',
	);
	
	our $SUPER_CLASS = undef;
	foreach my $super (@superclasses) {
		eval "use $super";
		unless ($@) {
			($SUPER_CLASS) = ($super =~ /^([\w:]+)/);
			last;
		}
	}
	
	unless (defined $SUPER_CLASS) {
		die "Failed to load any of super classes.";
	}
	
	
	# Check to see if Socket6 is available
	our $HAVE_SOCKET6 = 0;
	eval "use Socket6 qw/ AF_INET6 unpack_sockaddr_in6 inet_ntop /;";
	$HAVE_SOCKET6=1 unless ($@);
}



use vars qw/$VERSION @ISA $SUPER_CLASS $HAVE_SOCKET6/;
@ISA = ($SUPER_CLASS);
$VERSION="0.09";




sub new {
    my $class = shift;
	unshift @_,(Proto => 'udp') unless @_;
	return $class->SUPER::new(@_);
}


sub configure {
	my($self,$arg) = @_;
	
	# Default to UDP instead of TCP
	$arg->{Proto} ||= 'udp';
	$arg->{ReuseAddr} ||= 1;
	my $result = $self->SUPER::configure($arg);

	
	if (defined $result) {	
		# Join group if it a multicast IP address
		my $group = $self->sockhost();
		if (_is_multicast_ip($group)) {
			if ($self->superclass() =~ /Multicast/) {
				#print "Joining group: $group\n";
				$self->mcast_add( $group ) || croak "Failed to join multicast group";
			} else {
				croak "Error: can't receive multicast without either ".
					  "IO::Socket::Multicast or IO::Socket::Multicast6 installed.";
			}
		} 
	}
	
	return $result;
}


sub _is_multicast_ip {
	my ($group) = @_;
	
	return 0 unless (defined $group);
	
	# IPv4 multicast address ?
	if ($group =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		return 1 if ($1 >= 224 and $1 <= 239);
		
	# IPv6 multicast address ?
	} elsif ($group =~ /^ff[0-9a-f]{2}\:/i) {
		return 1;
	}

	# Not an multicast IP
	return 0;
}


sub superclass {
	return $SUPER_CLASS;
}


sub recv {
	my $self=shift;
	my ($size) = @_;
	
	# Default read size
	$size = 2048 unless (defined $size);
	
	# Receive a binary packet
	my $data = undef;
	my $sockaddr_in = $self->SUPER::recv($data, $size);
	if (defined $data and $data ne '') {
	
		# Parse the packet
		my $packet = new Net::RTP::Packet( $data );
		
		# Store the source address
		if ($sockaddr_in ne '' and defined $packet)
		{
			if ($self->sockdomain() == &AF_INET) {
				my ($port,$addr) = unpack_sockaddr_in($sockaddr_in);
				$packet->{'source_ip'} = inet_ntoa($addr);
				$packet->{'source_port'} = $port;
				
			} elsif ($HAVE_SOCKET6) {
				eval {
					if ($self->sockdomain() == &AF_INET6) {
						my ($port,$addr) = unpack_sockaddr_in6($sockaddr_in);
						$packet->{'source_ip'} = inet_ntop(&AF_INET6, $addr);
						$packet->{'source_port'} = $port;
					}
				};
			}
			
			# Failed to decode socket address ?
			unless (defined $packet->{'source_ip'}) {
				warn "Failed to get socket address for family: ".$self->sockdomain();
			}
		}
		
		return $packet;
	}
	
	return undef;
}


sub send {
	my $self=shift;
	my ($packet) = @_;
	
	if (!defined $packet or ref($packet) ne 'Net::RTP::Packet') {
		croak "Net::RTP->send() takes a Net::RTP::Packet as its only argument";
	}
	
	# Build packet and send it
	my $data = $packet->encode();
	return $self->SUPER::send($data);
}


sub DESTROY {
    my $self=shift;
	return $self->SUPER::DESTROY(@_);
}



1;

__END__

=pod

=head1 NAME

Net::RTP - Send and receive RTP packets (RFC3550)

=head1 SYNOPSIS

  use Net::RTP;

  my $rtp = new Net::RTP( LocalPort=>5170, LocalAddr=>'233.122.227.171' );
  
  my $packet = $rtp->recv();
  print "Payload type: ".$packet->payload_type()."\n";
  

=head1 DESCRIPTION

The C<Net::RTP> module subclasses L<IO::Socket::Multicast6> to enable
you to manipulate multicast groups. The multicast additions are 
optional, so you may also send and recieve unicast packets.

=over

=item $rtp = new Net::RTP( [LocalAdrr=>$addr, LocalPort=>$port,...] )

The new() method is the constructor for the Net::RTP class. 
It takes the same arguments as L<IO::Socket::INET>, however 
the B<Proto> argument defaults to "udp", which is more appropriate for RTP.

The Net::RTP super-class used will depend on what is available on your system
it will try and use one of the following (in order of preference) :

	IO::Socket::Multicast6 (IPv4 and IPv6 unicast and multicast)
	IO::Socket::Multicast (IPv4 unicast and multicast)
	IO::Socket::INET6 (IPv4 and IPv6 unicast)
	IO::Socket::INET (IPv4 unicast)

If LocalAddr looks like a multicast address, then Net::RTP will automatically 
try and join that multicast group for you.


=item my $packet = $rtp->recv( [$size] )

Blocks and waits for an RTP packet to arrive on the UDP socket.
The read C<$size> defaults to 2048 which is usually big enough to read
an entire RTP packet (as it is advisable that packets are less than 
the Ethernet MTU).

Returns a C<Net::RTP::Packet> object or B<undef> if there is a problem.


=item $rtp->send( $packet )

Send a L<Net::RTP::Packet> from out of the RTP socket. 
The B<PeerPort> and B<PeerAddr> should be defined in order to send packets. 
Returns the number of bytes sent, or the undefined value if there is an error.

=item $rtp->superclass()

Returns the name of the super-class that Net::RTP chose to use.

=back


=head1 SEE ALSO

L<Net::RTP::Packet>

L<IO::Socket::Multicast6>

L<IO::Socket::INET6>

L<IO::Socket::Multicast>

L<IO::Socket::INET>

L<http://www.ietf.org/rfc/rfc3550.txt>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-net-rtp@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org>.  I will be notified, and then you will automatically
be notified of progress on your bug as I make changes.

=head1 AUTHOR

Nicholas J Humfrey, njh@cpan.org

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 University of Southampton

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself, either Perl version 5.005 or, at
your option, any later version of Perl 5 you may have available.


=cut


syntax highlighted by Code2HTML, v. 0.9.1