File Coverage

blib/lib/POE/Component/Client/opentick/ProtocolMsg.pm
Criterion Covered Total %
statement 111 186 59.6
branch 13 30 43.3
condition 0 6 0.0
subroutine 32 43 74.4
pod 4 4 100.0
total 160 269 59.4


line stmt bran cond sub pod time code
1             package POE::Component::Client::opentick::ProtocolMsg;
2             #
3             # opentick.com POE client
4             #
5             # Protocol Message abstract base class
6             #
7             # infi/2008
8             #
9             # $Id: ProtocolMsg.pm 56 2009-01-08 16:51:14Z infidel $
10             #
11             # See docs/implementation-notes.txt for a detailed explanation of how
12             # this module works.
13             #
14             # Full POD documentation after __END__
15             #
16              
17 2     2   11 use strict;
  2         53  
  2         73  
18 2     2   11 use warnings;
  2         6  
  2         52  
19 2     2   10 use Carp qw( croak );
  2         3  
  2         120  
20             $Carp::CarpLevel = 2;
21 2     2   10 use POE;
  2         3  
  2         15  
22 2     2   635 use Data::Dumper;
  2         9  
  2         82  
23              
24             # Ours.
25 2     2   10 use POE::Component::Client::opentick::Constants;
  2         4  
  2         472  
26 2     2   13 use POE::Component::Client::opentick::Util;
  2         3  
  2         3461  
27 2     2   190 use POE::Component::Client::opentick::Error;
  2         5  
  2         1138  
28 2     2   2802 use POE::Component::Client::opentick::Record;
  2         6  
  2         75  
29 2     2   16 use POE::Component::Client::opentick::Output;
  2         4  
  2         159  
30              
31             ###
32             ### Variables
33             ###
34              
35 2     2   11 use vars qw( $VERSION $TRUE $FALSE $KEEP $DELETE );
  2         4  
  2         8165  
