File Coverage

blib/lib/Device/TNC/KISS.pm
Criterion Covered Total %
statement 15 116 12.9
branch 0 86 0.0
condition n/a
subroutine 5 9 55.5
pod 3 3 100.0
total 23 214 10.7


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Device::TNC::KISS - Device::TNC subclass interface to a KISS mode TNC
5              
6             =head1 DESCRIPTION
7              
8             This module trys to implement an easy way to talk to a KISS mode Terminal Node
9             Controller (TNC) such as the TNC-X via a serial port.
10              
11             =head1 SYNOPSIS
12              
13             use Device::TNC::KISS;
14              
15             To read data direct from a TNC:
16             my %tnc_config = (
17             'port' => ($Config{'osname'} eq "MSWin32") ? "COM3" : "/dev/TNC-X",
18             'baudrate' => 9600,
19             'warn_malformed_kiss' => 1,
20             'raw_log' => "raw_packet.log",
21             );
22              
23             To read data from a raw KISS log file
24             my %tnc_config = (
25             'warn_malformed_kiss' => 1,
26             'file' => "raw_packet.log",
27             );
28              
29             my $kiss_tnc = new Device::TNC::KISS(%tnc_config);
30              
31             my $kiss_frame = $kiss_tnc->read_kiss_frame();
32             my @kiss_frame = $kiss_tnc->read_kiss_frame();
33              
34             my ($kiss_type, $hdlc_frame) = $kiss_tnc->read_hdlc_frame();
35             my ($kiss_type, @hdlc_frame) = $kiss_tnc->read_hdlc_frame();
36              
37             This module was developed on Linux and Windows. It should work for any UNIX like
38             operating system where the Device::SerialPort module can be used.
39              
40             =cut
41              
42             package Device::TNC::KISS;
43              
44             ####################
45             # Standard Modules
46 1     1   470 use strict;
  1         1  
  1         24  
47 1     1   4 use Config;
  1         2  
  1         36  
48 1     1   736 use FileHandle 2.0;
  1         11350  
  1         6  
49             # Custom modules
50 1     1   838 use Device::TNC;
  1         3  
  1         34  
51              
52             ####################
53             # Variables
54 1     1   6 use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
  1         1  
  1         1224  
