ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-Knuddels/Net/Knuddels.pm
Revision: 1.13
Committed: Thu Jan 13 02:28:42 2005 UTC (19 years, 4 months ago) by root
Branch: MAIN
Changes since 1.12: +58 -5 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 root 1.13 use PApp::Util; warn PApp::Util::dumpval [$challenge, $pw];
29    
30 root 1.4 my $l1 = length $pw;
31     my $l2 = length $challenge;
32    
33     my $k = chr ($l1 ^ ($l2 << 4));
34    
35     my $l = $l1 < $l2 ? $l2 : $l1;
36    
37     my $xor = substr +($pw x 100) ^ ($challenge x 100) ^ ($k x 100), 0, $l;
38 root 1.13 use PApp::Util; warn PApp::Util::dumpval [$xor];
39 root 1.4
40     my ($i, $j);
41    
42     --$l;
43 root 1.13 use PApp::Util; warn PApp::Util::dumpval [$l];
44 root 1.4
45     if ($l <= 17) {
46     for (0 .. $l) {
47     $i = $i * 3 + ord substr $xor, $l - $_;
48     $j = $j * 5 + ord substr $xor, $_;
49    
50     $i = unpack "l", pack "L", (new Math::BigInt $i) & 0xffffffff;
51     $j = unpack "l", pack "L", (new Math::BigInt $j) & 0xffffffff;
52     }
53     } else {
54     for ($_ = $l; $_ >= 0; $_ -= int $_/19) {
55     $i = $i * 5 + ord substr $xor, $_;
56     $j = $j * 3 + ord substr $xor, $l - $_;
57    
58     $i = unpack "l", pack "L", (new Math::BigInt $i) & 0xffffffff;
59     $j = unpack "l", pack "L", (new Math::BigInt $j) & 0xffffffff;
60     }
61     }
62 root 1.13 warn sprintf "%x %x\n", $i, $j;
63     warn sprintf "d%d %d\n", $i, $j;
64 root 1.4
65     $i ^= $j;
66 root 1.13 warn sprintf "d%d\n", $i;
67     warn sprintf "%x\n", $i;
68    
69     # $i = unpack "l", pack "L", (new Math::BigInt +($i & 0xffffff) ^ ($i >> 24)) & 0xffffffff;
70     # warn sprintf "%x\n", $i;
71     # die $i;
72 root 1.4
73 root 1.13 ($i & 0xffffff) ^ ($i >> 24)
74 root 1.4 }
75    
76 root 1.13 warn "hi\n";
77     #die hash_pw "\300mz\350K\cA\321\cZ \256R", "b"; # 1722400 1a4820
78    
79 root 1.10 my $RE_dec = join "|", keys %$Net::Knuddels::Dictionary;
80    
81     sub decode {
82     my $bin = unpack "b*", $_[0];
83     my $res = "";
84    
85     while ($bin =~ /\G($RE_dec)/cog) {
86     my $frag = $Net::Knuddels::Dictionary->{$1};
87     $frag = chr unpack "v", pack "b*", $bin =~ /\G.{16}/cg && $1 if $frag eq "\\\\\\";
88     $res .= $frag;
89     }
90     $bin =~ /\G(.*[^0].*)$/ and die "Net::Knuddels::Receiver: undecodable message tail '$1'";
91    
92     $res
93     }
94    
95 root 1.9 my %encode = reverse %$Net::Knuddels::Dictionary;
96    
97 root 1.10 my $RE_enc = join "|", map quotemeta, sort { (length $b) <=> (length $a) } keys %encode;
98 root 1.9
99 root 1.10 sub encode($) {
100 root 1.9 my ($msg) = @_;
101    
102     my $data = "";
103    
104     while () {
105     $data .= $encode{$1} while $msg =~ /\G($RE_enc)/cog;
106    
107     $msg =~ /\G./csog
108     or last;
109    
110     $data .= $encode{"\\\\\\"} . unpack "b*", pack "v", ord $1;
111     }
112    
113     pack "b*", $data
114     }
115    
116 root 1.5 =head2 CLASS Net::Knuddels::Protocol
117    
118     You B<must> call the C<destroy> method of this class when you no longer
119     use it, as circular references will keep the object alive otherwise.
120    
121 root 1.6 =over 4
122    
123 root 1.8 =cut
124    
125     package Net::Knuddels::Protocol;
126    
127 root 1.6 =item new
128    
129     Create a new C<Net::Knuddels::Protocol> object.
130    
131 root 1.5 =cut
132 root 1.2
133     sub new {
134     my $class = shift;
135    
136 root 1.4 my %data;
137    
138     my $self = bless {
139     @_
140     }, $class;
141    
142     $self->register ("(" => sub {
143 root 1.8 $self->{login_challenge} = $_[1];
144     $self->{login_room} = $_[2];
145 root 1.13 $self->feed_event ("login");
146 root 1.4 });
147    
148     $self;
149 root 1.2 }
150    
151 root 1.6 =item $protocol->feed_data ($octets)
152    
153     Feed raw protocol data into the decoder.
154    
155     =cut
156    
157 root 1.2 sub feed_data($$) {
158     my ($self, $data) = @_;
159    
160     # split data stream into packets
161    
162     $data = "$self->{rbuf}$data";
163    
164     while () {
165     1 <= length $data or last;
166     my $len = ord substr $data, 0, 1;
167    
168     my $skip;
169     if ($len & 0x80) {
170     my $tail = (($len >> 5) & 3) - 1;
171     $len = ($len & 0x1f) + 1;
172    
173     $tail < length $data or last;
174     $len += (ord substr $data, $_ + 1, 1) << ($_ * 8 + 5)
175     for 0 .. $tail;
176    
177     $skip = 2 + $tail;
178     } else {
179     $skip = 1;
180     $len++;
181     }
182    
183     $len + $skip <= length $data or last;
184     substr $data, 0, $skip, "";
185     my $msg = substr $data, 0, $len, "";
186    
187     $self->feed_msg ($msg);
188     }
189    
190     $self->{rbuf} = $data;
191     }
192    
193     sub feed_msg($$) {
194     my ($self, $msg) = @_;
195 root 1.1
196 root 1.10 $self->feed_event (split /\0/, Net::Knuddels::decode $msg);
197 root 1.1 }
198    
199 root 1.2 sub feed_event($@) {
200     my ($self, $type, @arg) = @_;
201 root 1.1
202 root 1.2 for ($type, "ALL") {
203     my $ev = $self->{cb}{$_};
204     $_->($type, @arg) for values %$ev;
205 root 1.1 }
206 root 1.2 }
207 root 1.1
208 root 1.11 =item $msg = $protocol->encode_msg (@strings)
209    
210     Join the strings with C<\0>, encode the result into a protocol packet and
211     return it.
212    
213     =cut
214    
215     sub encode_msg($@) {
216     my ($self, @args) = @_;
217     my $msg = Net::Knuddels::encode join "\0", @args;
218 root 1.12 my $len = (length $msg) - 1;
219    
220     if ($len < 0x80) {
221     (chr $len) . $msg
222     } else {
223     (chr 0x80 | 0x40 | ($len & 0x1f))
224     . (chr +($len >> 5) % 0xff)
225     . (chr +($len >> 13) % 0xff)
226     . $msg
227     }
228 root 1.11 }
229    
230 root 1.6 =item $protocol->register ($type => $callback)
231    
232     Register a callback for events of type C<$type>, which is either the name
233     of a low-level event sent by the server (such as "k" for dialog box) or
234 root 1.8 the name of a generated event, such as C<login_info>.
235 root 1.6
236     =cut
237    
238 root 1.2 sub register {
239     my ($self, $type, $cb) = @_;
240 root 1.1
241 root 1.2 $self->{cb}{$type}{$cb} = $cb;
242 root 1.1 }
243    
244 root 1.8 =item $protocol->destroy
245    
246 root 1.9 I<MUST> be called to destroy the object, otherwise it will leak (no automatic cleanup).
247 root 1.8
248     =cut
249    
250 root 1.5 sub destroy {
251     my ($self) = @_;
252    
253     delete $self->{cb};
254     }
255    
256 root 1.6 =back
257    
258 root 1.8 =head2 CLASS Net::Knuddels::Client
259    
260 root 1.9 Implement a Knuddels client connection.
261    
262 root 1.8 =over 4
263    
264     =cut
265    
266     package Net::Knuddels::Client;
267    
268 root 1.9 =item new Net::Knuddels::Client [IO::Socket::new arguments]
269    
270     Create a new client connection.
271    
272     =cut
273    
274     use IO::Socket::INET;
275    
276     sub new {
277     my ($class, @arg) = @_;
278    
279     my $fh = new IO::Socket::INET @arg
280     or Carp::croak "Net::Knuddels::Client::new: $!";
281    
282     my $self = bless {
283     fh => $fh,
284     proto => (new Net::Knuddels::Protocol),
285     }, $class;
286    
287     syswrite $fh, "\0";
288    
289     $self
290     }
291    
292     =item $client->fh
293    
294     Return the fh used for communications. You are responsible for calling C<<
295 root 1.13 $client->ready >> whenever the fh becomes ready for reading.
296 root 1.9
297     =cut
298    
299     sub fh {
300     $_[0]->{fh}
301     }
302    
303 root 1.13 =item $client->ready
304    
305     To be called then the filehandle is ready for reading. Returns false if
306     the server closed the connection, true otherwise.
307    
308     =cut
309    
310     sub ready {
311     my ($self) = @_;
312    
313     sysread $self->{fh}, my $buf, 8192
314     or return;
315    
316     $self->{proto}->feed_data ($buf);
317    
318     1;
319     }
320    
321 root 1.9 =item $client->command ($type => @args)
322    
323     Send a message of type C<$type> and the given arguments to the server.
324    
325     =cut
326    
327     sub command {
328     my ($self, $type, @args) = @_;
329    
330 root 1.13 use Dumpvalue;
331     Dumpvalue->new (compactDump => 1, veryCompact => 1, quoteHighBit => 1, tick => '"')->dumpValue ([$type, @args]);
332    
333     syswrite $self->{fh}, $self->{proto}->encode_msg ($type, @args);
334 root 1.9 }
335    
336     =item $client->login ($url, $unknown)
337    
338     Send a 't' message. The default for C<$url> is
339     C<http://www.knuddels.de/applet.html?v=86a&c=0> and C<$unknown> is C<6>.
340    
341     =cut
342    
343     sub login {
344 root 1.13 my ($self, $url, $unknown) = @_;
345    
346     $self->command ("t", "V8.6a", $url || "http://www.knuddels.de/applet.html?v=86a&c=0", $unknown || 3);
347     }
348    
349     =item $client->set_nick ($room, $nick, $password)
350    
351     Registers the nick with the given password.
352    
353     =cut
354    
355     sub set_nick {
356     my ($self, $room, $nick, $password) = @_;
357    
358     exists $self->{proto}{login_challenge} or Carp::croak "set_nick can only be called after a login event";
359    
360     $self->command ("n", $room, $nick, Net::Knuddels::hash_pw $self->{proto}{login_challenge}, $password);
361 root 1.9 }
362    
363     =item $client->register ($type => $cb)
364    
365     See L<Net::Knuddels::Protocol::register>.
366    
367     =cut
368    
369     sub register {
370     my ($self, $type, $cb) = @_;
371    
372 root 1.13 $self->{proto}->register ($type, $cb);
373 root 1.9 }
374    
375 root 1.8 =back
376    
377     =head1 AUTHOR
378    
379     Marc Lehmann <pcg@goof.com>
380     http://home.schmorp.de/
381    
382 root 1.6 =cut
383    
384 root 1.2 1;
385