ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-Knuddels/Net/Knuddels.pm
Revision: 1.11
Committed: Thu Jan 13 00:55:00 2005 UTC (21 years, 8 months ago) by root
Branch: MAIN
Changes since 1.10: +12 -0 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.8 =head1 NAME
2    
3     Net::Knuddels - www.knuddels.de protocol implementation.
4    
5     =head1 SYNOPSIS
6    
7     use Net::Knuddels;
8    
9     =head1 DESCRIPTION
10    
11     RTSL.
12    
13     =cut
14    
15 root 1.1 package Net::Knuddels;
16    
17 root 1.3 use Net::Knuddels::Dictionary;
18    
19 root 1.2 use strict;
20     use utf8;
21    
22 root 1.4 use Carp;
23     use Math::BigInt;
24    
25     sub hash_pw($$) {
26     my ($challenge, $pw) = @_;
27    
28     my $l1 = length $pw;
29     my $l2 = length $challenge;
30    
31     my $k = chr ($l1 ^ ($l2 << 4));
32    
33     my $l = $l1 < $l2 ? $l2 : $l1;
34    
35     my $xor = substr +($pw x 100) ^ ($challenge x 100) ^ ($k x 100), 0, $l;
36    
37     my ($i, $j);
38    
39     --$l;
40    
41     if ($l <= 17) {
42     for (0 .. $l) {
43     $i = $i * 3 + ord substr $xor, $l - $_;
44     $j = $j * 5 + ord substr $xor, $_;
45    
46     $i = unpack "l", pack "L", (new Math::BigInt $i) & 0xffffffff;
47     $j = unpack "l", pack "L", (new Math::BigInt $j) & 0xffffffff;
48     }
49     } else {
50     for ($_ = $l; $_ >= 0; $_ -= int $_/19) {
51     $i = $i * 5 + ord substr $xor, $_;
52     $j = $j * 3 + ord substr $xor, $l - $_;
53    
54     $i = unpack "l", pack "L", (new Math::BigInt $i) & 0xffffffff;
55     $j = unpack "l", pack "L", (new Math::BigInt $j) & 0xffffffff;
56     }
57     }
58    
59     $i ^= $j;
60    
61     ($i & 0xffffff) ^ ($i >> 24);
62     }
63    
64 root 1.10 my $RE_dec = join "|", keys %$Net::Knuddels::Dictionary;
65    
66     sub decode {
67     my $bin = unpack "b*", $_[0];
68     my $res = "";
69    
70     while ($bin =~ /\G($RE_dec)/cog) {
71     my $frag = $Net::Knuddels::Dictionary->{$1};
72     $frag = chr unpack "v", pack "b*", $bin =~ /\G.{16}/cg && $1 if $frag eq "\\\\\\";
73     $res .= $frag;
74     }
75     $bin =~ /\G(.*[^0].*)$/ and die "Net::Knuddels::Receiver: undecodable message tail '$1'";
76    
77     $res
78     }
79    
80 root 1.9 my %encode = reverse %$Net::Knuddels::Dictionary;
81    
82 root 1.10 my $RE_enc = join "|", map quotemeta, sort { (length $b) <=> (length $a) } keys %encode;
83 root 1.9
84 root 1.10 sub encode($) {
85 root 1.9 my ($msg) = @_;
86    
87     my $data = "";
88    
89     while () {
90     $data .= $encode{$1} while $msg =~ /\G($RE_enc)/cog;
91    
92     $msg =~ /\G./csog
93     or last;
94    
95     $data .= $encode{"\\\\\\"} . unpack "b*", pack "v", ord $1;
96     }
97    
98     pack "b*", $data
99     }
100    
101 root 1.5 =head2 CLASS Net::Knuddels::Protocol
102    
103     You B<must> call the C<destroy> method of this class when you no longer
104     use it, as circular references will keep the object alive otherwise.
105    
106 root 1.6 =over 4
107    
108 root 1.8 =cut
109    
110     package Net::Knuddels::Protocol;
111    
112 root 1.6 =item new
113    
114     Create a new C<Net::Knuddels::Protocol> object.
115    
116 root 1.5 =cut
117 root 1.2
118     sub new {
119     my $class = shift;
120    
121 root 1.4 my %data;
122    
123     my $self = bless {
124     @_
125     }, $class;
126    
127     $self->register ("(" => sub {
128 root 1.8 $self->{login_challenge} = $_[1];
129     $self->{login_room} = $_[2];
130     $self->feed_event ("login_info");
131 root 1.4 });
132    
133     $self;
134 root 1.2 }
135    
136 root 1.6 =item $protocol->feed_data ($octets)
137    
138     Feed raw protocol data into the decoder.
139    
140     =cut
141    
142 root 1.2 sub feed_data($$) {
143     my ($self, $data) = @_;
144    
145     # split data stream into packets
146    
147     $data = "$self->{rbuf}$data";
148    
149     while () {
150     1 <= length $data or last;
151     my $len = ord substr $data, 0, 1;
152    
153     my $skip;
154     if ($len & 0x80) {
155     my $tail = (($len >> 5) & 3) - 1;
156     $len = ($len & 0x1f) + 1;
157    
158     $tail < length $data or last;
159     $len += (ord substr $data, $_ + 1, 1) << ($_ * 8 + 5)
160     for 0 .. $tail;
161    
162     $skip = 2 + $tail;
163     } else {
164     $skip = 1;
165     $len++;
166     }
167    
168     $len + $skip <= length $data or last;
169     substr $data, 0, $skip, "";
170     my $msg = substr $data, 0, $len, "";
171    
172     $self->feed_msg ($msg);
173     }
174    
175     $self->{rbuf} = $data;
176     }
177    
178     sub feed_msg($$) {
179     my ($self, $msg) = @_;
180 root 1.1
181 root 1.10 $self->feed_event (split /\0/, Net::Knuddels::decode $msg);
182 root 1.1 }
183    
184 root 1.2 sub feed_event($@) {
185     my ($self, $type, @arg) = @_;
186 root 1.1
187 root 1.2 for ($type, "ALL") {
188     my $ev = $self->{cb}{$_};
189     $_->($type, @arg) for values %$ev;
190 root 1.1 }
191 root 1.2 }
192 root 1.1
193 root 1.11 =item $msg = $protocol->encode_msg (@strings)
194    
195     Join the strings with C<\0>, encode the result into a protocol packet and
196     return it.
197    
198     =cut
199    
200     sub encode_msg($@) {
201     my ($self, @args) = @_;
202     my $msg = Net::Knuddels::encode join "\0", @args;
203     }
204    
205 root 1.6 =item $protocol->register ($type => $callback)
206    
207     Register a callback for events of type C<$type>, which is either the name
208     of a low-level event sent by the server (such as "k" for dialog box) or
209 root 1.8 the name of a generated event, such as C<login_info>.
210 root 1.6
211     =cut
212    
213 root 1.2 sub register {
214     my ($self, $type, $cb) = @_;
215 root 1.1
216 root 1.2 $self->{cb}{$type}{$cb} = $cb;
217 root 1.1 }
218    
219 root 1.8 =item $protocol->destroy
220    
221 root 1.9 I<MUST> be called to destroy the object, otherwise it will leak (no automatic cleanup).
222 root 1.8
223     =cut
224    
225 root 1.5 sub destroy {
226     my ($self) = @_;
227    
228     delete $self->{cb};
229     }
230    
231 root 1.6 =back
232    
233 root 1.8 =head2 CLASS Net::Knuddels::Client
234    
235 root 1.9 Implement a Knuddels client connection.
236    
237 root 1.8 =over 4
238    
239     =cut
240    
241     package Net::Knuddels::Client;
242    
243 root 1.9 =item new Net::Knuddels::Client [IO::Socket::new arguments]
244    
245     Create a new client connection.
246    
247     =cut
248    
249     use IO::Socket::INET;
250    
251     sub new {
252     my ($class, @arg) = @_;
253    
254     my $fh = new IO::Socket::INET @arg
255     or Carp::croak "Net::Knuddels::Client::new: $!";
256    
257     my $self = bless {
258     fh => $fh,
259     proto => (new Net::Knuddels::Protocol),
260     }, $class;
261    
262     syswrite $fh, "\0";
263    
264     $self
265     }
266    
267     =item $client->fh
268    
269     Return the fh used for communications. You are responsible for calling C<<
270     $client->fh_ready >> whenever the fh becomes ready for reading.
271    
272     =cut
273    
274     sub fh {
275     $_[0]->{fh}
276     }
277    
278     =item $client->command ($type => @args)
279    
280     Send a message of type C<$type> and the given arguments to the server.
281    
282     =cut
283    
284     sub command {
285     my ($self, $type, @args) = @_;
286    
287     syswrite $self->{fh}, Net::Knuddels::encode join "\0", $type, @args;
288     }
289    
290     =item $client->login ($url, $unknown)
291    
292     Send a 't' message. The default for C<$url> is
293     C<http://www.knuddels.de/applet.html?v=86a&c=0> and C<$unknown> is C<6>.
294    
295     =cut
296    
297     sub login {
298     }
299    
300     =item $client->register ($type => $cb)
301    
302     See L<Net::Knuddels::Protocol::register>.
303    
304     =cut
305    
306     sub register {
307     my ($self, $type, $cb) = @_;
308    
309     $self->{protocol}->register ($type, $cb);
310     }
311    
312 root 1.8 =back
313    
314     =head1 AUTHOR
315    
316     Marc Lehmann <pcg@goof.com>
317     http://home.schmorp.de/
318    
319 root 1.6 =cut
320    
321 root 1.2 1;
322