 package Net::Shadowsocks::Server;

    # ABSTRACT: Shadowsocks protocol server module.
    use AnyEvent;
    use AnyEvent::Handle;
    use AnyEvent::Socket;
    use Carp;
    use Config;
    use Digest::MD5;
    use Digest::SHA;
    use Net::Shadowsocks;
    use Net::Shadowsocks qw(_EVP_BytesToKey _initialize_cipher);
    use Socket qw(IPPROTO_TCP TCP_FASTOPEN);


our $VERSION = '0.7.0';

    sub new($$$$$$)
     {
        my $_osname = $Config{osname};
        my $_osvers = $Config{osvers};
        AE::log info => "Shadowsocks server starting up on $_osname $_osvers";
        my ( $class, %args ) = @_;
        my $self = bless {
            map { ( $_ => $args{$_} ) }
              qw(local_address local_port password server server_port method),
        }, $class;
        if ( $self->{server} eq 'localhost' )
         {
            undef $self->{server};
        }

        my $tcp_server;
        $tcp_server = AnyEvent::Socket::tcp_server
        (
            $self->{server},
            $self->{server_port},
            sub {
                my ( $client_socket, $client_host, $client_port ) = @_;

                AE::log info =>
                  "Got new client connection: $client_host:$client_port";
                my $sent_iv = 0;
                my $client_iv;
                my $mode = 0;

                my ( $encryptor, $decryptor, $key, $iv,$nonce ) = Net::Shadowsocks::_initialize_cipher( $self->{method},$self->{password} );
                carp length($iv);
                my $clienthandler;
                my $remotehandler;
                $clienthandler = AnyEvent::Handle->new
                (
                    autocork  => 1,
                    keepalive => 1,
                    no_delay  => 1,
                    fh        => $client_socket,
                    on_eof    => sub 
                    {
                        my $chandler = shift;
                        AE::log info => "Client: Done.";
                        $chandler->destroy();
                    },
                    on_error => sub 
                    {
                        my ( $chandler, $fatal, $msg ) = @_;
                        AE::log error => $msg;
                        $chandler->destroy();
                    },
                    on_read => sub 
                    {
                        my $client_buffer = $clienthandler->rbuf();

                        if ( $client_buffer eq '' ) 
                        {
                            return;
                        }
                        else 
                        {
                            my $incomingdata = $client_buffer;
                            #carp $incomingdata;
                            my $decrypteddata;
                            if ( !defined($client_iv) ) 
                            {
                                    $client_iv = substr( $client_buffer, 0, length($iv) );
                                    $incomingdata = substr( $client_buffer, length($iv) );
                                    #carp $incomingdata;

                                    if ( $self->{method} eq 'rc4-md5' ) 
                                    {
                                        my $md = Digest::MD5->new();
                                        $md->add($key .$client_iv );
                                        #$md->add($client_iv);
                                        my $decrypt_rc4_key = $md->digest();
                                        carp $decrypt_rc4_key;
                                        Mcrypt::mcrypt_init($decryptor, $decrypt_rc4_key, '' );
                                    }
                                    elsif($self->{method} eq 'rc4-sha')
                                    {
                                        my $sha = Digest::SHA->new();
                                        $sha->add($key .$client_iv );
                                        #$md->add($client_iv);
                                        my $decrypt_rc4_key = substr($sha->digest(),0,16);
                                        carp length($decrypt_rc4_key);
                                        Mcrypt::mcrypt_init($decryptor, $decrypt_rc4_key, '' );
                                        }
                                    if ($self->{method} eq 'rc6')
                                    {
                                    $decrypteddata = $decryptor->decrypt($incomingdata);
                                        }
                                        else 
                                    {
                                        Mcrypt::mcrypt_init($decryptor, $key, $client_iv );
                                    $decrypteddata = Mcrypt::mcrypt_decrypt($decryptor, $incomingdata );
                                    #carp $decrypteddata;
                                     }                                
                                my $addrtype = ord( substr( $decrypteddata, 0, 1 ) );
                                carp $addrtype;
                                if (    $addrtype != 1 and $addrtype != 3 and $addrtype != 4 )
                                {
                                    carp ' addrtype not supported ';
                                    return;
                                }
                                my $dest_addr;
                                my $dest_port;

                                if ( $addrtype == 1 ) 
                                {
                                    if ( length($decrypteddata) >= 7 ) 
                                    {
                                        $dest_addr = format_address(substr( $decrypteddata, 1, 4 ) );
                                        $dest_port = unpack( ' n ',substr( $decrypteddata, 5, 2 ) );
                                        $decrypteddata = substr( $decrypteddata, 7 );
                                    }
                                    else 
                                    {
                                        last;
                                    }
                                }
                                elsif ( $addrtype == 3 ) 
                                {
                                    if ( length($decrypteddata) > 2 ) 
                                    {
                                        my $addr_len = ord( substr( $decrypteddata, 1, 1 ) );
                                        if (length($decrypteddata) >= 2 + $addr_len )
                                        {
                                            $dest_addr = substr( $decrypteddata, 2,$addr_len );
                                            $dest_port = unpack('n',substr($decrypteddata,2 + $addr_len,2));
                                            $decrypteddata = substr( $decrypteddata,4 + $addr_len );
                                        }
                                        else 
                                        {
                                            last;
                                        }
                                    }
                                    else 
                                    {
                                        last;
                                    }
                                }
                                elsif ( $addrtype == 4 ) 
                                {
                                    if ( length($decrypteddata) >= 19 ) 
                                    {
                                        $dest_addr = format_address(substr( $decrypteddata, 1, 16 ) );
                                        $dest_port = unpack( "n", substr( $decrypteddata, 17, 2 ) );
                                        $decrypteddata = substr( $decrypteddata, 19 );
                                    }
                                    else 
                                    {
                                        last;
                                    }
                                }

                                if ( $dest_addr eq '' ) 
                                {
                                    last;
                                }
                                $remotehandler = AnyEvent::Handle->new
                                (
                                    autocork   => 1,
                                    keepalive  => 1,
                                    no_delay   => 1,
                                    connect    => [ $dest_addr, $dest_port ],
                                    on_connect => sub 
                                    {
                                        my ($rhandle,  $peerhost,$peerport, $retry) = @_;

                                        unless ($rhandle) 
                                        {
                                            carp "couldn' t connect : $! ";
                                            return;
                                        }
                                    },
                                    on_eof => sub 
                                    {
                                        my $rhandler = shift;
                                        AE::log info => " Remote : Done . ";
                                        $mode = 0;
                                        $rhandler->destroy();
                                    },
                                    on_error => sub 
                                    {
                                        my ( $rhandler, $fatal, $msg ) = @_;
                                        AE::log error => $msg;
                                        $mode = 0;
                                        $rhandler->destroy();
                                    },
                                    on_read => sub 
                                    {
                                        my $remote_buffer = $remotehandler->rbuf();
                                        my $plaindata = $remote_buffer;
                                        my $encrypteddata;
                                        if ( $sent_iv == 0 ) 
                                        {

                                            if ( $self->{method} eq 'rc4-md5' )
                                            {
                                                my $md = Digest::MD5->new();
                                                $md->add($key . $iv);
                                                #$md->add($iv);
                                                my $encrypt_rc4_key = $md->digest();
                                                Mcrypt::mcrypt_init($encryptor,$encrypt_rc4_key, '' );
                                            }
                                            elsif($self->{method} eq 'rc4-sha' )
                                            {
                                                my $sha = Digest::SHA->new();
                                                $sha->add($key . $iv);
                                                #$md->add($iv);
                                                my $encrypt_rc4_key = substr($sha->digest(),0,16);
                                                Mcrypt::mcrypt_init($encryptor,$encrypt_rc4_key, '' );
                                                }
                                            if ($self->{method} eq 'rc6')
                                            {
                                                $encrypteddata = $encryptor->encrypt($plaindata,$key,$iv); 
                                                }
                                                else 
                                            {
                                                Mcrypt::mcrypt_init($encryptor, $key, $iv );
                                                $encrypteddata = Mcrypt::mcrypt_encrypt($encryptor, $plaindata );
                                            }
                                            $sent_iv = 1;
                                        }
                                        my $datatosend;
                                        if ( $mode == 0 ) 
                                        {
                                            $datatosend = $iv . $encrypteddata;
                                            $mode       = 1;
                                        }
                                        else 
                                        {
                                            $datatosend = $encrypteddata;
                                        }
                                        $clienthandler->push_write($datatosend);
                                        $remotehandler->{rbuf} = '';
                                    }
                                );
                            }
                            else 
                            {
                                if ( $self->{method} eq 'rc6' ) 
                                {
                                    $decrypteddata = $decryptor->decrypt($incomingdata,$key,$client_iv);
                                }
                                else 
                                {
                                    $decrypteddata = Mcrypt::mcrypt_decrypt($decryptor, $incomingdata );
                                }
                            }
                            $remotehandler->push_write($decrypteddata);
                            $clienthandler->{rbuf} = '';
                        }
                    }
                );
            },
            sub 
            {
                my $fh = shift;
                if ( $_osname eq 'linux' ) 
                {
                    my $_tfo = do 
                    {
                        local ( @ARGV, $/ ) = '/proc/sys/net/ipv4/tcp_fastopen';<>;
                    };
                    if ( $_tfo eq '2' or $_tfo eq '3' ) 
                    {
                        setsockopt( $fh, IPPROTO_TCP, TCP_FASTOPEN, 1 );
                        AE::log info => "TCP Fast Open enabled on server.";
                    }
                }
                elsif ( $_osname eq 'darwin' ) 
                {
                    my $_version_major = substr( $_osvers, 0, index( $_osvers, '.' ) );
                    if ( $_version_major >= 15 ) 
                    {
                        setsockopt( $fh, IPPROTO_TCP, TCP_FASTOPEN, 1 );
                        AE::log info => "TCP Fast Open enabled on server.";
                    }
                }
            },
        );
        my $cv = AE::cv;
        $cv->recv();
        return $self;
    }
    1;    # End of Net::Shadowsocks::Server

