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