55             @ISA = qw( Device::TNC );
56             @EXPORT = qw();
57             @EXPORT_OK = qw();
58             $VERSION = 0.01;
59             $| = 1;
60              
61              
62             ####################
63             # Functions
64              
65             if ($Config{'osname'} eq "MSWin32")
66             {
67             require Win32::SerialPort;
68             Win32::SerialPort->import( qw( :STAT 0.19 ) );
69             }
70             else
71             {
72             require Device::SerialPort;
73             }
74              
75             my $FEND = 0xC0; # Frame end
76             my $FESC = 0xDB; # Frame escape
77             my $TFEND = 0xDC; # Transposed frame end
78             my $TFESC = 0xDD; # Transposed frame escape
79              
80             my $m_port = undef;
81             my $m_port_name = undef;
82             my $m_raw_log = undef;
83              
84             ################################################################################
85              
86             =head2 B
87              
88             my %port_data = { 'option' => 'value' };
89             my $kiss_tnc = new Device::TNC::KISS(%port_data);
90              
91             The new method creates and returns a new Device::TNC::KISS object that can be
92             used to communicate with a KISS mode terminal node controller.
93              
94             The method requires that a hash of settings be passed.
95             If the port and baudrate are not set in the passed settings or the port cannot
96             be opened an error message is printed and undef returned.
97              
98             =head3 Options and values
99              
100             =over 4
101              
102             =item B
103              
104             This sets the serial port name as appropriate for the operating system.
105             For UNIX this will be something like /dev/ttyS0 and for Windows this will be
106             something like COM1
107              
108             =item B
109              
110             The baud rate is the speed the TNC is configured to talk at. i.e. 1200 baud.
111              
112             =item B
113              
114             To see warnings about malformed KISS frames then set this option to a true value
115              
116             =item B
117              
118             If you want to keep a log of the raw packets that are read then set raw_log to
119             the name of the log file you which to write to. If there is an error opening the
120             log file the
121              
122             =item B
123              
124             Use to read KISS frames from a file instead of a port. Using this option will
125             cause the module to ignore any port, baudrate and raw_log options.
126              
127             The value for this option should be a raw KISS log file such as that created via
128             the raw_log option.
129              
130             =back
131              
132             The returned object contains a reference to a serial port object which is either a
133             Device::SerialPort (UNIX) or Win32::SerialPort (Windows) object.
134              
135             The serial port is initialised will the following
136              
137             $kiss_tnc->{'PORT'}->parity("none");
138             $kiss_tnc->{'PORT'}->databits(8);
139             $kiss_tnc->{'PORT'}->stopbits(1);
140             $kiss_tnc->{'PORT'}->handshake("none");
141             $kiss_tnc->{'PORT'}->read_interval(100) if $Config{'osname'} eq "MSWin32";
142             $kiss_tnc->{'PORT'}->read_char_time(0);
143             $kiss_tnc->{'PORT'}->read_const_time(1000);
144              
145             For more details on this see the documentation for Device::SerialPort (UNIX) or
146             Win32::SerialPort (Windows).
147              
148             =cut
149              
150             sub new
151             {
152 0     0 1   my $class = shift;
153 0           my %port_data = @_;
154              
155 0           my $baudrate;
156             my %data;
157 0           my $raw_log_file;
158 0           foreach my $key (keys %port_data)
159             {
160 0 0         if (lc($key) eq "port")
161             {
162 0           $m_port_name = $port_data{$key};
163             }
164 0 0         if (lc($key) eq "baudrate")
165             {
166 0           $baudrate = $port_data{$key};
167             }
168 0 0         if (lc($key) eq "warn_malformed_kiss")
169             {
170 0           $data{'WARN_MALFORMED_KISS'} = 1;
171             }
172 0 0         if (lc($key) eq "file")
173             {
174 0           $data{'FILE'} = $port_data{$key};
175             }
176 0 0         if (lc($key) eq "raw_log")
177             {
178 0           $raw_log_file = $port_data{$key};
179             }
180             }
181              
182 0 0         if ($data{'FILE'})
183             {
184 0           my $file = new FileHandle();
185 0 0         if ($file->open("<$data{'FILE'}"))
186             {
187 0           $file->autoflush(1);
188 0           $data{'FILE_HANDLE'} = $file;
189             }
190             else
191             {
192 0           warn "Warning: Cannot open raw KISS log file \"$data{'FILE'}\" for reading: $!\n";
193 0           return undef;
194             }
195             }
196             else
197             {
198 0           $m_raw_log = new FileHandle();
199 0 0         if ($m_raw_log->open(">>$raw_log_file"))
200             {
201 0           $m_raw_log->autoflush(1);
202 0           $data{'RAW_LOG'} = $m_raw_log;
203             }
204             else
205             {
206 0           warn "Warning: Cannot open raw log file \"$raw_log_file\" for append: $!\n";
207             }
208              
209 0 0         unless ($m_port_name)
210             {
211 0           warn "Error: No port was specified in the passed data.\n";
212 0           return undef;
213             }
214 0 0         unless ($baudrate)
215             {
216 0           warn "Error: No baudrate was specified in the passed data.\n";
217 0           return undef;
218             }
219              
220 0 0         if ($Config{'osname'} eq "MSWin32")
221             {
222 0 0         $m_port = new Win32::SerialPort($m_port_name) or
223             warn "Error: Cannot open serial port \"$m_port_name\": $^E\n";
224             }
225             else
226             {
227 0 0         $m_port = new Device::SerialPort($m_port_name) or
228             warn "Error: Cannot open serial port \"$m_port_name\": $!\n";
229             }
230 0           $m_port->baudrate($baudrate);
231 0           $m_port->parity("none");
232 0           $m_port->databits(8);
233 0           $m_port->stopbits(1);
234 0           $m_port->handshake("none");
235 0 0         $m_port->read_interval(100) if $Config{'osname'} eq "MSWin32";
236 0           $m_port->read_char_time(0);
237 0           $m_port->read_const_time(1000);
238              
239 0           $data{'PORT'} = $m_port;
240             }
241 0           my $self = bless \%data, $class;
242              
243 0           return $self;
244             }
245              
246             # When we close try and close the port too.
247             DESTROY
248             {
249             # Close the serial port
250 0 0   0     $m_port->close() or
251             warn "Error: Failed to the serial port \"$m_port_name\": $!\n";
252              
253             # Close the raw log file if we have opened it.
254 0 0         $m_raw_log->close() if $m_raw_log;
255              
256             }
257              
258             ################################################################################
259              
260             =head2 B
261              
262             my $kiss_frame = $kiss_tnc->read_kiss_frame();
263             my @kiss_frame = $kiss_tnc->read_kiss_frame();
264              
265             This method reads a KISS frame from the TNC and returns it. This has not had
266             the FEND, FESC, TFEND and TFESC bytes stripped out.
267              
268             =cut
269              
270             sub read_kiss_frame
271             {
272 0     0 1   my $self = shift;
273 0           my @frame;
274 0           my $fend_count = 0;
275 0 0         if ($self->{'FILE'})
276             {
277 0           while(1)
278             {
279             # Processing the file one byte at a time
280 0           my $saw = $self->{'FILE_HANDLE'}->getc();
281              
282 0 0         $fend_count++ if (ord($saw) == $FEND);
283             # Make sure we don't add bytes to the frame that are before the first FEND
284 0 0         push @frame, $saw if $fend_count > 0;
285              
286 0 0         if ($fend_count == 2)
287             {
288 0 0         if (scalar @frame > 2)
289             {
290             # We have data in the frame so return it.
291 0           last;
292             }
293             else
294             {
295             # we have an empty frame or we got the end of one frame and the start of another.
296             # So start the search again.
297 0           $fend_count--;
298 0           @frame = ($saw);
299             }
300             }
301             }
302             }
303             else
304             {
305 0           while(1)
306             {
307             # Processing one byte at a time makes things nice and easy
308 0           my ($count,$saw) = $self->{'PORT'}->read(1);
309 0 0         if ($count > 0)
310             {
311 0 0         $self->{'RAW_LOG'}->write($saw) if $self->{'RAW_LOG'};
312 0 0         $fend_count++ if (ord($saw) == $FEND);
313             # Make sure we don't add bytes to the frame that are before the first FEND
314 0 0         push @frame, $saw if $fend_count > 0;
315              
316 0 0         if ($fend_count == 2)
317             {
318 0 0         if (scalar @frame > 2)
319             {
320             # We have data in the frame so return it.
321 0           last;
322             }
323             else
324             {
325             # we have an empty frame or we got the end of one frame and the start of another.
326             # So start the search again.
327 0           $fend_count--;
328 0           @frame = ($saw);
329             }
330             }
331             }
332             }
333             }
334 0           $self->{'LAST_KISS_FRAME_LENGTH'} = scalar @frame;
335              
336 0 0         if (wantarray)
337             {
338 0           return @frame;
339             }
340             else
341             {
342 0           my $frame = join '', @frame;
343 0           return $frame;
344             }
345             }
346              
347             ################################################################################
348              
349             =head2 B
350              
351             my ($kiss_type, $hdlc_frame) = $kiss_tnc->read_hdlc_frame();
352             my ($kiss_type, @hdlc_frame) = $kiss_tnc->read_hdlc_frame();
353              
354             This method reads a KISS frame from the TNC and strips out the KISS FEND, FESC,
355             TFEND and TFESC bytes and returns the KISS type byte followed by the HDLC frame.
356              
357             The value of type indicator byte is use to distinguish between command and data
358             frames.
359              
360             From: The KISS TNC: A simple Host-to-TNC communications protocol
361             L
362              
363             To distinguish between command and data frames on the host/TNC link, the first
364             byte of each asynchronous frame between host and TNC is a "type" indicator. This
365             type indicator byte is broken into two 4-bit nibbles so that the low-order
366             nibble indicates the command number (given in the table below) and the
367             high-order nibble indicates the port number for that particular command.
368             In systems with only one HDLC port, it is by definition Port 0. In multi-port
369             TNCs, the upper 4 bits of the type indicator byte can specify one of up to
370             sixteen ports. The following commands are defined in frames to the TNC (the
371             "Command" field is in hexadecimal):
372              
373             Command Function Comments
374             0 Data frame The rest of the frame is data to
375             be sent on the HDLC channel.
376              
377             1 TXDELAY The next byte is the transmitter
378             keyup delay in 10 ms units.
379             The default start-up value is 50
380             (i.e., 500 ms).
381              
382             2 P The next byte is the persistence
383             parameter, p, scaled to the range
384             0 - 255 with the following
385             formula:
386              
387             P = p * 256 - 1
388              
389             The default value is P = 63
390             (i.e., p = 0.25).
391              
392             3 SlotTime The next byte is the slot interval
393             in 10 ms units.
394             The default is 10 (i.e., 100ms).
395              
396             4 TXtail The next byte is the time to hold
397             up the TX after the FCS has been
398             sent, in 10 ms units. This command
399             is obsolete, and is included here
400             only for compatibility with some
401             existing implementations.
402              
403             5 FullDuplex The next byte is 0 for half duplex,
404             nonzero for full duplex.
405             The default is 0
406             (i.e., half duplex).
407              
408             6 SetHardware Specific for each TNC. In the
409             TNC-1, this command sets the
410             modem speed. Other implementations
411             may use this function for other
412             hardware-specific functions.
413              
414             FF Return Exit KISS and return control to a
415             higher-level program. This is useful
416             only when KISS is incorporated
417             into the TNC along with other
418             applications.
419              
420             The following types are defined in frames to the host:
421              
422             Type Function Comments
423              
424             0 Data frame Rest of frame is data from
425             the HDLC channel.
426              
427             No other types are defined; in particular, there is no provision for
428             acknowledging data or command frames sent to the TNC. KISS implementations must
429             ignore any unsupported command types. All KISS implementations must implement
430             commands 0,1,2,3 and 5; the others are optional.
431              
432             =cut
433              
434             sub read_hdlc_frame
435             {
436 0     0 1   my $self = shift;
437              
438             # This is what we will return
439 0           my @frame;
440             my $type;
441              
442             # This is not the simplest method of getting the data but it allows for
443             # for catching errors.
444 0           my @kiss = $self->read_kiss_frame();
445 0           for (my $location = 0; $location <= $#kiss; $location++)
446             {
447 0 0         if ($location == 0)
    0          
    0          
448             {
449             # We should find an FEND here. If not print a warning.
450 0 0         unless (ord($kiss[$location]) == $FEND)
451             {
452 0 0         warn "Warning: Malformed KISS frame read. Didn't start with FEND\n" if $self->{'WARN_MALFORMED_KISS'};
453             }
454             }
455             elsif ($location == $#kiss)
456             {
457             # We should find an FEND here. If not print a warning.
458 0 0         unless (ord($kiss[$location]) == $FEND)
459             {
460 0 0         warn "Warning: Malformed KISS frame read. Didn't end with FEND\n" if $self->{'WARN_MALFORMED_KISS'};
461             }
462             }
463             elsif ($location == 1)
464             {
465             # This is the type byte
466 0           $type = $kiss[$location];
467             }
468             else
469             {
470             # this is the data but may contains transposed bytes
471             # FEND, FESC, TFEND and TFESC
472 0 0         if (ord($kiss[$location]) == $FESC)
473             {
474             # Entered frame escape mode
475 0 0         if (ord($kiss[$location + 1]) == $TFESC)
    0          
    0          
    0          
476             {
477             #warn "Un-Transposed a FESC\n";
478 0           push @frame, pack("c", $FESC);
479 0           $location++;
480             }
481             elsif (ord($kiss[$location + 1]) == $TFEND)
482             {
483             #warn "Un-Transposed a FEND\n";
484 0           push @frame, pack("c", $FEND);
485 0           $location++;
486             }
487             elsif (ord($kiss[$location + 1]) != $TFESC)
488             {
489 0 0         warn "Warning: Malformed KISS frame read. Expected TFESC after FESC\n" if $self->{'WARN_MALFORMED_KISS'};
490             }
491             elsif (ord($kiss[$location + 1]) != $TFEND)
492             {
493 0 0         warn "Warning: Malformed KISS frame read. Expected TFEND after FESC\n" if $self->{'WARN_MALFORMED_KISS'};
494             }
495             }
496             else
497             {
498 0           push @frame, $kiss[$location];
499             }
500             }
501             }
502              
503 0 0         if (wantarray)
504             {
505 0           return $type, @frame;
506             }
507             else
508             {
509 0           my $frame = join '', @frame;
510 0           return $type, $frame;
511             }
512             }
513              
514             1;
515              
516             __END__