File Coverage

blib/lib/FIX/Lite.pm
Criterion Covered Total %
statement 24 325 7.3
branch 0 176 0.0
condition 0 54 0.0
subroutine 8 42 19.0
pod 10 31 32.2
total 42 628 6.6


line stmt bran cond sub pod time code
1             package FIX::Lite;
2              
3 1     1   21047 use vars qw($VERSION @ISA);
  1         3  
  1         60  
4 1     1   5 use warnings;
  1         2  
  1         29  
5 1     1   5 use strict;
  1         5  
  1         20  
6              
7 1     1   807 use IO::Socket;
  1         37068  
  1         4  
8 1     1   1384 use POSIX qw(strftime);
  1         6750  
  1         5  
9             #use Net::Cmd;
10 1     1   1898 use FIX::Lite::Dictionary;
  1         2  
  1         28  
11 1     1   2576 use IO::Select;
  1         1660  
  1         51  
12 1     1   909 use Time::HiRes qw(gettimeofday);
  1         1517  
  1         5  
13              
14             #@ISA = qw(Net::Cmd IO::Socket::INET);
15             @ISA = qw(IO::Socket::INET);
16             $VERSION = "0.04";
17              
18             my $fixDict;
19             my $MsgSeqNum = 0;
20             my %fieldDefaults = (
21             EncryptMethod => 0,
22             HeartBtInt => 30,
23             );
24             my $sel;
25              
26             sub new {
27 0     0 1   my $self = shift;
28 0   0       my $type = ref($self) || $self;
29 0           my %arg = @_;
30 0           my $obj;
31             $obj = $type->SUPER::new(
32             PeerHost => defined $arg{Host} ? $arg{Host} : '127.0.0.1',
33             PeerPort => defined $arg{Port} ? $arg{Port} : '5201',
34 0 0         Timeout => defined $arg{Timeout} ? $arg{Timeout} : 60,
    0          
    0          
35             Proto => 'tcp',
36             );
37 0           $sel = IO::Select->new( $obj );
38              
39             return undef
40 0 0         unless defined $obj;
41              
42 0           $obj->autoflush(1);
43              
44             #$obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
45              
46             # Initialize $fixDict
47              
48 0 0         if ( defined $arg{version} ) {
49 0           FIX::Lite::Dictionary::load( $arg{version} );
50             }
51             else {
52 0           FIX::Lite::Dictionary::load('FIX44');
53             }
54 0           $fixDict = FIX::Lite::Dictionary->new();
55              
56 0           $obj;
57             }
58              
59             sub logon {
60 0     0 1   my $self = shift;
61 0           my %arg = @_;
62              
63 0           $arg{ResetSeqNumFlag} = 'Y';
64 0           $MsgSeqNum=0;
65              
66 0           my $msgBody = constructMessage('Logon',\%arg);
67 0 0         print "----\nPrepared Logon FIX Message:\n".readableFix($msgBody)."\n" if ($arg{Debug});
68              
69 0           my $size = $self->send($msgBody);
70 0 0         print " Sent data of length $size\n" if ($arg{Debug});
71              
72             # receive a response of up to 1024 characters from server
73 0           my $response = "";
74 0           $self->recv($response, 1024);
75 0 0         print "----\nReceived Logon response:\n".readableFix($response)."\n" if ($arg{Debug});
76 0           my $parsedResp;
77 0 0         $parsedResp = parseFixMessage($response) if ($response);
78 0           ${*$self}->{logon}=$parsedResp;
  0            
79 0           ${*$self}->{args}=\%arg;
  0            
80 0           return $parsedResp;
81             }
82              
83             sub request {
84 0     0 1   my $self = shift;
85 0           my %arg = @_;
86              
87 0   0       $arg{SenderCompID} ||= ${*$self}->{args}->{SenderCompID};
  0            
88 0   0       $arg{TargetCompID} ||= ${*$self}->{args}->{TargetCompID};
  0            
89 0 0 0       $arg{TargetSubID} ||= (${*$self}->{args}->{TargetSubID}) ? ${*$self}->{args}->{TargetSubID} : undef;
  0            
  0            
90              
91 0           my $msgBody = constructMessage($arg{MsgType},\%arg);
92 0 0         print "----\nPrepared FIX Message:\n".readableFix($msgBody)."\n" if ($arg{Debug});
93              
94 0           my $size = $self->send($msgBody);
95 0 0         print " Sent data of length $size\n" if ($arg{Debug});
96              
97 0           my $response = "";
98              
99 0           $self->recv($response, 4096);
100              
101 0 0         print "----\nReceived response:\n".readableFix($response)."\n" if ($arg{Debug});
102 0           my $parsedResp;
103 0 0         $parsedResp = parseFixMessage($response) if ($response);
104 0           ${*$self}->{request}=$parsedResp;
  0            
105              
106 0           return $parsedResp;
107             }
108              
109             sub heartbeat {
110 0     0 1   my $self = shift;
111 0           my %arg = @_;
112              
113 0   0       $arg{SenderCompID} ||= ${*$self}->{args}->{SenderCompID};
  0            
114 0   0       $arg{TargetCompID} ||= ${*$self}->{args}->{TargetCompID};
  0            
115 0 0 0       $arg{TargetSubID} ||= (${*$self}->{args}->{TargetSubID}) ? ${*$self}->{args}->{TargetSubID} : undef;
  0            
  0            
116              
117 0           my $msgBody = constructMessage('Heartbeat',\%arg);
118 0 0         print "----\nPrepared FIX Heartbeat:\n".readableFix($msgBody)."\n" if ($arg{Debug});
119 0           my $size = $self->send($msgBody);
120 0 0         print " Sent data of length $size\n" if ($arg{Debug});
121             }
122              
123             sub listen {
124 0     0 1   my $self = shift;
125 0           my $handler = shift;
126 0           my %arg = @_;
127              
128 0   0       my $HeartBtInt = $arg{HeartBtInt} || $fieldDefaults{HeartBtInt};
129 0           my $response;
130 0           my $lastHbTime = time;
131 0           while (1) {
132 0           my @ready = $sel->can_read(0);
133 0 0         if (scalar(@ready)) {
134 0           my $sock = $ready[0];
135 0 0         if (! sysread($ready[0], $response, 4096)) {
136 0           print "recv failed :$!\n";
137 0           return 1;
138             } else {
139 0 0         print "----\nReceived FIX message:\n".readableFix($response)."\n" if ($arg{Debug});
140              
141             #Split into each single msg
142 0           for my $fixMsg ( split /8=FIX.4.4\x{01}/, $response ) { # Split on FIX version
143 0 0         next if (length($fixMsg)<=0);
144              
145 0 0         print " Splitted FIX message:\n".readableFix($fixMsg)."\n" if ($arg{Debug});
146            
147 0           my $parsedResp = parseFixMessage($fixMsg);
148              
149 0 0         if ( ! defined $parsedResp->{MsgType} ) {
    0          
    0          
150 0 0         print " Cannot parse message\n" if ($arg{Debug});
151             }
152             elsif ( $parsedResp->{MsgType} eq '0' ) {
153 0 0         print " This is heartbeat. Will not pass it to handler\n" if ($arg{Debug});
154             }
155             elsif ( $parsedResp->{MsgType} eq '1' ) {
156 0 0         my $TestReqID = (defined $parsedResp->{TestReqID})?$parsedResp->{TestReqID}:'TEST';
157 0 0         print " This is TestRequest. Will send heartbeat with TestReqID $TestReqID\n" if ($arg{Debug});
158             $self->heartbeat(
159             TestReqID => $TestReqID,
160             Debug => $arg{Debug}
161 0           );
162             }
163             else {
164 0           $handler->($parsedResp);
165             }
166              
167             }
168             }
169             }
170              
171 0 0         if ( time - $lastHbTime > $HeartBtInt ) {
172 0           $lastHbTime = time;
173 0           $self->heartbeat( Debug => $arg{Debug} );
174             }
175 0           select(undef, undef, undef, 0.002);
176              
177             }
178             }
179              
180             sub loggedIn {
181 0     0 1   my $self = shift;
182 0 0 0       return 1 if (defined ${*$self}->{logon}->{'MsgType'} && ${*$self}->{logon}->{'MsgType'} eq getMessageType('Logon'));
  0            
  0            
183 0           return 0;
184             }
185              
186             sub lastRequest {
187 0     0 1   my $self = shift;
188 0           my $field = shift;
189 0           return getFieldDescription($field, ${*$self}->{request}->{$field});
  0            
190             }
191              
192             sub constructMessage($$) {
193              
194 0     0 0   my $msgtype = shift;
195 0           my $arg = shift;
196 0           my @fields;
197 0           undef $arg->{MsgType};
198 0           $MsgSeqNum++;
199              
200 0           my $time = strftime "%Y%m%d-%H:%M:%S.".getMilliseconds(), gmtime;
201 0           push @fields, getFieldNumber('MsgType')."=".getMessageType($msgtype);
202 0           push @fields, getFieldNumber('SendingTime')."=".$time;
203 0           push @fields, getFieldNumber('MsgSeqNum')."=".$MsgSeqNum;
204              
205 0           my @allFields = ( @{getMessageHeader()}, @{getMessageFields($msgtype)} );
  0            
  0            
206              
207 0           foreach my $field ( @allFields ) {
208 0 0 0       if ( defined $arg->{$field->{name}} ) {
    0 0        
    0 0        
      0        
      0        
      0        
209 0 0         if (ref($arg->{$field->{name}}) eq "HASH") {
210 0           my @tmpFields;
211 0           my $count=0;
212 0           foreach my $component ( keys %{$arg->{$field->{name}}} ) {
  0            
213 0 0         if (isComponent($component)) {
214 0           my @componentFields = @{getComponentFields($component)};
  0            
215 0           foreach ( @componentFields ) {
216 0 0         if ( defined $arg->{$field->{name}}->{$component}->{$_->{name}} ){
217 0           my $componentField = $arg->{$field->{name}}->{$component}->{$_->{name}};
218 0 0         if ( ref($componentField) eq "ARRAY" ) {
219 0           foreach my $entry ( @{$componentField} ) {
  0            
220 0           push @tmpFields, getFieldNumber($_->{name})."=".getFieldValue($_->{name},$entry);
221 0           $count++
222             }
223             } else {
224 0           push @tmpFields, getFieldNumber($_->{name})."=".getFieldValue($_->{name},$componentField);
225 0           $count++;
226             }
227             }
228             }
229             } else {
230 0           my $componentField = $arg->{$field->{name}}->{$component};
231 0 0         if ( ref($componentField) eq "ARRAY" ) {
232 0           foreach my $entry ( @{$componentField} ) {
  0            
233 0           push @tmpFields, getFieldNumber($component)."=".getFieldValue($component,$entry);
234 0           $count++
235             }
236             } else {
237 0           push @tmpFields, getFieldNumber($component)."=".getFieldValue($component,$componentField);
238             }
239             }
240            
241             }
242 0           push @fields, getFieldNumber($field->{name})."=".$count;
243 0           @fields = ( @fields, @tmpFields );
244             }
245              
246 0 0         next if (ref($arg->{$field->{name}}) eq "HASH");
247 0           push @fields, getFieldNumber($field->{name})."=".getFieldValue($field->{name},$arg->{$field->{name}});
248             }
249             elsif ( $field->{required} eq 'Y' && defined $fieldDefaults{$field->{name}} ) {
250             push @fields, getFieldNumber($field->{name})."=".$fieldDefaults{$field->{name}}
251 0           }
252             elsif ( $field->{required} eq 'Y' && $field->{name} ne 'BeginString' and $field->{name} ne 'BodyLength'
253             and $field->{name} ne 'MsgType' and $field->{name} ne 'MsgSeqNum' and $field->{name} ne 'SendingTime') {
254 0 0         if ($field->{name} eq "MDReqID") {
255 0           push @fields, getFieldNumber($field->{name})."=".randomString();
256             } else {
257 0           print "ERROR: $field->{name}\n";
258             }
259             }
260             }
261              
262 0           my $req = join "\x01",@fields;
263 0           $req .= "\x01";
264 0           $req = getFieldNumber('BeginString')."=FIX.4.4\x01".getFieldNumber('BodyLength')."=".length($req)."\x01".$req;
265 0           my $checksum = unpack("%8C*", $req) % 256;
266 0           $checksum = sprintf( "%03d", $checksum );
267 0           $req .= getFieldNumber('CheckSum')."=$checksum\x01";
268 0           return $req."\n";
269             }
270              
271             sub getField($) {
272 0     0 0   my $f = shift;
273 0           return $fixDict->{hFields}->{$f};
274             }
275              
276             # returns 1 if given field is a group header field
277             # isGroup('NoAllocs') -> returns 1
278             # isGroup('Symbol') -> returns 0
279             sub isGroup($) {
280 0     0 0   my $f = shift;
281 0           my $ff = getField($f);
282 0 0         return defined $ff ? $ff->{type} eq 'NUMINGROUP' : 0;
283             }
284              
285             # returns true if given field is a member of the given group of given message.
286             sub isFieldInGroup($$$) {
287 0     0 0   my ( $m, $g, $f ) = @_;
288              
289 0           my $gn = getFieldName($g);
290 0 0         return 0 if ! defined $gn;
291 0 0         return 0 if ! isGroup($gn);
292              
293 0           my $msg = getGroupInMessage($m, $g);
294 0 0         return 0 if ! defined $msg;
295 0           return _isFieldInStructure($msg, $f);
296             }
297              
298             # return a ref on group of a message, this then allows us to work on the group elements.
299             # $d->getGroupInMessage('D','NoAllocs')
300             # will return a ref on the NoAllocs group allowing us to then parse it
301             #
302             # Looks recursively into groups of groups if needed.
303             sub getGroupInMessage($$) {
304 0     0 0   my ( $m, $g ) = @_;
305 0           my $s = getMessageFields($m);
306 0 0         return undef if ! defined $s;
307 0           my $gn = getFieldName($g);
308 0 0         return undef if ! defined($gn);
309              
310 0 0         return undef if ! isGroup($g);
311              
312 0           return _getGroupInStructure( $s, $gn );
313             }
314              
315             # returns true if given field is found in the structure.
316             sub _isFieldInStructure($$);
317              
318             sub _isFieldInStructure($$) {
319 0     0     my ( $m, $f ) = @_;
320 0 0 0       return 0 unless ( defined $m && defined $f );
321 0           my $fn = getFieldName($f);
322 0 0         return 0 if ! defined $fn;
323              
324 0           for my $f2 ( @{$m} ) {
  0            
325             # found the field? return 1. Beware that if the element is a component then we don't accept
326             # it as a valid field of the structure.
327 0 0 0       return 1 if ( $f2->{name} eq $fn && !defined $f2->{component} );
328              
329             # if the field is a group then scan all elements of the group
330 0 0         if ( defined $f2->{group} ) {
331 0 0         return 1 if _isFieldInStructure( $f2->{group}, $fn ) == 1;
332             }
333              
334             # if the field is a component, we need to go to the component hash and check out its
335             # composition.
336 0 0         if ( defined $f2->{component} ) {
337 0 0         return 1 if _isFieldInStructure( getComponentFields($f2->{name}), $fn ) == 1;
338             }
339             }
340 0           return 0;
341             }
342              
343             sub _getGroupInStructure($$);
344              
345             sub _getGroupInStructure($$) {
346 0     0     my ($s, $gn) = @_;
347              
348 0           my $ret;
349             # parse each field in the structure, and ....
350 0           for my $e ( @{$s} ) {
  0            
351             # we found the group name
352 0 0 0       return $e->{group} if ($e->{name} eq $gn && defined $e->{group});
353              
354             # stop at each group header
355 0 0         if (defined $e->{group}) {
356             # and research recursively
357 0           $ret = _getGroupInStructure($e->{group},$gn);
358 0 0         return $ret if defined $ret;
359             }
360              
361             # if we run into a component we need to check that out too
362 0 0         if (defined $e->{component}) {
363 0           $ret = _getGroupInStructure(getComponentFields($e->{name}), $gn);
364 0 0         return $ret if defined $ret;
365             }
366             }
367 0           undef;
368             }
369              
370              
371             sub getFieldName($) {
372 0     0 0   my $f = shift;
373 0           my $fh = getField($f);
374 0 0         return defined $fh ? $fh->{name} : undef;
375             }
376              
377             sub getTagById {
378 0     0 1   my ($self, $f) = @_;
379 0           return getFieldName($f);
380             }
381              
382             sub getFieldNumber($) {
383 0     0 0   my $f = shift;
384 0 0         return $f if ( $f =~ /^[0-9]+$/ );
385 0           my $fh = getField($f);
386 0 0         warn("getFieldNumber($f) returning undef") if !defined $fh;
387 0 0         return defined $fh ? $fh->{number} : undef;
388             }
389              
390             sub getFieldValue($$) {
391 0     0 0   my $f = shift;
392 0           my $v = shift;
393 0 0         return $v if ( $v =~ /^[0-9]+$/ );
394 0           my $fh = getField($f);
395 0 0         warn("getField($f) returning undef") if !defined $fh;
396 0 0         if ($fh->{enum}) {
397 0           foreach ( @{$fh->{enum}} ) {
  0            
398 0 0         if ($_->{description} eq $v) {
399 0           return $_->{name};
400             }
401             }
402             }
403 0           return $v;
404             }
405              
406             sub getFieldDescription($$) {
407 0     0 0   my $f = shift;
408 0           my $v = shift;
409 0           my $fh = getField($f);
410 0 0         warn("getField($f) returning undef") if !defined $fh;
411 0 0         if ($fh->{enum}) {
412 0           foreach ( @{$fh->{enum}} ) {
  0            
413 0 0         if ($_->{name} eq $v) {
414 0           return $_->{description};
415             }
416             }
417             }
418 0           return $v;
419             }
420              
421             sub getMessage($) {
422 0     0 0   my $f = shift;
423 0           return $fixDict->{hMessages}->{$f};
424             }
425              
426             sub getMessageType($) {
427 0     0 0   my $f = shift;
428 0 0         return $f if ( $f =~ /^[0-9]+$/ );
429 0           my $fh = getMessage($f);
430 0 0         warn("getMessage($f) returning undef") if !defined $fh;
431 0 0         return defined $fh ? $fh->{msgtype} : undef;
432             }
433              
434             sub getMessageName($) {
435 0     0 0   my $f = shift;
436 0           my $fh = getMessage($f);
437 0 0         warn("getMessage($f) returning undef") if !defined $fh;
438 0 0         return defined $fh ? $fh->{name} : undef;
439             }
440              
441             sub getMsgByType {
442 0     0 1   my ($self, $f) = @_;
443 0           return getMessageName($f);
444             }
445              
446             sub getMessageFields($) {
447 0     0 0   my $f = shift;
448 0           my $fh = getMessage($f);
449 0 0         warn("getMessage($f) returning undef") if !defined $fh;
450 0 0         return defined $fh ? $fh->{fields} : undef;
451             }
452              
453             sub getMessageHeader {
454 0     0 0   return $fixDict->{header};
455             }
456              
457             sub getComponent($) {
458 0     0 0   my $f = shift;
459 0           return $fixDict->{hComponents}->{$f};
460             }
461              
462             sub isComponent($) {
463 0     0 0   my $f = shift;
464 0           return defined $fixDict->{hComponents}->{$f};
465             }
466              
467             sub getComponentFields($) {
468 0     0 0   my $f = shift;
469 0           my $fh = getComponent($f);
470 0 0         warn("getComponent($f) returning undef") if !defined $fh;
471 0 0         return defined $fh ? $fh->{fields} : undef;
472             }
473              
474             sub parseFixMessage {
475 0     0 0   my $message = shift;
476 0 0         return unless defined $message;
477 0           my $parsedMsg;
478              
479 0           my @fields = split /\x01/, $message; # Split on "SOH"
480 0           _parseFixArray( \$parsedMsg, undef, undef, 0, \@fields );
481              
482 0           return $parsedMsg;
483             }
484              
485             sub _parseFixArray($$$$$);
486              
487             sub _parseFixArray($$$$$) {
488 0     0     my ( $result, $msgType, $groupTag, $iField, $fields ) = @_;
489 0           my $i = $iField;
490              
491 0           while ( $i < scalar(@$fields) ) {
492 0           my $field = $fields->[$i];
493 0           my ( $k, $v ) = ( $field =~ /^([^=]+)=(.*)$/ );
494              
495 0 0         if ( defined $$result->{$k} ) {
496 0 0         return $i if defined $groupTag;
497 0           warn("Field $k is already in hash!");
498             }
499 0 0         if ( defined $groupTag ) {
500 0 0         return $i if !isFieldInGroup( $msgType, $groupTag, $k );
501             }
502             # Store both using Tag and FieldName.
503 0           $$result->{$k} = $v;
504 0           my $fieldName = getFieldName($k);
505 0 0         if ( defined $fieldName ) {
506 0           $$result->{$fieldName} = $v;
507             } else {
508 0           warn("Haven't found field $k in dictionary");
509             }
510            
511 0 0         if ( $fieldName eq 'MsgType' ) {
    0          
512 0           $msgType = $v;
513             }
514             elsif ( isGroup($k) ) {
515 0           my @elems;
516 0           $i++;
517 0           for ( 1 .. $v ) {
518 0           my $localResult;
519 0           $i = _parseFixArray( \$localResult, $msgType, $k, $i, $fields );
520 0           push( @elems, $localResult );
521             }
522             # Store both using Tag and FieldName.
523 0           $$result->{$k} = \@elems;
524 0           $$result->{$fieldName} = \@elems;
525 0           $i--;
526             }
527 0           $i++;
528             }
529             }
530              
531             sub randomString {
532 0     0 0   my @chars = ("A".."Z", "a".."z");
533 0           my $string;
534 0           $string .= $chars[rand @chars] for 1..6;
535 0           return $string;
536             }
537              
538             sub readableFix {
539 0     0 0   my $fixMsg = shift;
540 0           $fixMsg =~ s/\x01/\|/g;
541 0           return $fixMsg;
542             }
543              
544             sub quit {
545 0     0 1   my $self = shift;
546              
547 0           $self->close;
548             }
549              
550             sub getMilliseconds {
551 0     0 0   my $time = gettimeofday;
552 0           return sprintf("%03d",int(($time-int($time))*1000));
553             }
554             1; # End of FIX::Lite
555             __END__