__END__

=pod

=encoding UTF-8

=head1 NAME

Net::Shadowsocks::Server - Shadowsocks protocol server module.

=head1 VERSION

version 0.7.0

=head1 SYNOPSIS

    Shadowsocks protocol server module.
    
    The following encryption methods are supported:

rc4-md5 rc4-sha aes-128-cbc aes-128-cfb aes-128-ofb aes-256-cbc aes-256-cfb aes-256-ofb

Experimental support of RC6 is also provided.

Experimental server TCP Fast Open is also supported if available on Mac OS X and Linux. 

Please also note that both the stock libmcrypt and stock Mcrypt module do not export CTR 
mode, So the aes-128-ctr and aes-256-ctr method will not work out of box. You have to use this 
  patched version of libmcrypt and Libmcrypt module from https://osdn.net/projects/mcrypt/
to get it to work.  

=head1 NAME

    Net::Shadowsocks::Server
=head1 VERSION

Version 0.7.0

=head1 METHODS

    =cut

    =pod

=head2 new

    The C<new> constructor lets you create a new B<Net::Shadowsocks::Server> object.

    So no big surprises there...

    Returns a new B<Net::Shadowsocks::Server> or dies on error.

    example use:

    use Net::Shadowsocks::Server;

    my $foo = Net::Shadowsocks::Server->new(
    local_address => ' localhost ',
    local_port => 1491,
    password => ' 49923641 ',
    server => ' jp . ssip . club ',
    server_port => 23333,
    method => 'rc6',
    );

    This is all you need to do. Take a look at server.pl under eg directory for a compelete example on how to
    use the server module.

=head1 AUTHOR

Li ZHOU <lzh@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Li ZHOU.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
