File Coverage

blib/lib/Device/TNC.pm
Criterion Covered Total %
statement 12 91 13.1
branch 0 48 0.0
condition n/a
subroutine 4 6 66.6
pod 1 1 100.0
total 17 146 11.6


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Device::TNC - A generic interface to a TNC
5              
6             =head1 DESCRIPTION
7              
8             This module implements a generic interface to a Terminal Node Controller (TNC).
9              
10             It loads sub classes that provide the low level interface for the appropriate
11             TNC to be used and provides higher level methods to return frames of data to the
12             user is human readable form.
13              
14             =head1 SYNOPSIS
15              
16             use Device::TNC;
17             my $tnc_type = 'KISS';
18             my %tnc_config = (
19             'port' => ($Config{'osname'} eq "MSWin32") ? "COM3" : "/dev/TNC-X",
20             'baudrate' => 9600,
21             'warn_malformed_kiss' => 1,
22             'raw_log' => "raw_packet.log",
23             );
24             my $tnc = new Device::TNC($tnc_type, %tnc_config);
25             die "Error: Something went wrong connecting to the TNC.\n" unless $tnc;
26              
27             while (1)
28             {
29             my $data = $tnc->read_frame();
30             my $repeaters = join ", ", @{$data->{'ADDRESS'}->{'REPEATERS'}};
31             my $info = join "", @{$data->{'INFO'}};
32             print "From: $data->{'ADDRESS'}->{'SOURCE'} ";
33             print "To: $data->{'ADDRESS'}->{'DESTINATION'} ";
34             print "via $repeaters\n";
35             print "Data: $info\n";
36             }
37              
38             =cut
39              
40             package Device::TNC;
41              
42             ####################
43             # Standard Modules
44 1     1   5 use strict;
  1         1  
  1         25  
45 1     1   4 use Config;
  1         2  
  1         29  
46 1     1   708 use Data::Translate;
  1         872  
  1         78  
47             # Custom modules
48              
49             ####################
50             # Variables
51 1     1   5 use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
  1         1  
  1         965  