36              
37             ($VERSION) = q$Revision: 56 $ =~ /(\d+)/;
38             *TRUE = \1;
39             *FALSE = \0;
40             *KEEP = \0;
41             *DELETE = \1;
42              
43             my $packet_handler_states = {
44             cmds => {
45             OTConstant( 'OT_LOGIN' ) => '_ot_msg_login_o',
46             OTConstant( 'OT_LOGOUT' ) => '_ot_msg_generic_o',
47             OTConstant( 'OT_REQUEST_TICK_STREAM' ) => '_ot_msg_generic_o',
48             OTConstant( 'OT_CANCEL_TICK_STREAM' ) => '_ot_msg_generic_o',
49             OTConstant( 'OT_REQUEST_HIST_DATA' ) => '_ot_msg_generic_o',
50             OTConstant( 'OT_CANCEL_HIST_DATA' ) => '_ot_msg_generic_o',
51             OTConstant( 'OT_REQUEST_LIST_EXCHANGES' ) => '_ot_msg_generic_o',
52             OTConstant( 'OT_REQUEST_LIST_SYMBOLS' ) => '_ot_msg_generic_o',
53             OTConstant( 'OT_HEARTBEAT' ) => '_ot_msg_nobody_o',
54             OTConstant( 'OT_REQUEST_EQUITY_INIT' ) => '_ot_msg_generic_o',
55             OTConstant( 'OT_REQUEST_OPTION_CHAIN' ) => '_ot_msg_generic_o',
56             OTConstant( 'OT_CANCEL_OPTION_CHAIN' ) => '_ot_msg_generic_o',
57             OTConstant( 'OT_REQUEST_BOOK_STREAM' ) => '_ot_msg_generic_o',
58             OTConstant( 'OT_CANCEL_BOOK_STREAM' ) => '_ot_msg_generic_o',
59             OTConstant( 'OT_REQUEST_TICK_STREAM_EX' ) => '_ot_msg_generic_o',
60             OTConstant( 'OT_REQUEST_OPTION_CHAIN_EX' ) => '_ot_msg_generic_o',
61             OTConstant( 'OT_REQUEST_HIST_TICKS' ) => '_ot_msg_generic_o',
62             OTConstant( 'OT_REQUEST_SPLITS' ) => '_ot_msg_generic_o',
63             OTConstant( 'OT_REQUEST_DIVIDENDS' ) => '_ot_msg_generic_o',
64             OTConstant( 'OT_REQUEST_HIST_BOOKS' ) => '_ot_msg_generic_o',
65             OTConstant( 'OT_REQUEST_BOOK_STREAM_EX' ) => '_ot_msg_generic_o',
66             OTConstant( 'OT_REQUEST_OPTION_CHAIN_U' ) => '_ot_msg_generic_o',
67             OTConstant( 'OT_REQUEST_OPTION_INIT' ) => '_ot_msg_generic_o',
68             OTConstant( 'OT_REQUEST_LIST_SYMBOLS_EX' ) => '_ot_msg_generic_o',
69             OTConstant( 'OT_REQUEST_TICK_SNAPSHOT' ) => '_ot_msg_generic_o',
70             OTConstant( 'OT_REQUEST_OPTION_CHAIN_SNAPSHOT' ) => '_ot_msg_generic_o',
71             },
72             resp => {
73             OTConstant( 'OT_LOGIN' ) => '_ot_msg_login_i',
74             OTConstant( 'OT_LOGOUT' ) => '_ot_msg_logout_i',
75             OTConstant( 'OT_REQUEST_TICK_STREAM' ) => '_ot_msg_singledt_i',
76             OTConstant( 'OT_CANCEL_TICK_STREAM' ) => '_ot_msg_cancel_i',
77             OTConstant( 'OT_REQUEST_HIST_DATA' ) => '_ot_msg_multidt_i',
78             OTConstant( 'OT_CANCEL_HIST_DATA' ) => '_ot_msg_nobody_i',
79             OTConstant( 'OT_REQUEST_LIST_EXCHANGES' ) => '_ot_msg_listex_i',
80             OTConstant( 'OT_REQUEST_LIST_SYMBOLS' ) => '_ot_msg_multi_i',
81             OTConstant( 'OT_HEARTBEAT' ) => '_ot_msg_cancel_i',
82             OTConstant( 'OT_REQUEST_EQUITY_INIT' ) => '_ot_msg_single_i',
83             OTConstant( 'OT_REQUEST_OPTION_CHAIN' ) => '_ot_msg_singledt_i',
84             OTConstant( 'OT_CANCEL_OPTION_CHAIN' ) => '_ot_msg_cancel_i',
85             OTConstant( 'OT_REQUEST_BOOK_STREAM' ) => '_ot_msg_singledt_i',
86             OTConstant( 'OT_CANCEL_BOOK_STREAM' ) => '_ot_msg_cancel_i',
87             OTConstant( 'OT_REQUEST_TICK_STREAM_EX' ) => '_ot_msg_singledt_i',
88             OTConstant( 'OT_REQUEST_OPTION_CHAIN_EX' ) => '_ot_msg_singledt_i',
89             OTConstant( 'OT_REQUEST_HIST_TICKS' ) => '_ot_msg_multidt_i',
90             OTConstant( 'OT_REQUEST_SPLITS' ) => '_ot_msg_single_i',
91             OTConstant( 'OT_REQUEST_DIVIDENDS' ) => '_ot_msg_single_i',
92             OTConstant( 'OT_REQUEST_HIST_BOOKS' ) => '_ot_msg_multidt_i',
93             OTConstant( 'OT_REQUEST_BOOK_STREAM_EX' ) => '_ot_msg_singledt_i',
94             OTConstant( 'OT_REQUEST_OPTION_CHAIN_U' ) => '_ot_msg_singledt_i',
95             OTConstant( 'OT_REQUEST_OPTION_INIT' ) => '_ot_msg_single_i',
96             OTConstant( 'OT_REQUEST_LIST_SYMBOLS_EX' ) => '_ot_msg_multi_i',
97             OTConstant( 'OT_REQUEST_TICK_SNAPSHOT' ) => '_ot_msg_singledt_i',
98             OTConstant( 'OT_REQUEST_OPTION_CHAIN_SNAPSHOT' )
99             => '_ot_msg_singledt_i',
100             },
101             };
102              
103             # These arguments are for this object; pass the rest on.
104             my %valid_args = (
105             alias => $KEEP,
106             debug => $KEEP,
107             protocolver => $DELETE,
108             platform => $DELETE,
109             platformpass => $DELETE,
110             macaddr => $DELETE,
111             os => $DELETE,
112             username => $DELETE,
113             password => $DELETE,
114             );
115              
116              
117             ###
118             ### Public methods
119             ###
120              
121             sub new
122             {
123 1     1 1 5 my( $class, @args ) = @_;
124 1 50       10 croak( "$class requires an even number of parameters" ) if( @args & 1 );
125              
126 1         5 my $self = {
127             alias => OTDefault( 'alias' ),
128             debug => $FALSE, # Debug mode
129             protocolver => OTDefault( 'protocolver' ),
130             platform => OTDefault( 'platform' ),
131             platformpass => OTDefault( 'platformpass' ),
132             macaddr => OTDefault( 'macaddr' ),
133             os => OTDefault( 'os' ),
134             username => undef, # OT username
135             password => undef, # OT password
136             session_id => undef, # SessID for this OT session
137             };
138              
139             # Prepack the supplied MAC address for FASTAR
140 1         10 $self->{macaddr} = pack_macaddr( $self->{macaddr} );
141              
142 1         4 bless( $self, $class );
143              
144 1         6 $self->initialize( @args );
145              
146             # Make sure we have enough info to login.
147 1         7 $self->_get_auth_data();
148              
149 1         5 return( $self );
150             }
151              
152             # Initialize the object instance.
153             sub initialize
154             {
155 1     1 1 6 my( $self, %args ) = @_;
156              
157             # Store things. Things that make us go.
158 1         98 for( keys( %args ) )
159             {
160 7 100       36 $self->{lc $_} = delete( $args{$_} )
161             if( exists( $valid_args{lc $_} ) );
162             }
163              
164 1         4 return;
165             }
166              
167             # Generic body creation dispatcher
168             sub create_body
169             {
170 4     4 1 9 my( $self, $req_id, $cmd_id, @fields ) = @_;
171              
172 4         20 my $state = $packet_handler_states->{cmds}->{ $cmd_id };
173              
174 4 50       12 throw( "No state for outgoing command id: $cmd_id" ) unless( $state );
175              
176 4         21 my $body = $poe_kernel->call( $self->{alias},
177             $state,
178             $req_id,
179             $cmd_id,
180             @fields );
181              
182 4         37 return( $body );
183             }
184              
185             # Default handler to process generic packet bodies
186             sub process_body
187             {
188 3     3 1 10 my( $self, $body, $req_id, $cmd_id ) = @_;
189 3         6 my( $leftover, $objects );
190              
191 3         13 my $state = $packet_handler_states->{resp}->{ $cmd_id };
192              
193 3 50       13 throw( "No state for incoming command: $cmd_id" ) unless( $state );
194              
195 3         20 ( $leftover, $objects ) = $poe_kernel->call( $self->{alias},
196             $state,
197             $body,
198             $req_id,
199             $cmd_id );
200              
201 3         42 return( $leftover, $objects );
202             }
203              
204             ###
205             ### POE event handlers
206             ###
207              
208             ### OUTGOING packet body construction
209              
210             # The default case
211             sub _ot_msg_generic_o
212             {
213 1     1   38 my( $self, $req_id, $cmd_id, @fields ) = @_[OBJECT,ARG0..$#_];
214 1         2 my $body;
215              
216 1         4 my $template = OTTemplate( 'cmds/' . OTCommand( $cmd_id ) );
217 1 50       4 if( defined( $template ) )
218             {
219             # We can handle this packet body. Go.
220 1         4 $body = pack_binary( $template, $self->_get_session_id(), @fields );
221             }
222             else
223             {
224             # No template found, THROW
225 0         0 $self->_create_error( "Unhandled command type specified: $cmd_id",
226             $req_id, $cmd_id )->throw();
227             }
228              
229 1         3 return( $body );
230             }
231              
232             # No body. This is easy!
233             sub _ot_msg_nobody_o
234             {
235 1     1   52 return( '' );
236             }
237              
238             # LOGIN handling; need to do a few things here.
239             sub _ot_msg_login_o
240             {
241 2     2   119 my( $self ) = $_[OBJECT];
242              
243 2         8 my $template = OTTemplate( 'cmds/OT_LOGIN' );
244              
245 2         11 my $body = pack_binary(
246             $template,
247             $self->_get_protocol_ver(),
248             $self->_get_os(),
249             $self->_get_platform(),
250             $self->_get_platform_pass(),
251             $self->_get_mac_addr(),
252             $self->_get_username(),
253             $self->_get_password(),
254             );
255              
256 2         9 return( $body );
257             }
258              
259             ### INCOMING packet body parsing
260              
261             # Handle a login response.
262             sub _ot_msg_login_i
263             {
264 2     2   117 my( $self, $kernel, $body, $req_id, $cmd_id ) = @_[OBJECT,KERNEL,ARG0..$#_];
265              
266             # Unpack body
267 2         11 my $template = $self->_get_resp_template( $req_id, $cmd_id, $body );
268 2         7 my @fields = unpack_binary( $template, $body );
269 2         5 my( $session_id, $redirected, $redir_host, $redir_port ) = @fields;
270              
271             # Stash our OT session ID for later
272 2         8 $self->_set_session_id( $session_id );
273              
274             # Check if we have been redirected, and send a synchronous event.
275 2         2 my $object;
276 2 100       9 if( $redirected )
277             {
278 1         7 $poe_kernel->call( $poe_kernel->get_active_session(),
279             '_server_redirect', $redir_host, $redir_port );
280             }
281             else # tell ourselves we logged in
282             {
283 1         8 $kernel->yield( OTEvent( 'OT_ON_LOGIN' ) );
284 1         62 $object = $self->_create_record( $req_id, $cmd_id, undef, \@fields );
285             }
286              
287             # Return the resulting object, or nothing.
288 2 100       20 return( '', $object ? [ $object ] : [] );
289             }
290              
291             # Handle a logout response.
292             sub _ot_msg_logout_i
293             {
294 1     1   197 my( $self, $kernel ) = @_[OBJECT,KERNEL];
295              
296 1         8 $self->_set_session_id( undef );
297              
298 1         7 $kernel->yield( '_logged_out' );
299              
300 1         199 return( '', [] );
301             }
302              
303             # Handle a single record/message packet body
304             sub _ot_msg_single_i
305             {
306 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
307              
308             # Unpack body
309 0         0 my $template = $self->_get_resp_template( $req_id, $cmd_id, $body );
310              
311 0         0 my( $leftover, @fields ) = $self->_parse_row( $template, $body );
312              
313             # Check for and signal end of data
314 0         0 my $dt = $fields[0];
315 0 0       0 if( OTeod( $dt ) )
316             {
317 0         0 $poe_kernel->yield( _ot_proto_end_of_data => $req_id, $cmd_id );
318 0         0 return ( $leftover, [] );
319             }
320              
321 0         0 my $record = $self->_create_record( $req_id, $cmd_id, $dt, \@fields );
322              
323 0         0 return( $leftover, [ $record ] );
324             }
325              
326             # Handle a single record/message packet body, with datatype
327             sub _ot_msg_singledt_i
328             {
329 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
330              
331             # Check for and signal end of data
332 0         0 my $dt = unpack_binary( 'C', $body );
333 0 0       0 if( OTeod( $dt ) )
334             {
335 0         0 $poe_kernel->yield( _ot_proto_end_of_data => $req_id, $cmd_id );
336 0         0 return ( '', [] );
337             }
338              
339             # Unpack body
340 0         0 my $template = OTTemplate( 'datatype/' . OTDatatype( $dt ) );
341 0 0       0 throw( "Unknown Datatype: '$dt'\n" . dump_hex($body) ) unless( $template );
342              
343 0         0 my @fields;
344 0         0 @fields = unpack_binary( $template, $body );
345              
346 0         0 my $record = $self->_create_record( $req_id, $cmd_id, $dt, \@fields );
347              
348 0         0 return( '', [ $record ] );
349             }
350              
351             # Handle a multiple record/message packet body, with datatype
352             sub _ot_msg_multidt_i
353             {
354 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
355              
356             # Snarf row count and chop from beginning of data.
357 0         0 my( $leftover, $rowcount ) = $self->_parse_row( 'V', $body );
358              
359             # Go through each row, setting template to datatype and parsing
360 0         0 my @records = ();
361 0         0 for( 1..$rowcount )
362             {
363             # Peek ahead to get datatype, but leave it attached
364 0         0 my $dt = unpack( 'C', $leftover );
365 0         0 my $template = OTTemplate( 'datatype/' . OTDatatype( $dt ) );
366 0 0       0 throw( "Unknown Datatype: '$dt'\n".dump_hex($body)) unless( $template );
367              
368             # break loop if we don't have enough data left to fill template
369 0 0       0 last unless( length( $leftover ) >= pack_bytes( $template ) );
370              
371             # Parse and retrieve return values, trimming $leftover
372 0         0 my @fields;
373 0         0 ( $leftover, @fields) = $self->_parse_row( $template, $leftover );
374              
375             # Store in object
376 0         0 my $record = $self->_create_record( $req_id, $cmd_id, $dt, \@fields );
377 0         0 push( @records, $record );
378             }
379              
380 0         0 return( $leftover, \@records );
381             }
382              
383             # Handle a multiple record/message packet body, no datatype
384             sub _ot_msg_multi_i
385             {
386 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
387              
388             # Get template
389 0         0 my $template = $self->_get_resp_template( $req_id, $cmd_id, $body );
390              
391             # Snarf row count and chop from beginning of data.
392 0         0 my( $leftover, $rowcount ) = $self->_parse_row( 'v', $body );
393              
394             # Go through each row, setting template to datatype and parsing
395 0         0 my @records = ();
396 0         0 for( 1..$rowcount )
397             {
398             # Parse and retrieve return values, trimming $leftover
399 0         0 ( $leftover, my @fields ) = $self->_parse_row( $template, $leftover );
400              
401             # Store in object
402 0         0 my $record = $self->_create_record( $req_id, $cmd_id, undef, \@fields );
403 0         0 push( @records, $record );
404             }
405              
406 0         0 return( $leftover, \@records );
407             }
408              
409             # Handle ListExchanges response. Yes, only for this. Grr.
410             sub _ot_msg_listex_i
411             {
412 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
413              
414             # Get template
415 0         0 my $template = $self->_get_resp_template( $req_id, $cmd_id, $body );
416              
417             # Get urllen, url
418 0         0 my( $leftover, $url ) = $self->_parse_row( $template, $body );
419             # Get rowcount
420 0         0 ( $leftover, my $rowcount ) = $self->_parse_row( 'v', $leftover );
421              
422 0         0 $template = 'a15 C v/a v/a';
423              
424             # Go through each row, setting template to datatype and parsing
425 0         0 my @records = ();
426 0         0 for( 1..$rowcount )
427             {
428             # Parse and retrieve return values, trimming $leftover
429 0         0 ( $leftover, my @fields) = $self->_parse_row( $template, $leftover );
430              
431             # Store in object
432 0         0 my $record = $self->_create_record( $req_id, $cmd_id, undef, \@fields );
433 0         0 push( @records, $record );
434             }
435              
436 0         0 return( $leftover, \@records );
437             }
438              
439             # Build cancellation record.
440             sub _ot_msg_cancel_i
441             {
442 0     0   0 my( $self, $body, $req_id, $cmd_id ) = @_[OBJECT,ARG0..$#_];
443              
444 0         0 my $cancel = $self->_create_record( $req_id, $cmd_id, undef, [] );
445              
446 0         0 return( '', [ $cancel ] );
447             }
448              
449             # Handle no packet body. bvernt.
450             sub _ot_msg_nobody_i
451             {
452 0     0   0 return( '', [] );
453             }
454              
455              
456             ###
457             ### Private methods
458             ###
459              
460             # Grab the named template, or throw an exception.
461             sub _get_resp_template
462             {
463 2     2   6 my( $self, $req_id, $cmd_id, $body ) = @_;
464              
465             # Get template
466 2         17 my $template = OTTemplate( 'resp/' . OTCommand( $cmd_id ) );
467 2 50       6 unless( $template )
468             {
469 0         0 my $hex = dump_hex( $body );
470 0         0 $hex =~ s/\n/ /gms;
471              
472 0         0 $Carp::CarpLevel = 0;
473 0         0 print Carp::longmess();
474              
475 0         0 my $error = $self->_create_error( "Unhandled packet received: ($hex)",
476             $req_id, $cmd_id )->throw();
477             }
478              
479 2         7 return( $template );
480             }
481              
482             # Pull out a single row, returning leftover data and @fields
483             sub _parse_row
484             {
485 0     0   0 my( $self, $template, $input ) = @_;
486              
487 0         0 $template .= ' a*';
488 0         0 my @fields = unpack_binary( $template, $input );
489 0         0 my $leftover = pop( @fields );
490              
491 0         0 return( $leftover, @fields );
492             }
493              
494             # Create and populate a ::Record object
495             sub _create_record
496             {
497 1     1   3 my( $self, $req_id, $cmd_id, $datatype, $data ) = @_;
498              
499 1         18 my $record = POE::Component::Client::opentick::Record->new(
500             RequestID => $req_id,
501             CommandID => $cmd_id,
502             DataType => $datatype,
503             Data => $data,
504             );
505              
506 1         4 return( $record );
507             }
508              
509             # Create and populate an ::Error object
510             sub _create_error
511             {
512 0     0   0 my( $self, $message, $req_id, $cmd_id ) = @_;
513              
514 0         0 my $error = POE::Component::Client::opentick::Error->new(
515             RequestID => $req_id,
516             CommandID => $cmd_id,
517             Message => $message,
518             DumpStack => 1,
519             );
520              
521 0         0 return( $error );
522             }
523              
524             # Retrieve auth data from relevant sources
525             sub _get_auth_data
526             {
527 1     1   2 my( $self ) = @_;
528              
529 1 50 0     5 $self->{username} = $ENV{OPENTICK_USER}
530             or croak( "FATAL: Cannot get opentick username!" )
531             unless( $self->{username} );
532 1 50 0     5 $self->{password} = $ENV{OPENTICK_PASS}
533             or croak( "FATAL: Cannot get opentick password!" )
534             unless( $self->{password} );
535              
536 1         3 return;
537             }
538              
539             ###
540             ### Accessor methods
541             ###
542              
543             sub _set_session_id
544             {
545 3     3   22 my( $self, $sess_id ) = @_;
546              
547 3         11 return( $self->{session_id} = $sess_id );
548             }
549              
550             sub _set_platform_id
551             {
552 0     0   0 my( $self, $id ) = @_;
553              
554 0         0 return( $self->{platform} = $id );
555             }
556              
557             sub _set_platform_pass
558             {
559 0     0   0 my( $self, $pass ) = @_;
560              
561 0         0 return( $self->{platformpass} = $pass );
562             }
563              
564             sub _get_session_id
565             {
566 1     1   2 my( $self ) = @_;
567              
568 1         6 return( $self->{session_id} );
569             }
570              
571             sub _get_protocol_ver
572             {
573 2     2   4 my( $self ) = @_;
574              
575 2         11 return( $self->{protocolver} );
576             }
577              
578             sub _get_os
579             {
580 2     2   4 my( $self ) = @_;
581              
582 2         11 return( $self->{os} );
583             }
584              
585             sub _get_platform
586             {
587 2     2   3 my( $self ) = @_;
588              
589 2         9 return( $self->{platform} );
590             }
591              
592             sub _get_platform_pass
593             {
594 2     2   9 my( $self ) = @_;
595              
596 2         9 return( $self->{platformpass} );
597             }
598              
599             sub _get_mac_addr
600             {
601 2     2   3 my( $self ) = @_;
602              
603 2         11 return( $self->{macaddr} );
604             }
605              
606             sub _get_username
607             {
608 2     2   3 my( $self ) = @_;
609              
610 2         8 return( $self->{username} );
611             }
612              
613             sub _get_password
614             {
615 2     2   4 my( $self ) = @_;
616              
617 2         10 return( $self->{password} );
618             }
619              
620             1;
621              
622             __END__