File Coverage

blib/lib/Net/ICQ.pm
Criterion Covered Total %
statement 33 316 10.4
branch 5 80 6.2
condition 4 29 13.7
subroutine 8 34 23.5
pod 0 8 0.0
total 50 467 10.7


line stmt bran cond sub pod time code
1             package Net::ICQ;
2              
3              
4 1     1   9020 use strict;
  1         3  
  1         72  
5 1         148 use vars qw(
6             $VERSION
7             @_table
8             %cmd_codes %srv_codes
9             %status_codes %privacy_codes
10             %meta_codes %sex_codes %occupations %languages
11             %_parsers %_msg_parsers %_meta_parsers
12             %_builders %_msg_builders
13 1     1   6 );
  1         2  
14 1     1   5 use Carp;
  1         5  
  1         86  
15 1     1   906 use IO::Socket;
  1         37361  
  1         6  
16 1     1   2963 use IO::Select;
  1         2090  
  1         61  
17 1     1   1004 use Time::Local;
  1         1714  
  1         57  
18 1     1   6532 use Math::BigInt;
  1         48990  
  1         7  
19              
20             $VERSION = '0.16';
21              
22              
23             # "encryption" table (grumble grumble...)
24             @_table = (
25             0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48,
26             0x53, 0x61, 0x4C, 0x59, 0x60, 0x57, 0x5B, 0x3D,
27             0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F, 0x6F, 0x67,
28             0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39,
29             0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69,
30             0x48, 0x33, 0x31, 0x64, 0x35, 0x5A, 0x4A, 0x42,
31             0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C, 0x49,
32             0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48,
33             0x33, 0x31, 0x44, 0x65, 0x62, 0x46, 0x48, 0x53,
34             0x41, 0x07, 0x6C, 0x69, 0x48, 0x33, 0x51, 0x54,
35             0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A,
36             0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36,
37             0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x63,
38             0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x35, 0x5A,
39             0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36,
40             0x50, 0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35,
41             0x5A, 0x4A, 0x62, 0x66, 0x58, 0x3B, 0x4D, 0x66,
42             0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58, 0x3B,
43             0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53,
44             0x61, 0x4C, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64,
45             0x55, 0x6A, 0x32, 0x3E, 0x44, 0x45, 0x52, 0x6E,
46             0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C,
47             0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F,
48             0x47, 0x63, 0x59, 0x60, 0x57, 0x5B, 0x3D, 0x3E,
49             0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A, 0x52, 0x4E,
50             0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58,
51             0x3B, 0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F,
52             0x5F, 0x3F, 0x6F, 0x67, 0x53, 0x41, 0x25, 0x41,
53             0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D, 0x4E,
54             0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F,
55             0x47, 0x43, 0x69, 0x48, 0x33, 0x51, 0x54, 0x5D,
56             0x6E, 0x3C, 0x31, 0x64, 0x35, 0x5A, 0x00, 0x00,
57             );
58              
59              
60             %cmd_codes = (
61             CMD_ACK => 10,
62             CMD_SEND_MESSAGE => 270,
63             CMD_LOGIN => 1000,
64             CMD_REG_NEW_USER => 1020,
65             CMD_CONTACT_LIST => 1030,
66             CMD_SEARCH_UIN => 1050,
67             CMD_SEARCH_USER => 1060,
68             CMD_KEEP_ALIVE => 1070,
69             CMD_SEND_TEXT_CODE => 1080,
70             CMD_ACK_MESSAGES => 1090,
71             CMD_LOGIN_1 => 1100,
72             CMD_MSG_TO_NEW_USER => 1110,
73             CMD_INFO_REQ => 1120,
74             CMD_EXT_INFO_REQ => 1130,
75             CMD_CHANGE_PW => 1180,
76             CMD_NEW_USER_INFO => 1190,
77             CMD_UPDATE_EXT_INFO => 1200,
78             CMD_QUERY_SERVERS => 1210,
79             CMD_QUERY_ADDONS => 1220,
80             CMD_STATUS_CHANGE => 1240,
81             CMD_NEW_USER_1 => 1260,
82             CMD_UPDATE_INFO => 1290,
83             CMD_AUTH_UPDATE => 1300,
84             CMD_KEEP_ALIVE2 => 1310,
85             CMD_LOGIN_2 => 1320,
86             CMD_ADD_TO_LIST => 1340,
87             CMD_RAND_SET => 1380,
88             CMD_RAND_SEARCH => 1390,
89             CMD_META_USER => 1610,
90             CMD_INVIS_LIST => 1700,
91             CMD_VIS_LIST => 1710,
92             CMD_UPDATE_LIST => 1720
93             );
94              
95              
96             %srv_codes = (
97             SRV_ACK => 10,
98             SRV_GO_AWAY => 40,
99             SRV_NEW_UIN => 70,
100             SRV_LOGIN_REPLY => 90,
101             SRV_BAD_PASS => 100,
102             SRV_USER_ONLINE => 110,
103             SRV_USER_OFFLINE => 120,
104             SRV_QUERY => 130,
105             SRV_USER_FOUND => 140,
106             SRV_END_OF_SEARCH => 160,
107             SRV_NEW_USER => 180,
108             SRV_UPDATE_EXT => 200,
109             SRV_RECV_MESSAGE => 220,
110             SRV_X2 => 230,
111             SRV_NOT_CONNECTED => 240,
112             SRV_TRY_AGAIN => 250,
113             SRV_SYS_DELIVERED_MESS => 260,
114             SRV_INFO_REPLY => 280,
115             SRV_INFO_FAIL => 300,
116             SRV_EXT_INFO_REPLY => 290,
117             SRV_STATUS_UPDATE => 420,
118             SRV_SYSTEM_MESSAGE => 450,
119             SRV_UPDATE_SUCCESS => 480,
120             SRV_UPDATE_FAIL => 490,
121             SRV_AUTH_UPDATE => 500,
122             SRV_MULTI_PACKET => 530,
123             SRV_X1 => 540,
124             SRV_RAND_USER => 590,
125             SRV_META_USER => 990
126             );
127              
128              
129              
130             %status_codes = (
131             ONLINE => 0x0000,
132             AWAY => 0x0001,
133             DO_NOT_DISTURB_2 => 0x0002,
134             NOT_AVAILABLE => 0x0004,
135             NOT_AVAILABLE_2 => 0x0005,
136             OCCUPIED => 0x0010,
137             DO_NOT_DISTURB => 0x0013,
138             FREE_FOR_CHAT => 0x0020,
139             INVISIBLE => 0x0100
140             );
141              
142             %privacy_codes = (
143             WEB_AWARE => 0x0001,
144             SHOW_IP => 0x0002,
145             TCP_MUST_AUTH => 0x1000,
146             TCP_IF_ON_CONNECTLIST => 0x2000
147             );
148              
149             %meta_codes = (
150             GENERAL_INFO => 0x03E9,
151             WORK_INFO => 0x03F3,
152             MORE_INFO => 0x03FD,
153             ABOUT_INFO => 0x0406,
154             );
155              
156             %sex_codes = (
157             "UNSPECIFIED" => 0,
158             "FEMALE" => 1,
159             "MALE" => 2
160             );
161              
162             %occupations = (
163             "Academic" => 1,
164             "Administrative" => 2,
165             "Art/Entertainment" => 3,
166             "College Student" => 4,
167             "Computers" => 5,
168             "Community & Social" => 6,
169             "Education" => 7,
170             "Engineering" => 8,
171             "Financial Services" => 9,
172             "Government" => 10,
173             "High School Student" => 11,
174             "Home" => 12,
175             "ICQ - Providing Help" => 13,
176             "Law" => 14,
177             "Managerial" => 15,
178             "Manufacturing" => 16,
179             "Medical/Health" => 17,
180             "Military" => 18,
181             "Non-Government Organization" => 19,
182             "Professional" => 20,
183             "Retail" => 21,
184             "Retired" => 22,
185             "Science & Research" => 23,
186             "Sports" => 24,
187             "Technical" => 25,
188             "University Student" => 26,
189             "Web Building" => 27,
190             "Other Services" => 99,
191             );
192              
193             %languages = (
194             1 => 'Arabic',
195             2 => 'Bhojpuri',
196             3 => 'Bulgarian',
197             4 => 'Burmese',
198             5 => 'Cantonese',
199             6 => 'Catalan',
200             7 => 'Chinese',
201             8 => 'Croatian',
202             9 => 'Czech',
203             10 => 'Danish',
204             11 => 'Dutch',
205             12 => 'English',
206             13 => 'Esperanto',
207             14 => 'Estonian',
208             15 => 'Farsi',
209             16 => 'Finnish',
210             17 => 'French',
211             18 => 'Gaelic',
212             19 => 'German',
213             20 => 'Greek',
214             21 => 'Hebrew',
215             22 => 'Hindi',
216             23 => 'Hungarian',
217             24 => 'Icelandic',
218             25 => 'Indonesian',
219             26 => 'Italian',
220             27 => 'Japanese',
221             28 => 'Khmer',
222             29 => 'Korean',
223             30 => 'Lao',
224             31 => 'Latvian',
225             32 => 'Lithuanian',
226             33 => 'Malay',
227             34 => 'Norwegian',
228             35 => 'Polish',
229             36 => 'Portuguese',
230             37 => 'Romanian',
231             38 => 'Russian',
232             39 => 'Serbian',
233             40 => 'Slovak',
234             41 => 'Slovenian',
235             42 => 'Somali',
236             43 => 'Spanish',
237             44 => 'Swahili',
238             45 => 'Swedish',
239             46 => 'Tagalog',
240             47 => 'Tatar',
241             48 => 'Thai',
242             49 => 'Turkish',
243             50 => 'Ukrainian',
244             51 => 'Urdu',
245             52 => 'Vietnamese',
246             53 => 'Yiddish',
247             54 => 'Yoruba',
248             55 => 'Afrikaans',
249             56 => 'Bosnian',
250             57 => 'Persian',
251             58 => 'Albanian',
252             59 => 'Armenian',
253             60 => 'Punjabi',
254             61 => 'Chamorro',
255             62 => 'Mongolian',
256             63 => 'Mandarin',
257             64 => 'Taiwaness',
258             65 => 'Macedonian',
259             66 => 'Sindhi',
260             67 => 'Welsh',
261             68 => 'Azerbaijani',
262             69 => 'Kurdish',
263             70 => 'Gujarati',
264             71 => 'Tamil',
265             72 => 'Belorussian',
266             73 => 'Unknown',
267             );
268              
269             =head1 NAME
270              
271             Net::ICQ - Pure Perl interface to an ICQ server
272              
273             =head1 SYNOPSIS
274              
275             use Net::ICQ;
276              
277             $icq = Net::ICQ->new($uin, $password);
278             $icq->connect();
279              
280             $icq->add_handler('SRV_SYS_DELIVERED_MESS', \&on_msg);
281              
282             $params = {
283             'type' => 1,
284             'text' => 'Hello world',
285             'receiver_uin' => 1234
286             };
287             $icq->send_event('CMD_SEND_MESSAGE', $params);
288              
289             $icq->start();
290              
291             =head1 DESCRIPTION
292              
293             C is a class implementing an ICQ client interface
294             in pure Perl.
295              
296             =cut
297              
298             =head1 CONSTRUCTOR
299              
300             =over 4
301              
302             =item *
303              
304             new (uin, password [, server [, port]])
305              
306             Creates a new Net::ICQ object. A Net::ICQ object represents
307             a single user logged into a specific ICQ server. The UIN and
308             password to use are specified as the first two parameters.
309             Server and port are optional, and default to
310             'icq.mirabilis.com' and '4000', respectively.
311              
312             Also, environment variables will be checked as follows:
313              
314             uin - ICQ_UIN
315             password - ICQ_PASS
316             server - ICQ_SERVER
317             port - ICQ_PORT
318              
319             Constructor parameters have the highest priority, then environment
320             variables. The built-in defaults (for server and port only) have
321             the lowest priority.
322              
323             If either a UIN or password is not provided either directly or
324             through environment variables, new() will return undef.
325              
326             Note that after calling new() you must next call connect() before
327             you can send and receive ICQ events.
328              
329             =back
330              
331             =cut
332              
333             sub new {
334 1     1 0 732 my ($class, $uin, $password, $server, $port) = @_;
335 1         2 my ($params);
336              
337 1 50 33     6 $uin or $uin = $ENV{ICQ_UIN} or return;
338 1 50 33     4 $password or $password = $ENV{ICQ_PASS} or return;
339 1 50 33     5 $server or $server = $ENV{ICQ_SERVER} or $server = 'icq.mirabilis.com';
340 1 50 33     12 $port or $port = $ENV{ICQ_PORT} or $port = 4000;
341              
342 1         17 my $self = {
343             _uin => $uin,
344             _password => $password,
345             _server => $server,
346             _port => $port,
347             _socket => undef,
348             _select => undef,
349             _events_incoming => [], # array
350             _events_outgoing => [],
351             _acks_incoming => [], # acks are processed immediately, so they get their own array
352             _acks_outgoing => [],
353             _handlers => {},
354             _last_keepalive => undef,
355             _seen_seq => [],
356             _debug => 0
357             };
358              
359 1 50       13 $self->{_socket} = IO::Socket::INET->new(
360             Proto => 'udp',
361             PeerAddr => $self->{_server},
362             PeerPort => $self->{_port},
363             )
364             or croak("socket error: $@");
365              
366 1         473 $self->{_select} = IO::Select->new($self->{_socket});
367 1         73 $self->{_last_keepalive} = time();
368              
369 1         3 bless($self, $class);
370              
371 1         4 return $self;
372             }
373              
374              
375             =head1 METHODS
376              
377             All of the following methods are instance methods;
378             you must call them on a Net::ICQ object (for example, $icq->start).
379              
380             =over 4
381              
382             =item *
383              
384             connect
385              
386             Connects the Net::ICQ object to the server.
387              
388             =cut
389              
390             sub connect {
391 0     0 0   my ($self) = @_;
392              
393 0           $self->{_session_id} = int(rand(0xFFFFFFFF));
394 0           $self->{_seq_num_1} = int(rand(0xFFFF));
395 0           $self->{_seq_num_2} = 0x1;
396 0           $self->{_connected} = 1;
397              
398             # send a login event
399 0           my $params = {
400             password => $self->{_password},
401             client_ip => $self->{_socket}->sockaddr(),
402             # FIX: deal with client_port correctly when TCP communication is implemented
403             client_port => 0
404             };
405 0           $self->send_event('CMD_LOGIN', $params, 1);
406              
407             }
408              
409              
410             =item *
411              
412             disconnect
413              
414             Disconnects the Net::ICQ object from the server.
415              
416             =cut
417              
418             sub disconnect {
419 0     0 0   my ($self) = @_;
420              
421 0           $self->send_event('CMD_SEND_TEXT_CODE', {text_code => 'B_USER_DISCONNECTED'}, 1);
422 0           $self->_do_outgoing();
423 0           $self->{_connected} = 0;
424             }
425              
426              
427             =item *
428              
429             connected
430              
431             Returns true if the Net::ICQ object is connected to the server,
432             and false if it is not.
433              
434             =cut
435              
436             sub connected {
437 0     0 0   my ($self) = @_;
438              
439 0           return $self->{_connected};
440             }
441              
442              
443             =item *
444              
445             start
446              
447             If you're writing a fairly simple application that doesn't need to
448             interface with other event-loop-based libraries, you can just call
449             start() to begin communicating with the server.
450              
451             Note that start() will not return until the Net::ICQ object is
452             disconnected from the server, either by the server itself or by
453             your event-handler code calling disconnect().
454              
455             =cut
456              
457             sub start {
458 0     0 0   my ($self) = @_;
459              
460 0           while ($self->connected) {
461 0           $self->do_one_loop();
462             }
463             }
464              
465              
466             =item *
467              
468             do_one_loop
469              
470             If you don't want to (or can't) call the start() method, you must
471             continuously call do_one_loop when your Net::ICQ object
472             is connected to the server. It uses select() to wait for
473             data from the server and other ICQ clients, so it won't use
474             CPU power even if you call it in a tight loop. If you need
475             to do other processing, you could call do_one_loop as
476             infrequently as once every few seconds.
477              
478             This method does one processing loop, which involves looking
479             for incoming data from the network, calling registered event
480             handlers, sending acknowledgements for received packets,
481             transmitting outgoing data over the network, and sending
482             keepalives to the server to tell it that we are still online.
483             If it is not called often enough, you will not be notified of
484             incoming events in a timely fashion, or the server might even
485             think you have disconnected and start to ignore you.
486              
487              
488             =cut
489              
490             sub do_one_loop {
491 0     0 0   my ($self) = @_;
492              
493 0           $self->_do_incoming();
494 0           $self->_do_acks();
495 0           $self->_do_multis();
496 0           $self->_do_keepalives();
497 0           $self->_do_timeouts();
498 0           $self->_do_handlers();
499 0           $self->_do_outgoing();
500             }
501              
502              
503             =item *
504              
505             add_handler(command_number, handler_ref)
506              
507             Sets the handler function for a specific ICQ server event.
508             command_number specifies the event to handle. You may use
509             either the numeric code or the corresponding string code.
510             See the SERVER EVENTS section below for the numeric and
511             string codes for all the events, along with descriptions
512             of each event's function and purpose.
513             handler_ref is a code ref for the sub that you want to handle
514             the event. See the HANDLERS section for how a handler works
515             and what it needs to do.
516              
517             =cut
518              
519             sub add_handler {
520 0     0 0   my ($self, $command, $sub) = @_;
521 0           my ($command_num);
522              
523 0 0         $command_num = exists $srv_codes{$command} ?
524             $srv_codes{$command} :
525             $command;
526              
527 0 0         print "=== add handler <", sprintf("%04X", $command_num), "> = $sub\n"
528             if $self->{_debug};
529              
530 0           $self->{_handlers}{$command_num} = $sub;
531             }
532              
533              
534             =item *
535              
536             send_event(command_number, params)
537              
538             Sends an event to the server.
539             command_number specifies the event to be sent. You may use
540             either the numeric code or the corresponding string code.
541             See the CLIENT EVENTS section below for the numeric and
542             string codes for all the events, along with descriptions
543             of each event's function and purpose.
544             params is a reference to a hash containing the parameters
545             for the event. See the CLIENT EVENTS section for an
546             explanation of the correct parameters for each event.
547              
548             =cut
549              
550             sub send_event {
551 0     0 0   my ($self, $command, $params, $priority) = @_;
552              
553 0 0         $command = $cmd_codes{$command}
554             if exists ($cmd_codes{$command});
555              
556 0           $self->_queue_event(
557             {
558 0           params => &{$_builders{$command}}($params),
559             command => $command
560             },
561             $priority
562             );
563             }
564              
565              
566             =head1 CLIENT EVENTS
567              
568             Client events are the messages an ICQ client, i.e. your code,
569             sends to the server. They represent things such as a logon
570             request, a message to another user, or a user search request.
571             They are sometimes called 'commands' because they represent
572             the 'commands' that an ICQ client can execute.
573              
574             When you ask Net::ICQ to send an event with send_event()
575             (described above), you need to provide 2 things:
576             the event name, and the parameters.
577              
578             =head2 Event name
579              
580             The event name is the first parameter to send_event(),
581             and it specifies which event you are sending. You may either
582             specify the string code or the numeric code. The section
583             CLIENT EVENT LIST below describes all the events and
584             gives the codes for each. For example: when sending a
585             text message to a user, you may give the event name as
586             either the string 'CMD_SEND_MESSAGE' or the number 270.
587              
588             The hash C<%Net::ICQ::cmd_codes> maps string codes to numeric
589             codes. C will produce a list of
590             all the string codes.
591              
592             =head2 Parameters
593              
594             The parameters list is the second parameter to send_event(),
595             and it specifies the data for the event. Every event has
596             its own parameter list, but the general idea is the same.
597             The parameters list is stored as a hashref, where the hash
598             contains a key for each parameter. Almost all the events
599             utilize a regular 1-level hash where the values are plain
600             scalars, but a few events do require 2-level hash. The
601             CLIENT EVENT LIST section lists the parameters for every
602             client event.
603              
604             For example: to send a normal text message with the text
605             'Hello world' to UIN 1234, the parameters would
606             look like this:
607              
608             {
609             'type' => 1,
610             'text' => 'Hello world',
611             'receiver_uin' => 1234
612             }
613              
614             =head2 A complete example
615              
616             Here is the complete code using send_event() to send the
617             message 'Hello world' to UIN 1234:
618              
619             $params = {
620             'type' => 1,
621             'text' => 'Hello world',
622             'receiver_uin' => 1234
623             };
624             $icq->send_event('CMD_SEND_MESSAGE', $params);
625              
626             =cut
627              
628              
629             %_parsers = (
630             # SRV_ACK
631             10 => sub {
632             my ($event) = @_;
633             delete $event->{params};
634             },
635             # SRV_GO_AWAY
636             40 => sub {
637             my ($event) = @_;
638             delete $event->{params};
639             },
640             # SRV_NEW_UIN
641             70 => sub {
642             my ($event) = @_;
643             delete $event->{params};
644             },
645             # SRV_LOGIN_REPLY
646             90 => sub {
647             my ($event) = @_;
648             my ($parsedevent);
649              
650             $parsedevent->{your_ip} = _bytes_to_int($event->{params}, 12, 4);
651             $event->{params} = $parsedevent;
652             },
653             # SRV_BAD_PASS
654             100 => sub {
655             my ($event) = @_;
656             delete $event->{params};
657             },
658             # SRV_USER_ONLINE
659             110 => sub {
660             my ($event) = @_;
661             my ($parsedevent);
662              
663             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
664             $parsedevent->{ip} = _bytes_to_int($event->{params}, 4, 4);
665             $parsedevent->{port} = _bytes_to_int($event->{params}, 8, 4);
666             $parsedevent->{real_ip} = _bytes_to_int($event->{params}, 12, 4);
667             $parsedevent->{status} = _bytes_to_int($event->{params}, 17, 2);
668             $parsedevent->{privacy} = _bytes_to_int($event->{params}, 19, 2);
669             $event->{params} = $parsedevent;
670             },
671             # SRV_USER_OFFLINE
672             120 => sub {
673             my ($event) = @_;
674             my ($parsedevent);
675              
676             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
677             $event->{params} = $parsedevent;
678             },
679             # SRV_QUERY
680             130 => sub {
681             #FIX : don't know what to do here ..
682             },
683             # SRV_USER_FOUND
684             140 => sub {
685             my ($event) = @_;
686             my ($parsedevent, $offset, $length);
687              
688             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
689             $offset = 4;
690             foreach ('nickname', 'firstname', 'lastname', 'email') {
691             $length = _bytes_to_int($event->{params}, $offset, 2);
692             $offset += 2; # Fixed: NN 06 jan 01
693             $parsedevent->{$_} = _bytes_to_str($event->{params}, $offset, $length - 1);
694             $offset += $length;
695             }
696             $parsedevent->{authorize} = _bytes_to_str($event->{params}, $offset, 1);
697             $event->{params} = $parsedevent;
698              
699             # AUTHORIZE can contain either 00 or 01:
700             # 00 means that your client should request authorization before
701             # adding this user to the contact list.
702             # 01 means that authorization is not required to add him/her to
703             # your contact list.
704             },
705             # SRV_END_OF_SEARCH
706             160 => sub {
707             my ($event) = @_;
708             my ($parsedevent);
709              
710             $parsedevent->{too_many} = _bytes_to_int($event->{params}, 0, 1);
711             $event->{params} = $parsedevent;
712             },
713             # SRV_NEW_USER
714             180 => sub {
715             #FIX : don't know what to do here ..
716             },
717             # SRV_UPDATE_EXT
718             200 => sub {
719             #FIX : don't know what to do here ..
720             },
721             # SRV_RECV_MESSAGE
722             220 => sub {
723             my ($event) = @_;
724             my ($parsedevent, @time);
725              
726             # Remove the bytes storing the time of the message, which makes the
727             # params look just like a regular online message (SRV_SYS_DELIVERED_MESS).
728             # Then, we can use that handler directly instead of copying its code here.
729             # Mirabilis really dropped the ball on this one, defining two separate
730             # events where it should really just be one...
731             @time = splice(@{$event->{params}}, 4, 6, ());
732             &{$_parsers{260}}($event);
733              
734             # we still need to insert the time
735             $event->{params}->{time} = timelocal(0, # sec
736             _bytes_to_int(\@time, 5, 1), # min
737             _bytes_to_int(\@time, 4, 1), # hour
738             _bytes_to_int(\@time, 3, 1), # day
739             _bytes_to_int(\@time, 2, 1)-1, # mon (thanks Bek Oberin for the -1)
740             _bytes_to_int(\@time, 0, 2) # year
741             );
742             },
743             # SRV_X2
744             230 => sub {
745             #FIX : don't know what to do here ..
746             },
747             # SRV_NOT_CONNECTED
748             240 => sub {
749             #FIX : don't know what to do here ..
750             },
751             # SRV_TRY_AGAIN
752             250 => sub {
753             #FIX : don't know what to do here ..
754             },
755             # SRV_SYS_DELIVERED_MESS
756             260 => sub {
757             my ($event) = @_;
758             my ($parsedevent, @strings, @tmp);
759              
760             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
761             $parsedevent->{type} = _bytes_to_int($event->{params}, 4, 2);
762             $parsedevent->{length} = _bytes_to_int($event->{params}, 6, 2);
763             @strings = _bytes_to_strlist([@{$event->{params}}[8..@{$event->{params}}-1]]);
764             if ($parsedevent->{type} == 1) {
765             $parsedevent->{text} = $strings[0];
766             } elsif ($parsedevent->{type} == 4) {
767             $parsedevent->{description} = $strings[0];
768             $parsedevent->{url} = $strings[1];
769             } elsif ($parsedevent->{type} == 6) {
770             $parsedevent->{nickname} = $strings[0];
771             $parsedevent->{firstname} = $strings[1];
772             $parsedevent->{lastname} = $strings[2];
773             $parsedevent->{email} = $strings[3];
774             $parsedevent->{reason} = $strings[4];
775             } elsif ($parsedevent->{type} == 8) {
776             } elsif ($parsedevent->{type} == 12) {
777             $parsedevent->{nickname} = $strings[0];
778             $parsedevent->{firstname} = $strings[1];
779             $parsedevent->{lastname} = $strings[2];
780             $parsedevent->{email} = $strings[3];
781             } elsif ($parsedevent->{type} == 13) {
782             $parsedevent->{name} = $strings[0];
783             $parsedevent->{unknown1} = $strings[1];
784             $parsedevent->{unknown2} = $strings[2];
785             $parsedevent->{email} = $strings[3];
786             $parsedevent->{unknown3} = $strings[4]; #always has value: 3
787             $parsedevent->{message} = $strings[5];
788             } elsif ($parsedevent->{type} == 14){
789             $parsedevent->{name} = $strings[0];
790             $parsedevent->{unknown1} = $strings[1];
791             $parsedevent->{unknown2} = $strings[2];
792             $parsedevent->{email} = $strings[3];
793             $parsedevent->{unknown3} = $strings[4]; #always has value: 3
794             $parsedevent->{message} = $strings[5];
795             } elsif ($parsedevent->{type} == 19) {
796             $parsedevent->{contacts} = {};
797             shift @strings; # remove first element - number of contacts
798             for (my $i=0; $i<@strings-1; $i+=2) {
799             $parsedevent->{contacts}{$strings[$i]} = $strings[$i+1];
800             }
801             }
802              
803             $event->{params} = $parsedevent;
804             },
805             # SRV_INFO_REPLY
806             280 => sub {
807             # (same as SRV_USER_FOUND, above)
808             my ($event) = @_;
809             my ($parsedevent, $offset, $length);
810              
811             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
812             $offset = 4;
813             foreach ('nickname', 'firstname', 'lastname', 'email') {
814             $length = _bytes_to_int($event->{params}, $offset, 2);
815             $offset += 2; # Fixed: NN 06 jan 01
816             $parsedevent->{$_} = _bytes_to_str($event->{params}, $offset, $length - 1);
817             $offset += $length;
818             }
819             $parsedevent->{authorize} = _bytes_to_str($event->{params}, $offset, 1);
820             $event->{params} = $parsedevent;
821             },
822             # SRV_EXT_INFO_REPLY
823             290 => sub {
824             # Thanks to Nezar Nielsen for this bit.
825             my ($event) = @_;
826             my ($parsedevent, $offset, $length);
827              
828             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
829             my $citylength = _bytes_to_int($event->{params}, 4, 2);
830             $parsedevent->{city} = _bytes_to_str($event->{params}, 6, $citylength - 1);
831             $offset = 6 + $citylength;
832             $parsedevent->{country_code} = _bytes_to_int($event->{params}, $offset, 2);
833             $offset += 2;
834             $parsedevent->{country_status} = _bytes_to_int($event->{params}, $offset,1);
835             $offset += 1;
836             my $statelength = _bytes_to_int($event->{params}, $offset,2);
837             $offset += 2;
838             $parsedevent->{state} = _bytes_to_str($event->{params}, $offset,$statelength - 1);
839             $offset += $statelength;
840             $parsedevent->{age} = _bytes_to_int($event->{params}, $offset, 2);
841             $offset += 2;
842             $parsedevent->{sex} = _bytes_to_int($event->{params}, $offset, 1);
843             $offset += 1;
844             for('phone', 'home_page', 'about'){
845             my $length = _bytes_to_int($event->{params}, $offset, 2);
846             $offset += 2;
847             $parsedevent->{$_} = _bytes_to_str($event->{params}, $offset, $length - 1);
848             $offset += $length;
849             }
850             # done parsing
851             $event->{params} = $parsedevent;
852              
853             # And from the specification (pretty much), here is some extra info:
854             #
855             # The code used in COUNTRY_CODE is the international telephone prefix, e.g.
856             # 01 00 (1) for the USA, 2C 00 (44) for the UK, 2E 00 (46) for Sweden, etc.
857             # COUNTRY_STATUS is normally FE, unless the remote user has not entered a
858             # country, in which case COUNTRY_CODE will be FF FF, and COUNTRY_STATUS
859             # will be 9C.
860             # The field AGE has the value FF FF if the user has not entered his/her age.
861             # Values for SEX:
862             # 00 = Not specified
863             # 01 = Female
864             # 02 = Male
865             },
866             #SRV_INFO_FAIL
867             300 => sub {
868             # thanks to Robin Fisher
869             my ($event) = @_;
870             my $parsedevent;
871              
872             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
873             $event->{params} = $parsedevent;
874             },
875             # SRV_STATUS_UPDATE
876             420 => sub {
877             # RTG 8/26/2000
878             my ($event) = @_;
879             my $parsedevent;
880             $parsedevent->{uin} = _bytes_to_int($event->{params}, 0, 4);
881             $parsedevent->{status} = _bytes_to_int($event->{params}, 4, 2);
882             $parsedevent->{privacy} = _bytes_to_int($event->{params}, 6, 2);
883             $event->{params} = $parsedevent;
884             },
885             # SRV_SYSTEM_MESSAGE
886             450 => sub {
887             #FIX : don't know what to do here ..
888             },
889             # SRV_UPDATE_SUCCESS
890             480 => sub {
891             #FIX : don't know what to do here ..
892             },
893             # SRV_UPDATE_FAIL
894             490 => sub {
895             #FIX : don't know what to do here ..
896             },
897             # SRV_AUTH_UPDATE
898             500 => sub {
899             #FIX : don't know what to do here ..
900             },
901             # SRV_X1
902             540 => sub {
903             #FIX : don't know what to do here ..
904             },
905             # SRV_RAND_USER
906             590 => sub {
907             #FIX : don't know what to do here ..
908             },
909             # SRV_META_USER
910             990 => sub {
911             my ($event) = @_;
912             my ($parsedevent, $params);
913              
914             $parsedevent->{subcmd} = _bytes_to_int($event->{params}, 0, 2);
915             $parsedevent->{success} = (_bytes_to_int($event->{params}, 2, 1) == 10);
916             @$params = @{$event->{params}}[3..@{$event->{params}}-1];
917             if (defined($_meta_parsers{$parsedevent->{subcmd}})){
918             $parsedevent->{body} = &{$_meta_parsers{$parsedevent->{subcmd}}}($params);
919             } else {
920             $parsedevent->{body} = {};
921             }
922             $event->{params} = $parsedevent;
923             }
924             );
925              
926             %_meta_parsers = (
927             #GENERAL_INFO
928             100 => sub {
929             return {}
930             },
931             #WORK_INFO
932             110 => sub {
933             return {}
934             },
935             #MORE_INFO
936             120 => sub {
937             return {}
938             },
939             #ABOUT_INFO
940             130 => sub {
941             return {}
942             },
943             200 => sub {
944             my ($params) = @_;
945             my ($ret, $offset, $length);
946              
947             $ret->{uin} = _bytes_to_int($params, 0, 4);
948             $offset = 4;
949             foreach ('nickname', 'firstname', 'lastname',
950             'primary_email', 'secondary_email', 'old_email',
951             'city', 'state', 'phone', 'fax',
952             'street', 'cellular') {
953             $length = _bytes_to_int($params, $offset, 2);
954             $ret->{$_} = _bytes_to_str($params, $offset + 2, $length - 1);
955             $offset += $length;
956             }
957             $ret->{zipcode} = _bytes_to_str($params, $offset, 4);
958             $ret->{country} = _bytes_to_str($params, $offset+4, 2);
959             $ret->{authorize} = _bytes_to_str($params, $offset+6, 1);
960             $ret->{webaware} = _bytes_to_str($params, $offset+7, 1);
961             $ret->{hideip} = _bytes_to_str($params, $offset+8, 1);
962              
963             return $ret;
964             },
965             230 => sub {
966             my ($params) = @_;
967             return _bytes_to_str($params, 2, _byte_to_int($params, 0, 2) - 1);
968             },
969             410 => sub {
970             my ($params) = @_;
971             my ($ret, $offset, $length);
972              
973             $ret->{uin} = _bytes_to_int($params, 0, 4);
974             $offset = 4;
975             foreach ('nickname', 'firstname', 'lastname', 'email') {
976             $length = _bytes_to_int($params, $offset, 2);
977             $ret->{$_} = _bytes_to_str($params, $offset + 2, $length - 1);
978             $offset += $length;
979             }
980             $ret->{authorize} = _bytes_to_str($params, $offset, 1);
981              
982             return $ret;
983             }
984             );
985              
986              
987             %_builders = (
988             #CMD_ACK
989             10 => sub {
990             },
991             #CMD_SEND_MESSAGE
992             270 => sub {
993             my ($params) = @_;
994             my ($ret, $body2);
995              
996             $ret = [];
997             push @$ret, _int_to_bytes(4, $params->{receiver_uin});
998             push @$ret, _int_to_bytes(2, $params->{type});
999              
1000             $body2 = &{$_msg_builders{$params->{type}}}($params);
1001             push @$ret, _int_to_bytes(2, @$body2+1);
1002             push @$ret, @$body2;
1003             push @$ret, (0x0);
1004             return $ret;
1005             },
1006             #CMD_LOGIN
1007             1000 => sub {
1008             my ($params) = @_;
1009             return [
1010             _int_to_bytes(4, time()),
1011             _int_to_bytes(4, $params->{client_port}),
1012             _int_to_bytes(2, length($params->{password})+1),
1013             _str_to_bytes($params->{password}, 1),
1014             _int_to_bytes(4, 0xD5),
1015             _str_to_bytes($params->{client_ip}),
1016             _int_to_bytes(1, 4),
1017             _int_to_bytes(4, $status_codes{ONLINE}),
1018             _int_to_bytes(2, 6),
1019             _int_to_bytes(2, 0),
1020             _int_to_bytes(4, 0),
1021             _int_to_bytes(4, 0x013F0002),
1022             _int_to_bytes(4, 0x50),
1023             _int_to_bytes(4, 3),
1024             _int_to_bytes(4, 0)
1025             ];
1026             },
1027             #CMD_REG_NEW_USER
1028             1020 => sub {
1029             my ($params) = @_;
1030             return [
1031             _int_to_bytes(2, length($params->{password})+1),
1032             _str_to_bytes($params->{password}, 1),
1033             _int_to_bytes(4, 0xA0),
1034             _int_to_bytes(4, 0x2461),
1035             _int_to_bytes(4, 0xA00000),
1036             _int_to_bytes(4, 0x0)
1037             ];
1038             },
1039             #CMD_CONTACT_LIST
1040             1030 => sub {
1041             my ($params) = @_;
1042             my ($ret, $num);
1043              
1044             $num = $params->{num_contacts};
1045             # FIX: this shouldn't croak! handle it gracefully..
1046             croak ("120 contact limit, send more than one packet")
1047             if ($num > 120);
1048              
1049             $ret = [];
1050             push @$ret, _int_to_bytes(1, $num);
1051             for (my $i = 0; $i < $num; $i++){
1052             push @$ret, _int_to_bytes(4, $params->{uins}[$i]);
1053             }
1054             return $ret;
1055             },
1056             #CMD_SEARCH_UIN
1057             1050 => sub {
1058             # thanks to Germain Malenfant for the fix
1059             my ($params) = @_;
1060             return [
1061             _int_to_bytes(4, $params->{uin})
1062             ];
1063             },
1064             #CMD_SEARCH_USER
1065             1060 => sub {
1066             my ($params) = @_;
1067             return [
1068             _int_to_bytes(2, length($params->{nick})+1),
1069             _str_to_bytes($params->{nick}, 1),
1070             _int_to_bytes(2, length($params->{first})+1),
1071             _str_to_bytes($params->{first}, 1),
1072             _int_to_bytes(2, length($params->{last})+1),
1073             _str_to_bytes($params->{last}, 1),
1074             _int_to_bytes(2, length($params->{email})+1),
1075             _str_to_bytes($params->{email}, 1),
1076             ];
1077             },
1078             #CMD_KEEP_ALIVE
1079             1070 => sub {
1080             return [_int_to_bytes(4, int(rand(0xFFFFFFFF)))];
1081             },
1082             #CMD_SEND_TEXT_CODE
1083             1080 => sub {
1084             my ($params) = @_;
1085             return [
1086             _int_to_bytes(2, length($params->{text_code})+1),
1087             _str_to_bytes($params->{text_code}, 1),
1088             _int_to_bytes(2, 0x05)
1089             ];
1090             },
1091             #CMD_ACK_MESSAGES
1092             1090 => sub {
1093             return [_int_to_bytes(4, int(rand(0xFFFFFFFF)))];
1094             },
1095             #CMD_LOGIN_1
1096             1100 => sub {
1097             return [_int_to_bytes(4, int(rand(0xFFFFFFFF)))];
1098             },
1099             #CMD_MSG_TO_NEW_USER
1100             1110 => sub {
1101             },
1102             #CMD_INFO_REQ
1103             1120 => sub {
1104             my ($params) = @_;
1105             return [_int_to_bytes(4, $params->{uin})];
1106             },
1107             #CMD_EXT_INFO_REQ
1108             1130 => sub {
1109             my ($params) = @_;
1110             return [_int_to_bytes(4, $params->{uin})];
1111             },
1112             #CMD_CHANGE_PW
1113             1180 => sub {
1114             },
1115             #CMD_NEW_USER_INFO
1116             1190 => sub {
1117             my ($params) = @_;
1118             return [
1119             _int_to_bytes(2, length($params->{nick})+1),
1120             _str_to_bytes($params->{nick}, 1),
1121             _int_to_bytes(2, length($params->{first})+1),
1122             _str_to_bytes($params->{first}, 1),
1123             _int_to_bytes(2, length($params->{last})+1),
1124             _str_to_bytes($params->{last}, 1),
1125             _int_to_bytes(2, length($params->{email})+1),
1126             _str_to_bytes($params->{email}, 1),
1127             _int_to_bytes(1, 0x01),
1128             _int_to_bytes(1, 0x01),
1129             _int_to_bytes(1, 0x01)
1130             ];
1131             },
1132             #CMD_UPDATE_EXT_INFO
1133             1200 => sub {
1134             },
1135             #CMD_QUERY_SERVERS
1136             1210 => sub {
1137             },
1138             #CMD_QUERY_ADDONS
1139             1220 => sub {
1140             },
1141             #CMD_STATUS_CHANGE
1142             1240 => sub {
1143             my ($params) = @_;
1144             return [_int_to_bytes(4, $params->{status})];
1145             },
1146             #CMD_NEW_USER_1
1147             1260 => sub {
1148             },
1149             #CMD_UPDATE_INFO
1150             1290 => sub {
1151             my ($params) = @_;
1152             return [
1153             _int_to_bytes(2, length($params->{nick})+1),
1154             _str_to_bytes($params->{nick}, 1),
1155             _int_to_bytes(2, length($params->{first})+1),
1156             _str_to_bytes($params->{first}, 1),
1157             _int_to_bytes(2, length($params->{last})+1),
1158             _str_to_bytes($params->{last}, 1),
1159             _int_to_bytes(2, length($params->{email})+1),
1160             _str_to_bytes($params->{email}, 1)
1161             ];
1162             },
1163             #CMD_AUTH_UPDATE
1164             1300 => sub {
1165             },
1166             #CMD_KEEP_ALIVE2
1167             1310 => sub {
1168             return [_int_to_bytes(4, int(rand(0xFFFFFFFF)))];
1169             },
1170             #CMD_LOGIN_2
1171             1320 => sub {
1172             },
1173             #CMD_ADD_TO_LIST
1174             1340 => sub {
1175             my ($params) = @_;
1176             return [_int_to_bytes(4, $params->{uin})];
1177             },
1178             #CMD_RAND_SET
1179             1380 => sub {
1180             my ($params) = @_;
1181             return [_int_to_bytes(4, $params->{rand_group})];
1182             },
1183             #CMD_RAND_SEARCH
1184             1390 => sub {
1185             my ($params) = @_;
1186             return [_int_to_bytes(2, $params->{rand_group})];
1187             },
1188             #CMD_META_USER
1189             1610 => sub {
1190             my ($params) = @_;
1191              
1192             # Thanks to Nezar Nielsen for this handler (wow!)
1193             # (cleaned up and modified slightly by JLM 2/25/2001)
1194              
1195             # convert string to numeric code if necessary
1196             $params->{subcmd} = $meta_codes{$params->{subcmd}}
1197             if exists($meta_codes{$params->{subcmd}});
1198              
1199             my $return=[];
1200             push @$return, _int_to_bytes(2, $params->{subcmd});
1201              
1202             if ($params->{subcmd} == $meta_codes{GENERAL_INFO}) {
1203             #1001 - serverresponse: 100
1204             foreach ('nick', 'first', 'last',
1205             'primary_email', 'secondary_email', 'old_email',
1206             'city', 'state', 'phone', 'fax', 'street', 'cellular') {
1207             push @$return, _int_to_bytes(2, length($params->{$_} || '')+1);
1208             push @$return, _str_to_bytes($params->{$_} || '', 1);
1209             }
1210             # observe: this has changed since the spec was written,
1211             # zipcode is also sent as text with null-termination.
1212             push @$return, _int_to_bytes(2, length($params->{zipcode} || '')+1);
1213             push @$return, _str_to_bytes($params->{zipcode} || '',1);
1214             push @$return, _int_to_bytes(2, $params->{country} || 0);
1215             # timezone - don't know the spec for this
1216             push @$return, _int_to_bytes(1, $params->{timezone} || 0);
1217             push @$return, _int_to_bytes(1, $params->{authorize} || 0);
1218             push @$return, _int_to_bytes(1, $params->{webaware} || 0);
1219             push @$return, _int_to_bytes(1, $params->{hideip} || 0);
1220              
1221             } elsif ($params->{subcmd} == $meta_codes{WORK_INFO}) {
1222             #1011 - serverresponse: 110
1223             # FIX: Does not work, allthough it sends the info exactly like ICQ 2000b
1224             # (which sends it through TCP).
1225             foreach ('city', 'state', 'phone', 'fax', 'addr') {
1226             push @$return, _int_to_bytes(2, length($params->{$_} || '')+1);
1227             push @$return, _str_to_bytes($params->{$_} || '', 1);
1228             }
1229             # i sniffed my client (ICQ 2000b), and i can see that it sends the zipcode
1230             # like the other null-terminated strings
1231             push @$return, _int_to_bytes(2, length($params->{zipcode} || '')+1);
1232             push @$return, _str_to_bytes($params->{zipcode} || '', 1);
1233             push @$return, _int_to_bytes(2, $params->{country} || 0);
1234             foreach ('company', 'dept', 'pos') {
1235             push @$return, _int_to_bytes(2, length($params->{$_} || '')+1);
1236             push @$return, _str_to_bytes($params->{$_} || '', 1);
1237             }
1238             # got occupation codes from the Icqlib source, and sniffed my way to see that
1239             # my icq client sends two bytes here with the number according to what i chose.
1240             push @$return, _int_to_bytes(2, $params->{occupation});
1241             push @$return, _int_to_bytes(2, length($params->{url} || '') + 1);
1242             push @$return, _str_to_bytes($params->{url} || '', 1);
1243              
1244             } elsif ($params->{subcmd} == $meta_codes{MORE_INFO}) {
1245             #metauser code: 1021 - serverresponse: 120
1246             push @$return, _int_to_bytes(2, $params->{age} || 0xFFFF);
1247             push @$return, _int_to_bytes(1, $sex_codes{uc($params->{sex})} || $sex_codes{UNSPECIFIED});
1248             push @$return, _int_to_bytes(2, length($params->{url} || '')+1);
1249             push @$return, _str_to_bytes($params->{url} || '', 1);
1250             push @$return, _int_to_bytes(2, $params->{year});
1251             push @$return, _int_to_bytes(1, $params->{month} || 1);
1252             push @$return, _int_to_bytes(1, $params->{day} || 1);
1253             # three spoken languages (or set to 0)
1254             push @$return, _int_to_bytes(1, $params->{lang1} || 0);
1255             push @$return, _int_to_bytes(1, $params->{lang2} || 0);
1256             push @$return, _int_to_bytes(1, $params->{lang3} || 0);
1257              
1258             } elsif ($params->{subcmd} == $meta_codes{ABOUT_INFO}) {
1259             #1030 - serverresponse: 130
1260             push @$return, _int_to_bytes(2, length($params->{about} || '')+1);
1261             push @$return, _str_to_bytes($params->{about} || '',1);
1262             }
1263              
1264             return $return;
1265             },
1266             #CMD_INVIS_LIST
1267             1700 => sub {
1268             my ($params) = @_;
1269             my ($ret, $num);
1270              
1271             $num = $params->{num_contacts};
1272             croak ("120 contact limit, send more than one packet")
1273             if ($num > 120);
1274              
1275             $ret = [];
1276             push @$ret, _int_to_bytes(1, $num);
1277             for (my $i = 0; $i < $num; $i++){
1278             push @$ret, _int_to_bytes(4, $params->{uins}[$i]);
1279             }
1280             return $ret;
1281             },
1282             #CMD_VIS_LIST
1283             1710 => sub {
1284             my ($params) = @_;
1285             my ($ret, $num);
1286              
1287             $num = $params->{num_contacts};
1288             croak ("120 contact limit, send more than one packet")
1289             if ($num > 120);
1290              
1291             $ret = [];
1292             push @$ret, _int_to_bytes(1, $num);
1293             for (my $i = 0; $i < $num; $i++){
1294             push @$ret, _int_to_bytes(4, $params->{uins}[$i]);
1295             }
1296             return $ret;
1297             },
1298             #CMD_UPDATE_LIST
1299             1720 => sub {
1300             my ($params) = @_;
1301             return [
1302             _int_to_bytes(4, $params->{uin}),
1303             _int_to_bytes(1, $params->{list}),
1304             _int_to_bytes(1, $params->{remadd})
1305             ];
1306             },
1307             );
1308              
1309             %_msg_builders = (
1310             #MSG_TEXT
1311             1 => sub {
1312             my ($params) = @_;
1313             return [_str_to_bytes($params->{text})];
1314             },
1315             #MSG_URL
1316             4 => sub {
1317             my ($params) = @_;
1318             my (@ret, $first);
1319             $first = 1;
1320             foreach ('description', 'url'){
1321             push @ret, (0xFE) if !$first;
1322             $first = 0 if $first;
1323             push @ret, _str_to_bytes($params->{$_});
1324             }
1325             return \@ret;
1326             },
1327             #MSG_AUTH_REQ
1328             6 => sub {
1329             my ($params) = @_;
1330             my (@ret, $first);
1331             $first = 1;
1332             foreach ('nickname', 'firstname', 'lastname', 'email', 'reason'){
1333             push @ret, (0xFE) if !$first;
1334             $first = 0 if $first;
1335             push @ret, _str_to_bytes($params->{$_});
1336             }
1337             return \@ret;
1338             },
1339             #MSG_AUTH
1340             8 => sub {
1341             my ($params) = @_;
1342             my @ret = undef;
1343             return \@ret;
1344             },
1345             #MSG_USER_ADDED message
1346             12 => sub {
1347             my ($params) = @_;
1348             my (@ret, $first);
1349             $first = 1;
1350             foreach ('nickname', 'firstname', 'lastname', 'email'){
1351             push @ret, (0xFE) if !$first;
1352             $first = 0 if $first;
1353             push @ret, _str_to_bytes($params->{$_});
1354             }
1355             return \@ret;
1356             },
1357             #MSG_CONTACTS message
1358             19 => sub {
1359             my ($params) = @_;
1360             my (@ret, $num_uins);
1361             $num_uins = keys(%{$params->{contacts}});
1362             push @ret, _str_to_bytes($num_uins);
1363             foreach (%{$params->{contacts}}) {
1364             push @ret, (0xFE);
1365             push @ret, _str_to_bytes($_);
1366             }
1367             return \@ret;
1368             }
1369             );
1370              
1371             # == DEVELOPERS' NOTE ==
1372             # (should this be in pod???)
1373             #
1374             # An event is stored as a hash ref (note: not a full blessed object).
1375             # Here are the fields (keys) in the hash and their descriptions:
1376             #
1377             # command - The numeric command code
1378             # seq_num_1 - Sequence number 1, which is incremented in every packet
1379             # seq_num_2 - Sequence number 2, which is incremented in most (?) packets
1380             # params - The raw array of bytes that make up the parameters
1381             # is_ack - Set to 1 if this is an ACK event, otherwise not present
1382             # is_multi - Set to 1 if this is a multi packet, otherwise not present
1383             #
1384             # The following fields exist only in outgoing events:
1385             #
1386             # send_last - time of the last resend, as time() (seconds since the epoch)
1387             # send_count - number of times the event has been sent to the server
1388             # send_now - set to 1 when the event is due to be resent
1389              
1390             # ====
1391             # private methods
1392             # ====
1393              
1394             # look for data coming from the server and build events out of it
1395             sub _do_incoming {
1396 0     0     my ($self) = @_;
1397 0           my ($raw, @packet, $event);
1398              
1399 0           while (IO::Select->select($self->{_select}, undef, undef, .00001)) {
1400 0           $self->{_socket}->recv($raw, 10000);
1401 0           @packet = split('', $raw);
1402              
1403 0           foreach (@packet) {
1404 0           $_ = ord($_);
1405             }
1406              
1407             # build the event
1408 0           $event = $self->_parse_packet(\@packet);
1409              
1410             # DEBUG: print out incoming packets
1411 0 0         if ($self->{_debug}) {
1412 0           print '<-- event #', $event->{seq_num_1}, ' ';
1413 0           _print_packet(\@packet);
1414 0           print " <", $event->{command},">\n";
1415             }
1416              
1417             # put acks in separate array because they will be handled immediately.
1418 0 0         if ( $event->{is_ack} ) {
1419 0           push @{$self->{_acks_incoming}}, $event;
  0            
1420             }
1421             # stick everything that hasn't already been seen in the incoming events list
1422             else {
1423 0           my $not_in_array = 1;
1424 0           foreach my $seq ( @{$self->{_seen_seq}} ) {
  0            
1425 0 0         if ($seq == $event->{seq_num_1}) {
1426 0           $not_in_array = 0;
1427 0           last;
1428             }
1429             }
1430 0 0         if ($not_in_array) {
1431 0           push @{$self->{_events_incoming}}, $event;
  0            
1432 0           push @{$self->{_seen_seq}}, $event->{seq_num_1};
  0            
1433              
1434 0 0         if (@{$self->{_seen_seq}} > 20) {
  0            
1435 0           shift @{$self->{_seen_seq}};
  0            
1436             }
1437             }
1438            
1439             } # end else
1440             } # end while
1441             } # end sub _do_incoming
1442              
1443              
1444             # for each incoming ack, remove corresponding outgoing event from queue,
1445             # and send out acks for every non-ack event we received
1446             sub _do_acks {
1447 0     0     my ($self) = @_;
1448 0           my (@params);
1449              
1450             # incoming ACKs are received, delete corrosponding outgoing events
1451 0           foreach ( @{$self->{_acks_incoming}} ) {
  0            
1452              
1453             #DEBUG: print out incoming ACKS
1454 0 0         print " (ACK #", $_->{seq_num_1}, ")\n"
1455             if $self->{_debug};
1456              
1457             # remove the matching outgoing event that got ACK from server
1458 0 0 0       if ( defined $self->{_events_outgoing}[0] &&
1459             $_->{seq_num_1} == $self->{_events_outgoing}[0]{seq_num_1} ) {
1460              
1461 0           shift @{$self->{_events_outgoing}};
  0            
1462 0           $self->{_seq_num_1}++; # increment seq_num_1 because event was sucessfully received
1463 0           $self->{_seq_num_2}++; # increment seq_num_1 because event was sucessfully received
1464             }
1465             } # end foreach
1466              
1467             # remove all incoming acks because they're all processed
1468 0           $self->{_acks_incoming} = [];
1469              
1470             # got some incoming events, send some loving ACKs home
1471             # to tell them events are successfully received.
1472 0           foreach ( @{$self->{_events_incoming}} ) {
  0            
1473              
1474 0           push @{$self->{_acks_outgoing}}, { command => 10,
  0            
1475             is_ack => 1,
1476             seq_num_1 => $_->{seq_num_1},
1477             seq_num_2 => $_->{seq_num_2},
1478             params => [_int_to_bytes(4, int(rand(0xFFFFFFFF)))]
1479             };
1480             } # end foreach
1481             } # end sub _do_acks
1482              
1483              
1484             # split the sub-events out of all the multi events on the incoming
1485             # queue, put the sub-events on the queue, and remove the multi
1486             sub _do_multis {
1487 0     0     my ($self) = @_;
1488 0           my ($event, $i);
1489              
1490 0           $i = 0;
1491             # for every incoming packet
1492 0           foreach (@{$self->{_events_incoming}}) {
  0            
1493             # if it's not a multi, skip it
1494 0 0         if (!$_->{is_multi}) {
1495 0           $i++;
1496 0           next;
1497             }
1498              
1499 0           my (@newevents, $offset);
1500             #for each packet in the multi packet..
1501 0           $offset = 1;
1502 0           for (my $i = 0; $i < _bytes_to_int($_->{params}, 0, 1); $i++) {
1503             # build the event
1504 0           my $packet_length = _bytes_to_int($_->{params}, $offset, 2);
1505 0           $offset += 2;
1506 0           my @packet = @{$_->{params}}[$offset..($offset + $packet_length)-1];
  0            
1507 0           $offset += $packet_length;
1508              
1509             # build the event and queue it
1510 0           $event = $self->_parse_packet(\@packet);
1511 0           push @{$self->{_events_incoming}}, $event;
  0            
1512              
1513             # DEBUG: print out incoming packets
1514 0 0         if ($self->{_debug}) {
1515 0           print ' <+ multi #', $event->{seq_num_1}, ' ';
1516 0           _print_packet(\@packet);
1517 0           print " <", $event->{command},">\n";
1518             }
1519              
1520             } # end for
1521              
1522             # remove the multi from the queue
1523 0           splice(@{$self->{_events_incoming}}, $i, 1);
  0            
1524              
1525             } # end foreach
1526             } # end sub _do_multis
1527              
1528              
1529             # if it's time, queue a keepalive packet as close to the head of the queue
1530             # as possible
1531             sub _do_keepalives {
1532 0     0     my ($self) = @_;
1533 0           my ($now);
1534              
1535             # grab current time
1536 0           $now = time();
1537              
1538             # FIX: make the time configgable
1539             # Keepalive every 2 minutes, as recommanded by ICQ V5.
1540 0 0         if ($self->{_last_keepalive} + 2*60 < $now) {
1541              
1542             #DEBUG: print out keepalive
1543 0 0         print "=== queueing keepalive\n"
1544             if $self->{_debug};
1545              
1546 0           $self->{_last_keepalive} = $now;
1547 0           $self->send_event('CMD_KEEP_ALIVE', undef, 1);
1548              
1549             } # end if
1550             } #end _do_keepalives
1551              
1552              
1553             # see if the top event needs to be resent, and remove it from the
1554             # outgoing queue if it's been resent too many times
1555             sub _do_timeouts {
1556 0     0     my ($self) = @_;
1557              
1558             # FIX: make the time configgable
1559 0 0 0       if ( defined $self->{_events_outgoing}[0] &&
1560             $self->{_events_outgoing}[0]{send_last} + 10 <= time() ) {
1561              
1562 0 0         if ( $self->{_events_outgoing}[0]{send_count} >= 6 ) {
1563              
1564             # FIX: it would probably be wise to inform the programmer that
1565             # their event couldn't be sent.
1566              
1567             #DEBUG: print out timeout
1568 0 0         print "=== too many resends for ", $self->{_events_outgoing}[0]{seq_num_1}, "\n"
1569             if $self->{_debug};
1570              
1571             # out of tries, you loose, next!
1572 0           shift @{$self->{_events_outgoing}};
  0            
1573             }
1574             else {
1575 0           $self->{_events_outgoing}[0]{send_now} = 1;
1576             }
1577             }
1578             } # end sub _do_timeouts
1579              
1580              
1581             # call the handler for each event on the incoming queue
1582             sub _do_handlers {
1583 0     0     my ($self) = @_;
1584              
1585 0           foreach ( @{$self->{_events_incoming}} ) {
  0            
1586              
1587             # if a handler for this event has been registered
1588 0 0         if (exists $self->{_handlers}{$_->{command}} ) {
1589             # parse the raw event params
1590 0 0         &{$_parsers{$_->{command}}}($_)
  0            
1591             if ( exists $_parsers{$_->{command}} );
1592              
1593             #call the handler
1594 0           &{$self->{_handlers}{$_->{command}}}($self, $_);
  0            
1595              
1596             } # end if
1597             } # end foreach
1598              
1599             # empty incoming queue
1600 0           $self->{_events_incoming} = [];
1601             }
1602              
1603              
1604             # send all outgoing acks, send the top event on the regular
1605             # outgoing queue if it's marked as ready to go
1606             sub _do_outgoing {
1607 0     0     my ($self) = @_;
1608              
1609 0           foreach (@{$self->{_acks_outgoing}}) {
  0            
1610              
1611             #DEBUG: print out sending acks
1612 0 0         print "--> ACK #", $_->{seq_num_1}, "\n"
1613             if $self->{_debug};
1614              
1615 0           $self->_deliver_event($_);
1616              
1617             } # end foreach
1618              
1619             # clear outgoing ack array
1620 0           $self->{_acks_outgoing} = [];
1621              
1622 0 0 0       if ( $self->{_events_outgoing}[0] and
1623             $self->{_events_outgoing}[0]{send_now} ) {
1624              
1625 0           $self->{_events_outgoing}[0]{send_now} = 0;
1626 0           $self->{_events_outgoing}[0]{send_last} = time();
1627 0           $self->{_events_outgoing}[0]{send_count}++;
1628 0           $self->{_events_outgoing}[0]{seq_num_1} = $self->{_seq_num_1};
1629 0           $self->{_events_outgoing}[0]{seq_num_2} = $self->{_seq_num_2};
1630              
1631             #DEBUG: print out outgoing event
1632 0 0         print "--> event #", $self->{_events_outgoing}[0]{seq_num_1},
1633             " <" , $self->{_events_outgoing}[0]{command}, ">\n"
1634             if $self->{_debug};
1635              
1636 0           $self->_deliver_event($self->{_events_outgoing}[0]);
1637              
1638             } # end if
1639             } # end sub _do_outgoing
1640              
1641              
1642             # adds an event to the queue, with an optional priority flag
1643             # (priority means the event is put as close to the head as
1644             # possible without interrupting a "live" event)
1645             sub _queue_event {
1646 0     0     my ($self, $event, $priority) = @_;
1647              
1648 0           $event->{send_count} = 0; # not resent at all yet
1649 0           $event->{send_last} = 0; # a time as far in the past as possible
1650 0           $event->{send_now} = 1; # send me right away when I get to the head of the queue
1651              
1652 0 0         if (!$priority) {
1653             # regular event; just slap it on the tail of the queue
1654              
1655 0           push @{$self->{_events_outgoing}}, $event;
  0            
1656              
1657             } else {
1658             # priority event; stick it on top, or just after that if top event is "live"
1659              
1660 0 0 0       if (
      0        
1661             # top event not defined (queue empty)
1662             !defined $self->{_events_outgoing}[0] or
1663             # top event is defined but has not been sent out yet (not live)
1664             (defined $self->{_events_outgoing}[0] and
1665             $self->{_events_outgoing}[0]{send_count} == 0)
1666             ) {
1667             # then stick event on the head of the queue
1668 0           unshift @{$self->{_events_outgoing}}, $event;
  0            
1669             } else {
1670             # there is a live event on the top of the queue (we're waiting for it to be ACKed);
1671             # queue this event AFTER the live event so as not to interrupt it
1672 0           splice @{$self->{_events_outgoing}}, 1, 0, $event;
  0            
1673             }
1674              
1675             }
1676             }
1677              
1678              
1679             # takes an event, builds a UDP packet, and sends it to the server
1680             sub _deliver_event {
1681 0     0     my ($self, $event) = @_;
1682 0           my ($packet, $checkcode, $raw, $length);
1683              
1684 0           $packet = $self->_make_header($event);
1685 0           push @$packet, @{$event->{params}};
  0            
1686              
1687 0           $checkcode = $self->_calc_checkcode($packet);
1688              
1689 0           $length = @$packet;
1690 0           $raw = $self->_encrypt($packet, $checkcode); # now $raw might have extra 0-bytes
1691 0           substr($raw, $length) = ''; # truncate data to correct length
1692              
1693 0           $self->{_socket}->send($raw);
1694             }
1695              
1696              
1697             # ICQ Packet Header (client side)
1698             # ===============================
1699             # Length Content (if fixed) Designation Description
1700             # ------ ------------------ ----------- -----------
1701             # 2 bytes 05 00 VERSION Protocol version
1702             # 4 bytes 00 00 00 00 ZERO Just zeros, purpouse unknown
1703             # 4 bytes xx xx xx xx UIN Your (the client's) UIN
1704             # 4 bytes xx xx xx xx SESSION_ID Used to prevent 'spoofing'. See below.
1705             # 2 bytes xx xx COMMAND
1706             # 2 bytes xx xx SEQ_NUM1 Starts at a random number
1707             # 2 bytes xx xx SEQ_NUM2 Starts at 1
1708             # 4 bytes xx xx xx xx CHECKCODE
1709             # variable xx ... PARAMETERS Parameters for the command being sent
1710              
1711             sub _make_header {
1712 0     0     my ($self, $event) = @_;
1713 0           my ($header);
1714              
1715 0           $header = [];
1716 0           push @$header, _int_to_bytes(2, 5);
1717 0           push @$header, _int_to_bytes(4, 0);
1718 0           push @$header, _int_to_bytes(4, $self->{_uin});
1719 0           push @$header, _int_to_bytes(4, $self->{_session_id});
1720 0           push @$header, _int_to_bytes(2, $event->{command});
1721 0           push @$header, _int_to_bytes(2, $event->{seq_num_1});
1722 0           push @$header, _int_to_bytes(2, $event->{seq_num_2});
1723 0           push @$header, _int_to_bytes(4, 0); # checkcode gets set later
1724              
1725 0           return $header;
1726             }
1727              
1728              
1729             sub _calc_checkcode {
1730 0     0     my ($self, $packet) = @_;
1731 0           my ($number1, $number2, $r1, $r2, @checkcode);
1732              
1733             # NUMBER1 = B8 B4 B2 B6
1734 0           $number1 = $packet->[8];
1735 0           $number1 <<= 8;
1736 0           $number1 |= $packet->[4];
1737 0           $number1 <<= 8;
1738 0           $number1 |= $packet->[2];
1739 0           $number1 <<= 8;
1740 0           $number1 |= $packet->[6];
1741              
1742             # PL = Packet length
1743             # R1 = A random number beetween 0x18 and PL
1744             # R2 = Another random number beetween 0 and 0xFF
1745             # (the max here may end up 1 too small.. who cares)
1746              
1747 0           $r1 = int(rand(@$packet - 0x18)) + 0x18;
1748 0           $r2 = int(rand(0xFF));
1749              
1750 0           $number2 = $r1;
1751 0           $number2 <<= 8;
1752 0           $number2 |= $packet->[$r1];
1753 0           $number2 <<= 8;
1754 0           $number2 |= $r2;
1755 0           $number2 <<=8;
1756 0           $number2 |= $_table[$r2];
1757 0           $number2 ^= 0x00FF00FF;
1758              
1759 0           @checkcode = _int_to_bytes(4, $number1 ^ $number2);
1760 0           splice(@$packet, 0x14, 0x04, @checkcode);
1761              
1762 0           return _bytes_to_int(\@checkcode, 0, 4);
1763             }
1764              
1765              
1766             sub _encrypt {
1767 0     0     my ($self, $packet, $cc) = @_;
1768 0           my ($code, @plain, @dwords, $i, $raw, $cc_raw);
1769              
1770 0           $code = Math::BigInt->new(@$packet * 0x68656C6C + $cc);
1771 0           $code = $code->band(Math::BigInt->new(0xFFFFFFFF));
1772              
1773 0           @plain = splice(@$packet, 0, 0xA, ());
1774 0           $i = 0;
1775 0           while ($i < @$packet) {
1776 0           push @dwords, _bytes_to_int($packet, $i, 4);
1777 0           $i += 4;
1778             }
1779              
1780 0           $i = 0xA;
1781 0           foreach (@dwords) {
1782 0           $_ = Math::BigInt->new($_);
1783 0           $_ = $_->bxor(Math::BigInt->new($code + $_table[$i & 0xFF]));
1784 0           $i += 4;
1785             }
1786              
1787             $cc =
1788 0           (($cc & 0x0000001F) << 0x0C) |
1789             (($cc & 0x03E003E0) << 0x01) |
1790             (($cc & 0xF8000400) >> 0x0A) |
1791             (($cc & 0x0000F800) << 0x10) |
1792             (($cc & 0x041F0000) >> 0x0F);
1793 0           for ($i = 0; $i < 4; $i++) {
1794 0           $cc_raw .= chr($cc & 0xFF);
1795 0           $cc >>= 8;
1796             }
1797              
1798 0           $raw = '';
1799 0           foreach (@plain) {
1800 0           $raw .= chr($_);
1801             }
1802 0           foreach (@dwords) {
1803 0           for ($i = 0; $i < 4; $i++) {
1804 0           $raw .= chr($_ & 0xFF);
1805 0           $_ >>= 8;
1806             }
1807             }
1808 0           substr($raw, 0x14, 4, $cc_raw);
1809              
1810 0           return $raw;
1811             }
1812              
1813              
1814             # ICQ Packet Header (server side)
1815             # ===============================
1816             # Length Content (if fixed) Designation Description
1817             # 2 bytes 05 00 VERSION Protocol version
1818             # 1 byte 00 ZERO Unknown
1819             # 4 bytes xx xx xx xx SESSION_ID Same as in your login packet.
1820             # 2 bytes xx xx COMMAND
1821             # 2 bytes xx xx SEQ_NUM1 Sequence 1
1822             # 2 bytes xx xx SEQ_NUM2 Sequence 2
1823             # 4 bytes xx xx xx xx UIN Your (the client's) UIN
1824             # 4 bytes xx xx xx xx CHECKCODE
1825             # variable xx ... PARAMETERS Parameters for the command being sent
1826              
1827             sub _parse_packet {
1828 0     0     my ($self, $packet) = @_;
1829 0           my ($event, @params);
1830              
1831             # Thanks to Robin Fisher for this fix for V3 packets.
1832             # if it's a version 3 packet, change the header to match a version 5 packet.
1833             # (apparently, the only difference in V5 is the addition of the session id)
1834 0 0         if (_bytes_to_int($packet, 0, 2) == 3) {
1835 0           print("OOPS: Server sent a V3 packet. Converting to V5.\n");
1836 0           splice @$packet, 0, 2, (5, 0, 0, _int_to_bytes(4, $self->{_session_id}));
1837             }
1838              
1839             # sanity checks
1840 0 0         if (_bytes_to_int($packet, 3, 4) != $self->{_session_id}) {
1841 0 0         print("OOPS: Server told us the wrong session ID!\n") if $self->{_debug};
1842 0           $self->disconnect;
1843             }
1844 0 0         if (_bytes_to_int($packet, 13, 4) != $self->{_uin}) {
1845 0 0         print("OOPS: Server told us the wrong UIN!\n") if $self->{_debug};
1846 0           $self->disconnect;
1847             }
1848              
1849             # fill in the event's fields
1850 0           $event = {};
1851 0           $event->{command} = _bytes_to_int($packet, 7, 2);
1852 0           $event->{seq_num_1} = _bytes_to_int($packet, 9, 2);
1853 0           $event->{seq_num_2} = _bytes_to_int($packet, 11, 2);
1854 0 0         $event->{is_ack} = 1 if $event->{command} == 10;
1855 0 0         $event->{is_multi} = 1 if $event->{command} == 530;
1856 0           @params = @$packet[21..@$packet-1];
1857 0           $event->{params} = \@params;
1858              
1859 0           return $event;
1860             }
1861              
1862              
1863             # ====
1864             # private functions
1865             # (they're not methods, so don't call them on a Net::ICQ object!)
1866             # ====
1867              
1868              
1869             # _int_to_bytes(bytes, val)
1870             #
1871             # Converts into an array of bytes and returns it.
1872             # If is too big, only the least significant bytes are
1873             # returned. The array is in little-endian order.
1874             #
1875             # _int_to_bytes(2, 0x1234) == (0x34, 0x12)
1876             # _int_to_bytes(2, 0x12345) == (0x45, 0x23)
1877              
1878             sub _int_to_bytes {
1879 0     0     my ($bytes, $val) = @_;
1880 0           my (@ret);
1881              
1882 0           for (my $i=0; $i<$bytes; $i++) {
1883 0           push @ret, ($val >> ($i*8) & 0xFF);
1884             }
1885              
1886 0           return @ret;
1887             }
1888              
1889              
1890             # _str_to_bytes(str, add_zero)
1891             #
1892             # Converts into an array of bytes and returns it. If
1893             # is true, makes the array null-terminated (adds a 0 as a the last byte).
1894             #
1895             # _str_to_bytes('foo') == ('f', 'o', 'o')
1896             # _str_to_bytes('foo', 1) == ('f', 'o', 'o', 0)
1897              
1898             sub _str_to_bytes {
1899 0     0     my ($string, $add_zero) = @_;
1900 0           my (@ret);
1901              
1902             # the ?: keeps split() from complaining about undefined values
1903 0 0         foreach (split('', defined($string) ? $string : '')) {
1904 0           push @ret, ord($_);
1905             }
1906 0 0         push @ret, 0 if $add_zero;
1907              
1908 0           return @ret;
1909             }
1910              
1911              
1912             # _bytes_to_int(array_ref, start, bytes)
1913             #
1914             # Converts the byte array referenced by , starting at offset
1915             # and running for values, into an integer, and returns it.
1916             # The bytes in the array must be in little-endian order.
1917             #
1918             # _bytes_to_int([0x34, 0x12, 0xAA, 0xBB], 0, 2) == 0x1234
1919             # _bytes_to_int([0x34, 0x12, 0xAA, 0xBB], 2, 1) == 0xAA
1920              
1921             sub _bytes_to_int {
1922 0     0     my ($array, $start, $bytes) = @_;
1923 0           my ($ret);
1924              
1925 0           $ret = 0;
1926 0           for (my $i = $start+$bytes-1; $i >= $start; $i--) {
1927 0           $ret <<= 8;
1928 0   0       $ret |= ($array->[$i] or 0);
1929             }
1930              
1931 0           return $ret;
1932             }
1933              
1934              
1935             # _bytes_to_str(array_ref, start, bytes)
1936             #
1937             # Converts the byte array referenced by , starting at offset
1938             # and running for values, into a string, and returns it.
1939             #
1940             # _bytes_to_str([0x12, 'f', 'o', 'o', '!'], 1, 3) == 'foo'
1941              
1942             sub _bytes_to_str {
1943             # thanks to Dimitar Peikov for the fix
1944 0     0     my ($array, $start, $bytes) = @_;
1945 0           my ($ret);
1946              
1947 0           $ret = '';
1948 0           for (my $i = $start; $i < $start+$bytes; $i++) {
1949 0 0         $ret .= $array->[$i] ? chr($array->[$i]) : '';
1950             }
1951              
1952 0           return $ret;
1953             }
1954              
1955             # _bytes_to_strlist(array_ref)
1956             #
1957             # Converts the byte array referenced by into an array of
1958             # strings, and returns a reference to the array.
1959             # The strings in the byte array must be separated by the byte 0xFE, and the
1960             # end of the last string to be converted must be followed by the byte 0x00.
1961             #
1962             # _bytes_to_strlist(['a', 'b', 0xFE, 'x', 'y', 'z', 0x00]) == ['ab', 'xyz']
1963              
1964             sub _bytes_to_strlist {
1965 0     0     my ($array) = @_;
1966 0           my (@ret, $str);
1967              
1968 0           $str = '';
1969 0           foreach (@$array) {
1970 0 0         if ($_ == 0xFE) {
1971 0           push @ret, $str;
1972 0           $str = '';
1973             }
1974             else {
1975 0           $str .= chr($_);
1976             }
1977             }
1978              
1979             # remove last 0 from the last string
1980 0           substr($str, -1, 1, '');
1981 0           push @ret, $str;
1982 0           return @ret;
1983             }
1984              
1985              
1986             # print_packet(packet_ref)
1987             #
1988             # Dumps the ICQ packet contained in the byte array referenced by
1989             # to STDOUT. The format is '[byte0 byte1 ...]'
1990             # where byte0 byte1 ... are all the actual bytes
1991             # that make up the packet, in 2-character 0-padded hex format.
1992             # For instance, a dump might begin like this:
1993             # [02 BD 14 4A ...
1994              
1995             sub _print_packet {
1996 0     0     my ($packet) = @_;
1997              
1998 0           print "[";
1999 0           foreach (@$packet) {
2000 0           print sprintf("%02X ", $_);
2001             }
2002 0           print "]";
2003              
2004             }
2005              
2006             1;
2007