Squelch compiler warnings.
[metze/wireshark/wip.git] / wiretap / cosine.c
1 /* cosine.c
2  *
3  * CoSine IPNOS L2 debug output parsing
4  * Copyright (c) 2002 by Motonori Shindo <motonori@shin.do>
5  *
6  * Wiretap Library
7  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include "config.h"
25 #include "wtap-int.h"
26 #include "cosine.h"
27 #include "file_wrappers.h"
28
29 #include <stdlib.h>
30 #include <string.h>
31
32 /*
33
34   IPNOS: CONFIG VPN(100) VR(1.1.1.1)# diags
35   ipnos diags: Control (1/0) :: layer-2 ?
36   Registered commands for area "layer-2"
37       apply-pkt-log-profile  Configure packet logging on an interface
38       create-pkt-log-profile  Set packet-log-profile to be used for packet logging (see layer-2 pkt-log)
39       detail                Get Layer 2 low-level details
40
41   ipnos diags: Control (1/0) :: layer-2 create ?
42       create-pkt-log-profile  <pkt-log-profile-id ctl-tx-trace-length ctl-rx-trace-length data-tx-trace-length data-rx-trace-length pe-logging-or-control-blade>
43
44   ipnos diags: Control (1/0) :: layer-2 create 1 32 32 0 0 0
45   ipnos diags: Control (1/0) :: layer-2 create 2 32 32 100 100 0
46   ipnos diags: Control (1/0) :: layer-2 apply ?
47       apply-pkt-log-profile  <slot port channel subif pkt-log-profile-id>
48
49   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 1
50   Successfully applied packet-log-profile on LI
51
52   -- Note that only the control packets are logged because the data packet size parameters are 0 in profile 1
53   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# ping 20.20.20.43
54   vpn 200 : [max tries 4, timeout 5 seconds, data length 64 bytes, ttl 255]
55   ping #1 ok, RTT 0.000 seconds
56   ping #2 ok, RTT 0.000 seconds
57   ping #3 ok, RTT 0.000 seconds
58   ping #4 ok, RTT 0.000 seconds
59   [finished]
60
61   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# 2000-2-1,18:19:46.8:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
62
63
64   2000-2-1,18:19:46.8:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
65
66   2000-2-1,18:19:46.8:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
67
68   2000-2-1,18:19:46.8:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x8030000]
69
70   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 0
71   Successfully applied packet-log-profile on LI
72   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 2
73   Successfully applied packet-log-profile on LI
74
75   -- Note that both control and data packets are logged because the data packet size parameter is 100 in profile 2
76      Please ignore the event-log messages getting mixed up with the ping command
77   ping 20.20.20.43 cou2000-2-1,18:20:17.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
78
79           00 D0 D8 D2 FF 03 C0 21  09 29 00 08 6B 60 84 AA
80
81   2000-2-1,18:20:17.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
82           00 D0 D8 D2 FF 03 C0 21  09 29 00 08 6D FE FA AA
83
84   2000-2-1,18:20:17.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
85           00 D0 D8 D2 FF 03 C0 21  0A 29 00 08 6B 60 84 AA
86
87   2000-2-1,18:20:17.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x8030000]
88           00 D0 D8 D2 FF 03 C0 21  0A 29 00 08 6D FE FA AA
89
90   nt 1 length 500
91   vpn 200 : [max tries 1, timeout 5 seconds, data length 500 bytes, ttl 255]
92   2000-2-1,18:20:24.1:  l2-tx (PPP:3/7/1:100), Length:536, Pro:1, Off:8, Pri:7, RM:0, Err:0 [0x4070, 0x801]
93           00 D0 D8 D2 FF 03 00 21  45 00 02 10 00 27 00 00
94           FF 01 69 51 14 14 14 22  14 14 14 2B 08 00 AD B8
95           00 03 00 01 10 11 12 13  14 15 16 17 18 19 1A 1B
96           1C 1D 1E 1F 20 21 22 23  24 25 26 27 28 29 2A 2B
97           2C 2D 2E 2F 30 31 32 33  34 35 36 37 38 39 3A 3B
98           3C 3D 3E 3F 40 41 42 43  44 45 46 47 48 49 4A 4B
99           4C 4D 4E 4F
100
101   ping #1 ok, RTT 0.010 seconds
102   2000-2-1,18:20:24.1:  l2-rx (PPP:3/7/1:100), Length:536, Pro:1, Off:8, Pri:7, RM:0, Err:0 [0x4071, 0x30801]
103           00 D0 D8 D2 FF 03 00 21  45 00 02 10 00 23 00 00
104           FF 01 69 55 14 14 14 2B  14 14 14 22 00 00 B5 B8
105           00 03 00 01 10 11 12 13  14 15 16 17 18 19 1A 1B
106           1C 1D 1E 1F 20 21 22 23  24 25 26 27 28 29 2A 2B
107           2C 2D 2E 2F 30 31 32 33  34 35 36 37 38 39 3A 3B
108           3C 3D 3E 3F 40 41 42 43  44 45 46 47 48 49 4A 4B
109           4C 4D 4E 4F
110
111   [finished]
112
113   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# 2000-2-1,18:20:27.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
114
115           00 D0 D8 D2 FF 03 C0 21  09 2A 00 08 6B 60 84 AA
116
117   2000-2-1,18:20:27.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
118           00 D0 D8 D2 FF 03 C0 21  09 2A 00 08 6D FE FA AA
119
120   2000-2-1,18:20:27.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
121           00 D0 D8 D2 FF 03 C0 21  0A 2A 00 08 6B 60 84 AA
122
123   2000-2-1,18:20:27.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
124           00 D0 D8 D2 FF 03 C0 21  0A 2A 00 08 6D FE FA AA
125
126
127   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 0
128   Successfully applied packet-log-profile on LI
129   ipnos diags: Control (1/0) ::
130
131  */
132
133 /* XXX TODO:
134
135   o Handle a case where an empty line doesn't exists as a delimiter of
136     each packet. If the output is sent to a control blade and
137     displayed as an event log, there's always an empty line between
138     each packet output, but it may not be true when it is an PE
139     output.
140
141   o Some telnet client on Windows may put in a line break at 80
142     columns when it save the session to a text file ("CRT" is such an
143     example). I don't think it's a good idea for the telnet client to
144     do so, but CRT is widely used in Windows community, I should
145     take care of that in the future.
146
147 */
148
149 /* Magic text to check for CoSine L2 debug output */
150 #define COSINE_HDR_MAGIC_STR1   "l2-tx"
151 #define COSINE_HDR_MAGIC_STR2   "l2-rx"
152
153 /* Magic text for start of packet */
154 #define COSINE_REC_MAGIC_STR1   COSINE_HDR_MAGIC_STR1
155 #define COSINE_REC_MAGIC_STR2   COSINE_HDR_MAGIC_STR2
156
157 #define COSINE_HEADER_LINES_TO_CHECK    200
158 #define COSINE_LINE_LENGTH              240
159
160 static gboolean empty_line(const gchar *line);
161 static gint64 cosine_seek_next_packet(wtap *wth, int *err, gchar **err_info,
162         char *hdr);
163 static gboolean cosine_check_file_type(wtap *wth, int *err, gchar **err_info);
164 static gboolean cosine_read(wtap *wth, int *err, gchar **err_info,
165         gint64 *data_offset);
166 static gboolean cosine_seek_read(wtap *wth, gint64 seek_off,
167         struct wtap_pkthdr *phdr, Buffer *buf, int *err, gchar **err_info);
168 static int parse_cosine_packet(FILE_T fh, struct wtap_pkthdr *phdr, Buffer* buf,
169         char *line, int *err, gchar **err_info);
170 static int parse_single_hex_dump_line(char* rec, guint8 *buf,
171         guint byte_offset);
172
173 /* Returns TRUE if the line appears to be an empty line. Otherwise it
174    returns FALSE. */
175 static gboolean empty_line(const gchar *line)
176 {
177         while (*line) {
178                 if (g_ascii_isspace(*line)) {
179                         line++;
180                         continue;
181                 } else {
182                         break;
183                 }
184         }
185         if (*line == '\0')
186                 return TRUE;
187         else
188                 return FALSE;
189 }
190
191 /* Seeks to the beginning of the next packet, and returns the
192    byte offset. Copy the header line to hdr. Returns -1 on failure,
193    and sets "*err" to the error and sets "*err_info" to null or an
194    additional error string. */
195 static gint64 cosine_seek_next_packet(wtap *wth, int *err, gchar **err_info,
196         char *hdr)
197 {
198         gint64 cur_off;
199         char buf[COSINE_LINE_LENGTH];
200
201         while (1) {
202                 cur_off = file_tell(wth->fh);
203                 if (cur_off == -1) {
204                         /* Error */
205                         *err = file_error(wth->fh, err_info);
206                         return -1;
207                 }
208                 if (file_gets(buf, sizeof(buf), wth->fh) == NULL) {
209                         *err = file_error(wth->fh, err_info);
210                         return -1;
211                 }
212                 if (strstr(buf, COSINE_REC_MAGIC_STR1) ||
213                     strstr(buf, COSINE_REC_MAGIC_STR2)) {
214                         g_strlcpy(hdr, buf, COSINE_LINE_LENGTH);
215                         return cur_off;
216                 }
217         }
218         return -1;
219 }
220
221 /* Look through the first part of a file to see if this is
222  * a CoSine L2 debug output.
223  *
224  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
225  * if we get an I/O error, "*err" will be set to a non-zero value and
226  * "*err_info" will be set to null or an additional error string.
227  */
228 static gboolean cosine_check_file_type(wtap *wth, int *err, gchar **err_info)
229 {
230         char    buf[COSINE_LINE_LENGTH];
231         gsize   reclen;
232         guint   line;
233
234         buf[COSINE_LINE_LENGTH-1] = '\0';
235
236         for (line = 0; line < COSINE_HEADER_LINES_TO_CHECK; line++) {
237                 if (file_gets(buf, COSINE_LINE_LENGTH, wth->fh) == NULL) {
238                         /* EOF or error. */
239                         *err = file_error(wth->fh, err_info);
240                         return FALSE;
241                 }
242
243                 reclen = strlen(buf);
244                 if (reclen < strlen(COSINE_HDR_MAGIC_STR1) ||
245                         reclen < strlen(COSINE_HDR_MAGIC_STR2)) {
246                         continue;
247                 }
248
249                 if (strstr(buf, COSINE_HDR_MAGIC_STR1) ||
250                     strstr(buf, COSINE_HDR_MAGIC_STR2)) {
251                         return TRUE;
252                 }
253         }
254         *err = 0;
255         return FALSE;
256 }
257
258
259 wtap_open_return_val cosine_open(wtap *wth, int *err, gchar **err_info)
260 {
261         /* Look for CoSine header */
262         if (!cosine_check_file_type(wth, err, err_info)) {
263                 if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
264                         return WTAP_OPEN_ERROR;
265                 return WTAP_OPEN_NOT_MINE;
266         }
267
268         if (file_seek(wth->fh, 0L, SEEK_SET, err) == -1)        /* rewind */
269                 return WTAP_OPEN_ERROR;
270
271         wth->file_encap = WTAP_ENCAP_COSINE;
272         wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_COSINE;
273         wth->snapshot_length = 0; /* not known */
274         wth->subtype_read = cosine_read;
275         wth->subtype_seek_read = cosine_seek_read;
276         wth->file_tsprec = WTAP_TSPREC_CSEC;
277
278         return WTAP_OPEN_MINE;
279 }
280
281 /* Find the next packet and parse it; called from wtap_read(). */
282 static gboolean cosine_read(wtap *wth, int *err, gchar **err_info,
283     gint64 *data_offset)
284 {
285         gint64  offset;
286         char    line[COSINE_LINE_LENGTH];
287
288         /* Find the next packet */
289         offset = cosine_seek_next_packet(wth, err, err_info, line);
290         if (offset < 0)
291                 return FALSE;
292         *data_offset = offset;
293
294         /* Parse the header and convert the ASCII hex dump to binary data */
295         return parse_cosine_packet(wth->fh, &wth->phdr, wth->frame_buffer,
296             line, err, err_info);
297 }
298
299 /* Used to read packets in random-access fashion */
300 static gboolean
301 cosine_seek_read(wtap *wth, gint64 seek_off, struct wtap_pkthdr *phdr,
302         Buffer *buf, int *err, gchar **err_info)
303 {
304         char    line[COSINE_LINE_LENGTH];
305
306         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
307                 return FALSE;
308
309         if (file_gets(line, COSINE_LINE_LENGTH, wth->random_fh) == NULL) {
310                 *err = file_error(wth->random_fh, err_info);
311                 if (*err == 0) {
312                         *err = WTAP_ERR_SHORT_READ;
313                 }
314                 return FALSE;
315         }
316
317         /* Parse the header and convert the ASCII hex dump to binary data */
318         return parse_cosine_packet(wth->random_fh, phdr, buf, line, err,
319             err_info);
320 }
321
322 /* Parses a packet record header. There are two possible formats:
323     1) output to a control blade with date and time
324         2002-5-10,20:1:31.4:  l2-tx (FR:3/7/1:1), Length:18, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
325     2) output to PE without date and time
326         l2-tx (FR:3/7/1:1), Length:18, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0] */
327 static gboolean
328 parse_cosine_packet(FILE_T fh, struct wtap_pkthdr *phdr, Buffer *buf,
329     char *line, int *err, gchar **err_info)
330 {
331         union wtap_pseudo_header *pseudo_header = &phdr->pseudo_header;
332         int     num_items_scanned;
333         int     yy, mm, dd, hr, min, sec, csec, pkt_len;
334         int     pro, off, pri, rm, error;
335         guint   code1, code2;
336         char    if_name[COSINE_MAX_IF_NAME_LEN] = "", direction[6] = "";
337         struct  tm tm;
338         guint8 *pd;
339         int     i, hex_lines, n, caplen = 0;
340
341         if (sscanf(line, "%4d-%2d-%2d,%2d:%2d:%2d.%9d:",
342                    &yy, &mm, &dd, &hr, &min, &sec, &csec) == 7) {
343                 /* appears to be output to a control blade */
344                 num_items_scanned = sscanf(line,
345                    "%4d-%2d-%2d,%2d:%2d:%2d.%9d: %5s (%127[A-Za-z0-9/:]), Length:%9d, Pro:%9d, Off:%9d, Pri:%9d, RM:%9d, Err:%9d [%8x, %8x]",
346                         &yy, &mm, &dd, &hr, &min, &sec, &csec,
347                                    direction, if_name, &pkt_len,
348                                    &pro, &off, &pri, &rm, &error,
349                                    &code1, &code2);
350
351                 if (num_items_scanned != 17) {
352                         *err = WTAP_ERR_BAD_FILE;
353                         *err_info = g_strdup("cosine: purported control blade line doesn't have code values");
354                         return FALSE;
355                 }
356         } else {
357                 /* appears to be output to PE */
358                 num_items_scanned = sscanf(line,
359                    "%5s (%127[A-Za-z0-9/:]), Length:%9d, Pro:%9d, Off:%9d, Pri:%9d, RM:%9d, Err:%9d [%8x, %8x]",
360                                    direction, if_name, &pkt_len,
361                                    &pro, &off, &pri, &rm, &error,
362                                    &code1, &code2);
363
364                 if (num_items_scanned != 10) {
365                         *err = WTAP_ERR_BAD_FILE;
366                         *err_info = g_strdup("cosine: header line is neither control blade nor PE output");
367                         return FALSE;
368                 }
369                 yy = mm = dd = hr = min = sec = csec = 0;
370         }
371         if (pkt_len < 0) {
372                 *err = WTAP_ERR_BAD_FILE;
373                 *err_info = g_strdup("cosine: packet header has a negative packet length");
374                 return FALSE;
375         }
376         if (pkt_len > WTAP_MAX_PACKET_SIZE) {
377                 /*
378                  * Probably a corrupt capture file; don't blow up trying
379                  * to allocate space for an immensely-large packet.
380                  */
381                 *err = WTAP_ERR_BAD_FILE;
382                 *err_info = g_strdup_printf("cosine: File has %u-byte packet, bigger than maximum of %u",
383                     pkt_len, WTAP_MAX_PACKET_SIZE);
384                 return FALSE;
385         }
386
387         phdr->rec_type = REC_TYPE_PACKET;
388         phdr->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
389         tm.tm_year = yy - 1900;
390         tm.tm_mon = mm - 1;
391         tm.tm_mday = dd;
392         tm.tm_hour = hr;
393         tm.tm_min = min;
394         tm.tm_sec = sec;
395         tm.tm_isdst = -1;
396         phdr->ts.secs = mktime(&tm);
397         phdr->ts.nsecs = csec * 10000000;
398         phdr->len = pkt_len;
399
400         /* XXX need to handle other encapsulations like Cisco HDLC,
401            Frame Relay and ATM */
402         if (strncmp(if_name, "TEST:", 5) == 0) {
403                 pseudo_header->cosine.encap = COSINE_ENCAP_TEST;
404         } else if (strncmp(if_name, "PPoATM:", 7) == 0) {
405                 pseudo_header->cosine.encap = COSINE_ENCAP_PPoATM;
406         } else if (strncmp(if_name, "PPoFR:", 6) == 0) {
407                 pseudo_header->cosine.encap = COSINE_ENCAP_PPoFR;
408         } else if (strncmp(if_name, "ATM:", 4) == 0) {
409                 pseudo_header->cosine.encap = COSINE_ENCAP_ATM;
410         } else if (strncmp(if_name, "FR:", 3) == 0) {
411                 pseudo_header->cosine.encap = COSINE_ENCAP_FR;
412         } else if (strncmp(if_name, "HDLC:", 5) == 0) {
413                 pseudo_header->cosine.encap = COSINE_ENCAP_HDLC;
414         } else if (strncmp(if_name, "PPP:", 4) == 0) {
415                 pseudo_header->cosine.encap = COSINE_ENCAP_PPP;
416         } else if (strncmp(if_name, "ETH:", 4) == 0) {
417                 pseudo_header->cosine.encap = COSINE_ENCAP_ETH;
418         } else {
419                 pseudo_header->cosine.encap = COSINE_ENCAP_UNKNOWN;
420         }
421         if (strncmp(direction, "l2-tx", 5) == 0) {
422                 pseudo_header->cosine.direction = COSINE_DIR_TX;
423         } else if (strncmp(direction, "l2-rx", 5) == 0) {
424                 pseudo_header->cosine.direction = COSINE_DIR_RX;
425         }
426         g_strlcpy(pseudo_header->cosine.if_name, if_name,
427                 COSINE_MAX_IF_NAME_LEN);
428         pseudo_header->cosine.pro = pro;
429         pseudo_header->cosine.off = off;
430         pseudo_header->cosine.pri = pri;
431         pseudo_header->cosine.rm = rm;
432         pseudo_header->cosine.err = error;
433
434         /* Make sure we have enough room for the packet */
435         ws_buffer_assure_space(buf, pkt_len);
436         pd = ws_buffer_start_ptr(buf);
437
438         /* Calculate the number of hex dump lines, each
439          * containing 16 bytes of data */
440         hex_lines = pkt_len / 16 + ((pkt_len % 16) ? 1 : 0);
441
442         for (i = 0; i < hex_lines; i++) {
443                 if (file_gets(line, COSINE_LINE_LENGTH, fh) == NULL) {
444                         *err = file_error(fh, err_info);
445                         if (*err == 0) {
446                                 *err = WTAP_ERR_SHORT_READ;
447                         }
448                         return FALSE;
449                 }
450                 if (empty_line(line)) {
451                         break;
452                 }
453                 if ((n = parse_single_hex_dump_line(line, pd, i*16)) == -1) {
454                         *err = WTAP_ERR_BAD_FILE;
455                         *err_info = g_strdup("cosine: hex dump line doesn't have 16 numbers");
456                         return FALSE;
457                 }
458                 caplen += n;
459         }
460         phdr->caplen = caplen;
461         return TRUE;
462 }
463
464 /* Take a string representing one line from a hex dump and converts
465  * the text to binary data. We place the bytes in the buffer at the
466  * specified offset.
467  *
468  * Returns number of bytes successfully read, -1 if bad.  */
469 static int
470 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
471 {
472         int num_items_scanned, i;
473         unsigned int bytes[16];
474
475         num_items_scanned = sscanf(rec, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
476                                &bytes[0], &bytes[1], &bytes[2], &bytes[3],
477                                &bytes[4], &bytes[5], &bytes[6], &bytes[7],
478                                &bytes[8], &bytes[9], &bytes[10], &bytes[11],
479                                &bytes[12], &bytes[13], &bytes[14], &bytes[15]);
480         if (num_items_scanned == 0)
481                 return -1;
482
483         if (num_items_scanned > 16)
484                 num_items_scanned = 16;
485
486         for (i=0; i<num_items_scanned; i++) {
487                 buf[byte_offset + i] = (guint8)bytes[i];
488         }
489
490         return num_items_scanned;
491 }
492
493 /*
494  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
495  *
496  * Local variables:
497  * c-basic-offset: 8
498  * tab-width: 8
499  * indent-tabs-mode: t
500  * End:
501  *
502  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
503  * :indentSize=8:tabSize=8:noTabs=false:
504  */