52             @ISA = qw();
53             @EXPORT = qw();
54             @EXPORT_OK = qw();
55             $VERSION = 0.03;
56             $| = 1;
57              
58             my $translator = new Data::Translate();
59              
60             # I'm not sure what this was for... It came from the KISS2ASC program that I
61             # originally based a lot of this on. I've since updated everything that is here
62             # to match/comply with what the AX.25 standard doc says.
63             # I'm not using it now and will remove soon unless I find the reason it was in
64             # KISS2ASC.
65              
66             # pollcode gives the flags used to represent the various sets of poll and final flags.
67             # It is indexed by three bits:
68             # pollcode[0x10 bit of control][0x80 bit of destcall][0x80 bit of fromcall]
69             #
70             # We use the following codes, Followed WA8DED:
71             # ! Version 1 with poll/final
72             # ^ Version 2 command without poll
73             # + Version 2 command with poll
74             # - Version 2 response with final
75             # v Version 2 response without final
76             # 0 in the table indicates no charater is to be output.
77             #
78             my @m_pollcode;
79             $m_pollcode[0]->[0]->[0] = 0;
80             $m_pollcode[0]->[0]->[1] = 'v';
81             $m_pollcode[0]->[1]->[0] = '^';
82             $m_pollcode[0]->[1]->[1] = 0;
83             $m_pollcode[1]->[0]->[0] = '!';
84             $m_pollcode[1]->[0]->[1] = '-';
85             $m_pollcode[1]->[1]->[0] = '+';
86             $m_pollcode[1]->[1]->[1] = '!';
87              
88             # This is how this array is used.
89             #my $val1 = ($control & 0x10) ? 1 : 0;
90             #my $val2 = (ord($frame[6]) & 0x80) ? 1 : 0;
91             #my $val3 = (ord($frame[13]) & 0x80) ? 1 : 0;
92             #$data{'POLLCHAR'} = $m_pollcode[$val1]->[$val2]->[$val3];
93              
94             ####################
95             # Functions
96              
97             ################################################################################
98              
99             =head2 B
100              
101             my $type = "KISS";
102             my %tnc_data = { 'option' => 'value' };
103             my $tnc = new Device::TNC($type, %tnc_data);
104              
105             The new method creates and returns a new Device::TNC object that can be
106             used to communicate with a Terminal Node Controller (TNC) of the type passed.
107              
108             The method requires that the first passed argument be the type of TNC to connect
109             to. This will try and load the appropriate module for the TNC type.
110              
111             The subsequent options are passed to the module that is loaded to connect to the
112             desired TNC.
113              
114             For more details on these options see the module documentation for the TNC type.
115              
116             =cut
117              
118             sub new
119             {
120 0     0     my $class = shift;
121 0           my $type = uc(shift);
122 0           my %tnc_data = @_;
123 0           my $tnc = undef;
124              
125 0 0         unless (scalar $type)
126             {
127 0           warn "Error: No TNC type passed.\n";
128 0           return undef;
129             }
130              
131 0           my $load_module = "require Device::TNC::$type;\n";
132 0           $load_module .= '$tnc' . " = new Device::TNC::$type(%tnc_data);\n";
133 0           eval $load_module;
134 0 0         if ($@)
135             {
136 0           warn "$@\n";
137 0           warn "Error: Failed to load Device::TNC::$type\n";
138             }
139              
140 0           return $tnc;
141             }
142              
143             ################################################################################
144              
145             =head2 B
146              
147             my $frame_data = $tnc->read_frame();
148             my %frame_data = $tnc->read_frame();
149              
150             This method reads a HDLC frame from the TNC and returns a structure as either a
151             hash or a hash reference that contains the fields of the frame.
152              
153             The structure of the returned data is like the following.
154              
155             {
156             'INFO' => [
157             '/', '0', '6', '4', '6', '5', '8', 'h', '3', '3', '5', '0', '.', '0', '0',
158             'S', '\\', '1', '5', '1', '1', '2', '.', '0', '0', 'E', 'O', '2', '2', '6',
159             '/', '0', '0', '0', '/', 'A', '=', '0', '0', '0', '1', '1', '1'
160             ],
161             'PID' => 'F0',
162             'CONTROL' => {
163             'POLL_FINAL' => 0,
164             'FIELD_TYPE' => 'UI',
165             'FRAME_TYPE' => 'U'
166             },
167             'ADDRESS' => {
168             'DESTINATION' => 'APT311',
169             'REPEATERS' => [
170             'WIDE1-1',
171             'WIDE2-2'
172             ],
173             'SOURCE' => 'VK2KFJ-7'
174             }
175             }
176              
177             While developing this module I only received U (UI) type frames and so
178             development of the code to work with I and S frames didn't really progress.
179             If anyone want's to read I or S frames please let me know and I'll have a look
180             at implementing them. Please create a KISS log of the data and email it to me.
181              
182             =cut
183              
184             sub read_frame
185             {
186 0     0 1   my $self = shift;
187 0           my %data;
188 0           my ($type, @frame) = $self->read_hdlc_frame();
189              
190 0           my $location = 0;
191             # Get the destination
192 0           for (my $loc = 0; $loc < 7; $loc++)
193             {
194 0           my $byte = ord($frame[$location]);
195 0 0         if ($byte != 0x40)
196             {
197 0           my $shift_byte = ($byte >> 1);
198 0           my ($s, $ascii) = $translator->h2a(sprintf("%X",$shift_byte));
199 0 0         if ($loc == 6)
200             {
201 0           my $ssid = ($byte & 0x1E) >> 1;
202 0 0         $data{'ADDRESS'}->{'DESTINATION'} .= "-$ssid" if $ssid > 0;
203             }
204             else
205             {
206 0           $data{'ADDRESS'}->{'DESTINATION'} .= $ascii;
207             }
208             }
209 0           $location++;
210             }
211             # Get the source
212 0           for (my $loc = 0; $loc < 7; $loc++)
213             {
214 0           my $byte = ord($frame[$location]);
215 0 0         if ($byte != 0x40)
216             {
217 0           my $shift_byte = ($byte >> 1);
218 0           my ($s, $ascii) = $translator->h2a(sprintf("%X",$shift_byte));
219 0 0         if ($loc == 6)
220             {
221 0           my $ssid = ($byte & 0x1E) >> 1;
222 0 0         $data{'ADDRESS'}->{'SOURCE'} .= "-$ssid" if $ssid > 0;
223             }
224             else
225             {
226 0           $data{'ADDRESS'}->{'SOURCE'} .= $ascii;
227             }
228             }
229 0           $location++;
230             }
231              
232             # Find the repeaters if any.
233 0           @{$data{'ADDRESS'}->{'REPEATERS'}} = ();
  0            
234 0           my $control = ord($frame[$location]);
235 0           while (($control & 1) == 0)
236             {
237 0           my $repeater = "";
238 0           for (my $loc = 0; $loc < 7; $loc++)
239             {
240 0           my $byte = ord($frame[$location]);
241 0 0         if ($byte != 0x40)
242             {
243 0           my $shift_byte = ($byte >> 1);
244 0           my ($s, $ascii) = $translator->h2a(sprintf("%X",$shift_byte));
245 0 0         if ($loc == 6)
246             {
247 0           my $ssid = ($byte & 0x1E) >> 1;
248             #printf(" SSID: %d\n", $ssid);
249 0 0         $repeater .= "-$ssid" if $ssid > 0;
250             }
251             else
252             {
253 0           $repeater .= $ascii;
254             }
255             }
256 0           $location++;
257             }
258 0           push @{$data{'ADDRESS'}->{'REPEATERS'}}, $repeater;
  0            
259 0           $control = ord($frame[$location]);
260             }
261              
262             # Now find the frame type
263 0           $location++;
264 0 0         if (($control & 1) == 0) # Information (I) frame found
    0          
    0          
265             {
266             # No data gathered to work with here.
267              
268             # Control field is 1 or 2 bytes
269             # push @{$data{'CONTROL'}}, $control, ord($frame[$location]);
270             # push @{$data{'CRAP'}->{'CONTROL_BIN'}}, $translator->d2b($control), $translator->d2b(ord($frame[$location]));
271              
272             # $data{'CRAP'}->{'FRAME_TYPE'} = sprintf("I%d%d", (($control & 0xE0) >> 5), (($control & 0x0E) >> 1));
273             # $data{'PID'} = sprintf("%02X", (ord($frame[$location]) & 0xFF));
274             # while ( (my $byte = $frame[$location]) and ( ($location ne $#frame) or ($location ne $#frame - 1)) )
275             # {
276             # #my ($s, $ascii) = $translator->h2a(sprintf("%X",$byte));
277             # push @{$data{'INFO'}}, $byte;
278             # $location++;
279             # }
280             }
281             elsif (($control & 3) == 1) # Supervisory (S) frame found
282             {
283             # No data gathered to work with here.
284              
285             # Control field is 1 or 2 bytes
286             # push @{$data{'CONTROL'}}, $control, ord($frame[$location]);
287             # push @{$data{'CRAP'}->{'CONTROL_BIN'}}, $translator->d2b($control), $translator->d2b(ord($frame[$location]));
288              
289             # $data{'CRAP'}->{'FRAME_TYPE'} = sprintf("%s%d", (($control & 0x0C) >> 2), (($control & 0xE0) >> 5) );
290             # $data{'CRAP'}->{'FRAME_TYPE'} .= sprintf(" FRAME TYPE IN HEX = %X for 0C or maybe %X for EF it's number is %d", ($control & 0x0C), ($control & 0xEF), (($control & 0xE0) >> 5) );
291             }
292             elsif (($control & 0xEF) == 0x03) # Unnumbered (U) frame found
293             {
294             # Control field is 1 byte
295 0           $data{'CONTROL'}->{'FRAME_TYPE'} = "U";
296 0 0         $data{'CONTROL'}->{'POLL_FINAL'} = ($control & 0x10) ? 1 : 0;
297 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "SABM" if ($control & 0xEF) == 0x2F;
298 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "DISC" if ($control & 0xEF) == 0x43;
299 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "DM" if ($control & 0xEF) == 0x0F;
300 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "UA" if ($control & 0xEF) == 0x63;
301 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "FRMR" if ($control & 0xEF) == 0x87;
302 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "UI" if ($control & 0xEF) == 0x03;
303 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "XID" if ($control & 0xEF) == 0xAF;
304 0 0         $data{'CONTROL'}->{'FIELD_TYPE'} = "TEST" if ($control & 0xEF) == 0xE3;
305              
306 0           $data{'PID'} = sprintf("%02X", ord($frame[$location]));
307 0           $location++;
308 0           while ($location <= $#frame)
309             {
310 0           my $byte = $frame[$location];
311             #my ($s, $ascii) = $translator->h2a(sprintf("%X",$byte));
312 0           push @{$data{'INFO'}}, $byte;
  0            
313 0           $location++;
314             }
315             }
316             else
317             {
318 0           warn "Error: Couldn't determine the frame type.\n";
319             }
320              
321             #Finally get the Frame Check Sequence which is the last two bytes of the frame
322             #$data{'FCS'} = sprintf("%02X", ord($frame[$#frame - 1])) . sprintf("%02X", ord($frame[$#frame]));
323              
324 0 0         if (wantarray)
325             {
326 0           return %data;
327             }
328             else
329             {
330 0           return \%data;
331             }
332             }
333              
334             1;
335              
336             __END__