#!perl
use POE qw(Component::Server::TCP Filter::Stackable Filter::Block);
use YuGiOh::Filter;
use YuGiOh::Filter::Stackable;
#use YuGiOh::Constants; #For the sake of posting it on PerlMonks, I'll just dump any important constants below
use Digest::MD5;
use constant {
PORT => 1001,
ACCEPT => 1,
FAIL => 0,
MSG_SPEC => 2,
MSG_ANNOUNCE => 3,
MSG_LOGIN => 4,
MSG_CHATSND => 5,
MSG_PRIVSND => 6, #UIDOFTARGET followed by MESSAGE, when resent, replaced with UIDOFSENDER and MESSAGE
MSG_CHATLIST => 7, #UIDName resends to increase list
MSG_LEFTCHAT => 8, #UID
MSG_REFRESH => 9, #Message field is ignored
#Syntax for initiated challenge, replies the reverse, check vs a stored challenge list.
MSG_CHALLENGE => 10, #UID TO CHALLENGE
MSG_CHLCANCEL => 11,
#Greeting
MSG_GREETING => 28,
};
use vars qw(%users @chars $slt);
#$slt should not be referenced outside of the salt function, declared here for memory allocation optimization
use strict;
@chars = ( 0 .. 9, 'a' .. 'z', 'A' .. 'Z', '#', ',',
qw(~ ! @ $ % ^ & * ( ) _ + = - { } | : " < > ? / . ' ; ] [ \ `));
$slt = ' ' x 1000;
#Used for including nested messages
POE::Component::Server::TCP->new(
Alias => 'master',
Port => PORT,
InlineStates => {
'send' => \&handle_send,
#Events
MSG_LOGIN => \&e_login,
MSG_CHATSND => \&e_chat,
MSG_PRIVSND => \&e_pchat,
MSG_REFRESH => \&e_rfrsh,
},
ClientConnected => \&client_connected,
ClientError => \&client_error,
ClientDisconnected => \&client_disconnected,
ClientInput => \&client_input,
ClientFilter => ['YuGiOh::Filter::Stackable', Filters => ['POE::Filter::Block','YuGiOh::Filter']],
);
$poe_kernel->run();
exit 0;
#Server related functions
sub handle_send{
#my ($heap,$message) = @_[HEAP,ARG0];
warn("Sending!"); #Debugging message
$_[HEAP]->{client}->put(@_[ARG0..$#_]);
warn("Sent!");
#$heap->{client}->put($message);
}
sub client_connected {
my $id = $_[SESSION]->ID;
my $ary = [];
$ary->[CHAL_BY] = [];
$users{$id} = $ary;
warn("$id conected!");
$poe_kernel->post($id, 'send' => [MSG_GREETING,$ary->[SALT] = salt()]);
}
sub client_error {
$_[KERNEL]->yield("shutdown");
}
sub client_disconnected {
my $id = $_[SESSION]->ID;
print "$id disconnected\n";
#Clean up other user's challenges
foreach my $usr (@{ $users{$id}[CHAL_BY] }){
$users{$usr}[CHALLENGED] = '';
}
delete($users{$id});
broadcast([MSG_LEFTCHAT,$id]);
}
sub client_input {
my($id,$input) = ($_[SESSION]->ID,$_[ARG0]);
#Ensure that the version has been sent
warn("Input: $input");
unless($users{$id}[VERSION] || $input->[0] == MSG_GREETING){
#If this is ever called, assume they are cheating us...
$poe_kernel->post($id, send => passfail(FAIL,$input,'Invalid Call and Response'));
$poe_kernel->post($id,'shutdown');
}
}
#Utility Functions
#Pass this the original message[,error text]
sub broadcast {
foreach my $id (keys %users){
$poe_kernel->post($id,send => $_[0]);
}
}
#Forces login, returns false when not logged in and broadcasts not logged in error
sub login{
my $id = $_[0][SESSION]->ID;
if($users{$id}){
return 1;
}else{
$_[SESSION]->yield([FAIL,passfail($_[ARG0],'User not logged in!')]);
return;
}
}
sub passfail {
return [$_[0],sprintf('%03d',$_[1][0]).$_[2]];
}
sub salt {
my $len = int(rand(1000));
$slt = '';
for(1..$len){
$slt .= $chars[int(rand($#chars))];
}
return $slt;
}
#Events
sub e_challenge {
my($id,$targ) = ($_[SESSION]->ID,$_[ARG0][1]);
return unless(login(\@_));
unless($users{$targ}){
$_[SESSION]->yield('send' => [FAIL,passfail($_[ARG0],'Unable to locate target.')]);
return;
}
$_[SESSION]->yield('send' => [ACCEPT,passfail($_[ARG0])]);
my $usr = $users{$id};
if($usr->[CHALLENGED]){
$poe_kernel->post($usr->[CHALLENGED], 'send' => [MSG_CHLCANCEL,$id]);
}
if($users{$targ}[CHALLENGED] == $id){
#Battle Time
}else{
$usr->[CHALLENGED] = $targ;
push(@{ $users{$targ}[CHAL_BY] },$id);
$poe_kernel->post($id, 'send' => [MSG_CHALLENGE,$id]);
}
}
sub e_chat {
my($id,$text) = ($_[SESSION]->ID,$_[ARG0][1]);
return unless(login(\@_));
broadcast([MSG_CHATSND,$id."\0".$text]);
$_[SESSION]->yield('send' => [ACCEPT,passfail($_[ARG0])]);
}
sub e_login {
my($id,$text) = ($_[SESSION]->ID,$_[ARG0][1]);
warn();
my($login,$pass) = split(/\0/,$text,1);
#do db lookup & pass comparison
$users{$id}[NAME] = $login;
#Remember to set UID
$_[SESSION]->yield('send' => [ACCEPT,passfail($_[ARG0],$id)]); #Assume sucess until database finalized
broadcast([MSG_CHATLIST,$id."\0".$login]);
$_[SESSION]->yield(MSG_REFRESH);
}
sub e_pchat {
my($id,$txt) = ($_[SESSION]->ID,$_[ARG0][1]);
return unless(login(\@_));
my($dest,$msg) = split(/\0/,$txt,1);
if($users{int($dest)}){
$poe_kernel->post($dest, 'send' => [MSG_PRIVSND,$id."\0".$msg]);
$_[SESSION]->yield('send' => [ACCEPT,passfail($_[ARG0])]);
}else{
$poe_kernel->yield('send' => [FAIL,passfail($_[ARG0],'Unable to locate destination user.')]);
}
}
sub e_rfrsh {
my $s = $_[SESSION];
#might want to check for login... but...
#Might want to re-implement this so this array-ref is a const, rather than re-create each time
$s->yield('send',[MSG_REFRESH],map({ [MSG_CHATLIST,$_."\0".$users{$_}[NAME]] } keys(%users)));
}
sub e_version {
my($id,$version) = ($_[SESSION]->ID,$_[ARG0][1]);
warn();
#Compare with newest acceptable version, possibly alerting that a newer version is available
$users{$id}[VERSION] = $version; #Why we store it I don't know... yet... if I can't find a reason I'll make it a bool
$_[SESSION]->yield('send' => [ACCEPT,passfail($_[ARG0])]);
}
####
package YuGiOh::Filter;
use Data::Dumper;
use strict;
#THIS FILTER SHOULD ONLY BE USED WHEN CHAINED WITH THE BLOCK FILTER THROUGH THE STACKABLE FILTER
sub new {
my $self = [];
return bless($self,'YuGiOh::Filter');
}
sub get {
my $self = shift;
$self->get_one_start(@_); #Append needed/available info
my @list;
while(my $record = $self->parse()){
push(@list,$record);
}
return \@list;
}
sub get_one_start {
push(@{$_[0]},@{$_[1]});
}
sub get_one {
my $self = shift;
my $record = $self->parse();
return $record ? [$record] : [];
}
sub parse {
my $self = shift;
my $txt = shift(@$self);
return [substr($txt,0,3,''),$txt];
}
sub put {
#warn("In the custom put");
my $tmp = [map({ sprintf('%03d',$_->[0]).$_->[1] } @{$_[1]})];
#print Dumper($tmp);
return $tmp;
}
sub get_pending {
return [ @{$_[0]} ];
}
1;
####
package YuGiOh::Filter::Stackable;
use base 'POE::Filter::Stackable';
use strict;
sub new {
my $type = shift;
my %param = @_;
my $list = [];
if($param{Filters}){
foreach my $filter (@{$param{Filters}}){
push(@$list,ref($filter) ? $filter : $filter->new());
}
}
$type->SUPER::new(Filters => $list);
}
1;
####
#!perl
use POE qw(Component::Client::TCP Filter::Stackable Filter::Block);
use YuGiOh::Filter;
#use YuGiOh::Constants;
use Digest::MD5;
use Data::Dumper;
use constant {
PORT => 1001,
};
POE::Component::Client::TCP->new
( RemoteAddress => '192.168.1.11',
RemotePort => PORT,
ConnectTimeout => 5, # Seconds; optional.
#Started => \&handle_starting, # Optional.
#Args => [ "arg0", "arg1" ], # Optional. Started args.
#Connected => \&handle_connect,
#ConnectError => \&handle_connect_error,
#Disconnected => \&handle_disconnect,
ServerInput => \&handle_server_input,
#ServerError => \&handle_server_error,
#ServerFlushed => \&handle_server_flush,
Filter => ["POE::Filter::Stackable", Filters => [new POE::Filter::Block(),new YuGiOh::Filter()]],
);
$poe_kernel->run();
sub handle_server_input {
my($session,$message) = ($_[SESSION],$_[ARG0]);
print Dumper($message);
}