aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/UDP/DatagramProcessor.pm
blob: 99ced03ac10454da9ef5419157ed9373e60c3f3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package MasterServer::UDP::DatagramProcessor;

use strict;
use warnings;
use Exporter 'import';
our @EXPORT = qw| process_datagram |;

################################################################################
## Process datagrams after querying a server.
## %o contains ip, port, recv buffer, secure string
################################################################################                  
sub process_datagram {
  my ($self, %o) = @_;
  my $rx = $self->data2hashref($o{rxbuf});

  # can not proceed if validate was provided, but not gamename
  return 0 unless $rx->{gamename};
  # do not process data if no hostport was provided.
  return 0 unless $rx->{hostport};

  # truncate excessively long fields like hostname
  $rx->{hostname} = substr $rx->{hostname}, 0, 199 if (length $rx->{hostname} >= 199);
  
  # try updating serverlist info based on ip/hostport
  my $update = $self->update_server(
    ip        => $o{ip},
    hostport  => $rx->{hostport},
    direct    => $o{direct},
    %{$rx},
  );

  # if not found, insert it in the table, after verification
  if ($update == 0) {
    # can not proceed if gamename was provided, but not validate
    return 0 if ( not($rx->{validate}) && $rx->{gamename} );
  
    # does the recv buffer contain a validation segment?
    my $auth = $self->auth_server(
      gamename  => lc $rx->{gamename},
      secure    => $o{secure},
      enctype   => $rx->{enctype},
      validate  => $rx->{validate},
    ) if ($rx->{validate} && $rx->{gamename});

    # if authenticated, or known to be incapable of authenticating (tribesv)
    if ($auth || $self->{secure_unsupported} =~ m/$rx->{gamename}/i ) {
      # add to the database in three steps. First, insert basic data.
      $self->insert_server(
        ip        => $o{ip},
        port      => $o{port},
        hostport  => $rx->{hostport},
      );
      # second, update the entry with all available information
      $self->update_server(
        ip        => $o{ip},
        hostport  => $rx->{hostport},
        direct    => $o{direct},
        %{$rx},
      );
      # third, insert an entry for extended server information
      $self->insert_extended(
        ip        => $o{ip}, 
        hostport  => $rx->{hostport}
      );
      # log new beacon
      $self->log("add", "new server $o{ip}, $rx->{hostport}". 
        ($rx->{gamename} ? (" for $rx->{gamename}") : "") );

      # addresses are often added through pending list. delete if successful
      $self->remove_pending(ip => $o{ip}, port => $o{port});
    }
    else {
      # log: failed secure test
      my $val_str = $self->validate_string(
        gamename  => lc $rx->{gamename},
        secure    => $o{secure},
        enctype   => $rx->{enctype},
        validate  => $rx->{validate},
      );
      $self->log("secure","$o{ip}, $o{port} failed validation for ".($rx->{gamename} || "empty_gamename") );
      $self->log("secure", 
        "cipher: "   .($self->get_game_props(gamename => $rx->{gamename})->[0]->{cipher} || "empty_cipher") . ", "
       ."secure: "   .($o{secure}      || "empty_secure"). ", "
       ."expected: " .($val_str        || "empty_v_string"). ", "
       ."received: " .($rx->{validate} || "empty_r_validate"));

      # remove addresses anyway to prevent error spamming in log
      $self->remove_pending(ip => $o{ip}, port => $o{port});
      return 0;
    }
  }
  
  # select server id for faster/easier referencing
  my $sid = $self->get_server(
    ip        => $o{ip}, 
    hostport  => $rx->{hostport}, 
    limit     => 1
  )->[0]->{id} || 0;
  
  # server not found in db. strange. manually deleted? ignore and return.
  return 0 unless $sid;

  # update extended information with the unified/new info columns
  my ($uei, $upi) = unify_information($sid,$rx);
  my $u = $self->update_extended(sid => $sid, %{$uei});
  
  # update player information (first delete, then add new)
  $self->delete_players($sid);
  for my $pl (@{$upi}) {$self->insert_players(@{$pl});}
  
  # return true when all done
  return 1 if int($u || 0);
  
  # update possibly failed because we migrated from an older serverlist.
  $self->log("warning", "no extended information for $o{ip}, $rx->{hostport} to update");
  
  # insert extended table entry again
  $self->insert_extended(
    ip        => $o{ip}, 
    hostport  => $rx->{hostport}
  );
  
  # and try to update it again (players were already added independently)
  $u = $self->update_extended(sid => $sid, %{$uei});
  
  # return true when all done
  return 1 if int($u || 0);
  
  # now we're toast
  $self->log("error", "failed to insert $o{ip}, $rx->{hostport} extended information twice");
  return 0;
}

################################################################################
## Process data into readable player stat columns
## server id, received data buffer hash
## returns unified extended info, unified player info
################################################################################     
sub unify_information {
  my ($sid, $rx) = @_;
  my %uei; # unified extended info
  my @upi; # unified  player  info
  
  # FIXME unify with {player playername name, other keys/columns}

  # first process all available player entries
  for (my $i = 0; exists $rx->{"player_$i"}; $i++) {
    # add player info to UPI and remove from hash
    my @player;
    push @player, $sid;
    push @player, delete $rx->{"player_$i"} || "Derp";
    push @player, delete $rx->{"team_$i"};
    push @player, int (delete $rx->{"frags_$i"} || 0);
    push @player, delete $rx->{"mesh_$i"};
    push @player, delete $rx->{"skin_$i"};
    push @player, delete $rx->{"face_$i"};
    push @player, int (delete $rx->{"ping_$i"} || 0);
    push @player, delete $rx->{"ngsecret_$i"};
    push @upi, \@player;
  }
  # return remaining values, player array
  return ($rx, \@upi);
}

1;