/src/wireshark/epan/dissectors/packet-cups.c
Line | Count | Source |
1 | | /* packet-cups.c |
2 | | * Routines for Common Unix Printing System (CUPS) Browsing Protocol |
3 | | * packet disassembly for the Wireshark network traffic analyzer. |
4 | | * |
5 | | * Charles Levert <charles@comm.polymtl.ca> |
6 | | * Copyright 2001 Charles Levert |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | * |
10 | | */ |
11 | | |
12 | | #include "config.h" |
13 | | |
14 | | #include <epan/packet.h> |
15 | | #include <epan/tfs.h> |
16 | | #include <wsutil/str_util.h> |
17 | | |
18 | | /**********************************************************************/ |
19 | | |
20 | | void proto_register_cups(void); |
21 | | void proto_reg_handoff_cups(void); |
22 | | |
23 | | static dissector_handle_t cups_handle; |
24 | | |
25 | | /* From cups/cups.h, GNU GPL, Copyright 1997-2001 by Easy Software Products. */ |
26 | | typedef uint32_t cups_ptype_t; /**** Printer Type/Capability Bits ****/ |
27 | | enum /* Not a typedef'd enum so we can OR */ |
28 | | { |
29 | | CUPS_PRINTER_LOCAL = 0x0000, /* Local printer or class */ |
30 | | CUPS_PRINTER_CLASS = 0x0001, /* Printer class */ |
31 | | CUPS_PRINTER_REMOTE = 0x0002, /* Remote printer or class */ |
32 | | CUPS_PRINTER_BW = 0x0004, /* Can do B&W printing */ |
33 | | CUPS_PRINTER_COLOR = 0x0008, /* Can do color printing */ |
34 | | CUPS_PRINTER_DUPLEX = 0x0010, /* Can do duplexing */ |
35 | | CUPS_PRINTER_STAPLE = 0x0020, /* Can staple output */ |
36 | | CUPS_PRINTER_COPIES = 0x0040, /* Can do copies */ |
37 | | CUPS_PRINTER_COLLATE = 0x0080, /* Can collage copies */ |
38 | | CUPS_PRINTER_PUNCH = 0x0100, /* Can punch output */ |
39 | | CUPS_PRINTER_COVER = 0x0200, /* Can cover output */ |
40 | | CUPS_PRINTER_BIND = 0x0400, /* Can bind output */ |
41 | | CUPS_PRINTER_SORT = 0x0800, /* Can sort output */ |
42 | | CUPS_PRINTER_SMALL = 0x1000, /* Can do Letter/Legal/A4 */ |
43 | | CUPS_PRINTER_MEDIUM = 0x2000, /* Can do Tabloid/B/C/A3/A2 */ |
44 | | CUPS_PRINTER_LARGE = 0x4000, /* Can do D/E/A1/A0 */ |
45 | | CUPS_PRINTER_VARIABLE = 0x8000, /* Can do variable sizes */ |
46 | | CUPS_PRINTER_IMPLICIT = 0x10000, /* Implicit class */ |
47 | | CUPS_PRINTER_DEFAULT = 0x20000, /* Default printer on network */ |
48 | | CUPS_PRINTER_OPTIONS = 0xfffc /* ~(CLASS | REMOTE | IMPLICIT) */ |
49 | | }; |
50 | | /* End insert from cups/cups.h */ |
51 | | |
52 | | typedef enum _cups_state { |
53 | | CUPS_IDLE = 3, |
54 | | CUPS_PROCESSING, |
55 | | CUPS_STOPPED |
56 | | } cups_state_t; |
57 | | |
58 | | static const value_string cups_state_values[] = { |
59 | | { CUPS_IDLE, "idle" }, |
60 | | { CUPS_PROCESSING, "processing" }, |
61 | | { CUPS_STOPPED, "stopped" }, |
62 | | { 0, NULL } |
63 | | }; |
64 | | |
65 | | static const true_false_string tfs_implicit_explicit = { "Implicit class", "Explicit class" }; |
66 | | static const true_false_string tfs_printer_class = { "Printer class", "Single printer" }; |
67 | | |
68 | | static int proto_cups; |
69 | | static int hf_cups_ptype; |
70 | | static int hf_cups_ptype_default; |
71 | | static int hf_cups_ptype_implicit; |
72 | | static int hf_cups_ptype_variable; |
73 | | static int hf_cups_ptype_large; |
74 | | static int hf_cups_ptype_medium; |
75 | | static int hf_cups_ptype_small; |
76 | | static int hf_cups_ptype_sort; |
77 | | static int hf_cups_ptype_bind; |
78 | | static int hf_cups_ptype_cover; |
79 | | static int hf_cups_ptype_punch; |
80 | | static int hf_cups_ptype_collate; |
81 | | static int hf_cups_ptype_copies; |
82 | | static int hf_cups_ptype_staple; |
83 | | static int hf_cups_ptype_duplex; |
84 | | static int hf_cups_ptype_color; |
85 | | static int hf_cups_ptype_bw; |
86 | | static int hf_cups_ptype_remote; |
87 | | static int hf_cups_ptype_class; |
88 | | static int hf_cups_state; |
89 | | static int hf_cups_uri; |
90 | | static int hf_cups_location; |
91 | | static int hf_cups_information; |
92 | | static int hf_cups_make_model; |
93 | | |
94 | | static int ett_cups; |
95 | | static int ett_cups_ptype; |
96 | | |
97 | | /* patterns used for tvb_ws_mempbrk_pattern_uint8 */ |
98 | | static ws_mempbrk_pattern pbrk_whitespace; |
99 | | |
100 | | /* This protocol is heavily related to IPP, but it is CUPS-specific |
101 | | and non-standard. */ |
102 | 15 | #define UDP_PORT_CUPS 631 |
103 | 28 | #define PROTO_TAG_CUPS "CUPS" |
104 | | |
105 | | static unsigned get_hex_uint(tvbuff_t *tvb, unsigned offset, unsigned *next_offset); |
106 | | static bool skip_space(tvbuff_t *tvb, unsigned offset, unsigned *next_offset); |
107 | | static const char* get_quoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, |
108 | | unsigned *next_offset, unsigned *len); |
109 | | static const char* get_unquoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, |
110 | | unsigned *next_offset, unsigned *len); |
111 | | |
112 | | /**********************************************************************/ |
113 | | |
114 | | static int |
115 | | dissect_cups(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
116 | 28 | { |
117 | 28 | proto_tree *cups_tree = NULL; |
118 | 28 | proto_tree *ptype_subtree = NULL; |
119 | 28 | proto_item *ti = NULL; |
120 | 28 | unsigned offset = 0; |
121 | 28 | unsigned next_offset; |
122 | 28 | unsigned len; |
123 | 28 | const char *str; |
124 | 28 | cups_ptype_t ptype; |
125 | 28 | unsigned int state; |
126 | | |
127 | 28 | col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CUPS); |
128 | 28 | col_clear(pinfo->cinfo, COL_INFO); |
129 | | |
130 | 28 | ti = proto_tree_add_item(tree, proto_cups, tvb, offset, -1, ENC_NA); |
131 | 28 | cups_tree = proto_item_add_subtree(ti, ett_cups); |
132 | | |
133 | | /* Format (1450 bytes max.): */ |
134 | | /* type state uri ["location" ["info" ["make-and-model"]]]\n */ |
135 | | |
136 | 28 | ptype = get_hex_uint(tvb, offset, &next_offset); |
137 | 28 | len = next_offset - offset; |
138 | 28 | if (len != 0) { |
139 | 2 | ti = proto_tree_add_uint(cups_tree, hf_cups_ptype, tvb, offset, len, ptype); |
140 | 2 | ptype_subtree = proto_item_add_subtree(ti, ett_cups_ptype); |
141 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_default, tvb, offset, len, ENC_BIG_ENDIAN); |
142 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_implicit, tvb, offset, len, ENC_BIG_ENDIAN); |
143 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_variable, tvb, offset, len, ENC_BIG_ENDIAN); |
144 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_large, tvb, offset, len, ENC_BIG_ENDIAN); |
145 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_medium, tvb, offset, len, ENC_BIG_ENDIAN); |
146 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_small, tvb, offset, len, ENC_BIG_ENDIAN); |
147 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_sort, tvb, offset, len, ENC_BIG_ENDIAN); |
148 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_bind, tvb, offset, len, ENC_BIG_ENDIAN); |
149 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_cover, tvb, offset, len, ENC_BIG_ENDIAN); |
150 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_punch, tvb, offset, len, ENC_BIG_ENDIAN); |
151 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_collate, tvb, offset, len, ENC_BIG_ENDIAN); |
152 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_copies, tvb, offset, len, ENC_BIG_ENDIAN); |
153 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_staple, tvb, offset, len, ENC_BIG_ENDIAN); |
154 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_duplex, tvb, offset, len, ENC_BIG_ENDIAN); |
155 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_color, tvb, offset, len, ENC_BIG_ENDIAN); |
156 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_bw, tvb, offset, len, ENC_BIG_ENDIAN); |
157 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_remote, tvb, offset, len, ENC_BIG_ENDIAN); |
158 | 2 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_class, tvb, offset, len, ENC_BIG_ENDIAN); |
159 | 2 | } |
160 | 28 | offset = next_offset; |
161 | | |
162 | 28 | if (!skip_space(tvb, offset, &next_offset)) |
163 | 2 | return offset; /* end of packet */ |
164 | 26 | offset = next_offset; |
165 | | |
166 | 26 | state = get_hex_uint(tvb, offset, &next_offset); |
167 | 26 | len = next_offset - offset; |
168 | 26 | if (len != 0) { |
169 | 1 | proto_tree_add_uint(cups_tree, hf_cups_state, tvb, offset, len, state); |
170 | 1 | } |
171 | 26 | offset = next_offset; |
172 | | |
173 | 26 | if (!skip_space(tvb, offset, &next_offset)) |
174 | 0 | return offset; /* end of packet */ |
175 | 26 | offset = next_offset; |
176 | | |
177 | 26 | str = get_unquoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
178 | 26 | if (str == NULL) |
179 | 8 | return offset; /* separator/terminator not found */ |
180 | | |
181 | 18 | proto_tree_add_string(cups_tree, hf_cups_uri, tvb, offset, len, str); |
182 | 18 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s (%s)", |
183 | 18 | str, val_to_str(pinfo->pool, state, cups_state_values, "0x%x")); |
184 | 18 | offset = next_offset; |
185 | | |
186 | 18 | if (!cups_tree) |
187 | 0 | return offset; |
188 | | |
189 | 18 | if (!skip_space(tvb, offset, &next_offset)) |
190 | 2 | return offset; /* end of packet */ |
191 | 16 | offset = next_offset; |
192 | | |
193 | 16 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
194 | 16 | if (str == NULL) |
195 | 12 | return offset; /* separator/terminator not found */ |
196 | 4 | proto_tree_add_string(cups_tree, hf_cups_location, tvb, offset+1, len, str); |
197 | 4 | offset = next_offset; |
198 | | |
199 | 4 | if (!skip_space(tvb, offset, &next_offset)) |
200 | 0 | return offset; /* end of packet */ |
201 | 4 | offset = next_offset; |
202 | | |
203 | 4 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
204 | 4 | if (str == NULL) |
205 | 1 | return offset; /* separator/terminator not found */ |
206 | 3 | proto_tree_add_string(cups_tree, hf_cups_information, tvb, offset+1, len, str); |
207 | 3 | offset = next_offset; |
208 | | |
209 | 3 | if (!skip_space(tvb, offset, &next_offset)) |
210 | 0 | return offset; /* end of packet */ |
211 | 3 | offset = next_offset; |
212 | | |
213 | 3 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
214 | 3 | if (str == NULL) |
215 | 1 | return offset; /* separator/terminator not found */ |
216 | 2 | proto_tree_add_string(cups_tree, hf_cups_make_model, tvb, offset+1, len, str); |
217 | | |
218 | 2 | return next_offset; |
219 | 3 | } |
220 | | |
221 | | static unsigned |
222 | | get_hex_uint(tvbuff_t *tvb, unsigned offset, unsigned *next_offset) |
223 | 53 | { |
224 | 53 | char c; |
225 | 53 | unsigned u = 0; |
226 | | |
227 | 53 | while (g_ascii_isxdigit(c = tvb_get_uint8(tvb, offset))) { |
228 | 9 | u = 16*u + ws_xton(c); |
229 | | |
230 | 9 | offset++; |
231 | 9 | } |
232 | | |
233 | 53 | *next_offset = offset; |
234 | | |
235 | 53 | return u; |
236 | 53 | } |
237 | | |
238 | | WS_WARN_UNUSED static bool |
239 | | skip_space(tvbuff_t *tvb, unsigned offset, unsigned *next_offset) |
240 | 72 | { |
241 | 72 | char c; |
242 | | |
243 | 100 | while ((c = tvb_get_uint8(tvb, offset)) == ' ') |
244 | 28 | offset++; |
245 | 72 | if (c == '\r' || c == '\n') |
246 | 4 | return false; /* end of packet */ |
247 | | |
248 | 68 | *next_offset = offset; |
249 | | |
250 | 68 | return true; |
251 | 72 | } |
252 | | |
253 | | WS_WARN_UNUSED static const char* |
254 | | get_quoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned *next_offset, unsigned *len) |
255 | 17 | { |
256 | 17 | char c; |
257 | 17 | const char* s = NULL; |
258 | 17 | unsigned l = 0; |
259 | 17 | unsigned o; |
260 | | |
261 | 17 | c = tvb_get_uint8(tvb, offset); |
262 | 17 | if (c == '"') { |
263 | 3 | if (tvb_find_uint8_remaining(tvb, offset+1, '"', &o)) { |
264 | 3 | offset++; |
265 | 3 | l = o - offset; |
266 | 3 | s = (char *)tvb_get_string_enc(scope, tvb, offset, l, ENC_UTF_8); |
267 | 3 | offset = o + 1; |
268 | 3 | } |
269 | 3 | } |
270 | | |
271 | 17 | *next_offset = offset; |
272 | 17 | *len = l; |
273 | | |
274 | 17 | return s; |
275 | 17 | } |
276 | | |
277 | | WS_WARN_UNUSED static const char* |
278 | | get_unquoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned *next_offset, unsigned *len) |
279 | 25 | { |
280 | 25 | const char* s = NULL; |
281 | 25 | unsigned l = 0; |
282 | 25 | unsigned o; |
283 | | |
284 | 25 | if (tvb_ws_mempbrk_uint8_remaining(tvb, offset, &pbrk_whitespace, &o, NULL)) { |
285 | 17 | l = o - offset; |
286 | 17 | s = (char*)tvb_get_string_enc(scope, tvb, offset, l, ENC_UTF_8); |
287 | 17 | offset = o; |
288 | 17 | } |
289 | | |
290 | 25 | *next_offset = offset; |
291 | 25 | *len = l; |
292 | | |
293 | 25 | return s; |
294 | 25 | } |
295 | | |
296 | | /**********************************************************************/ |
297 | | |
298 | | void |
299 | | proto_register_cups(void) |
300 | 15 | { |
301 | 15 | static hf_register_info hf[] = { |
302 | 15 | { &hf_cups_ptype, |
303 | 15 | { "Type", "cups.ptype", FT_UINT32, BASE_HEX, |
304 | 15 | NULL, 0x0, NULL, HFILL }}, |
305 | 15 | { &hf_cups_ptype_default, |
306 | 15 | { "Default printer on network", "cups.ptype.default", FT_BOOLEAN, 32, |
307 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_DEFAULT, NULL, HFILL }}, |
308 | 15 | { &hf_cups_ptype_implicit, |
309 | 15 | { "Class", "cups.ptype.implicit", FT_BOOLEAN, 32, |
310 | 15 | TFS(&tfs_implicit_explicit), CUPS_PRINTER_IMPLICIT, NULL, HFILL }}, |
311 | 15 | { &hf_cups_ptype_variable, |
312 | 15 | { "Can print variable sizes", "cups.ptype.variable", FT_BOOLEAN, 32, |
313 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_VARIABLE, NULL, HFILL }}, |
314 | 15 | { &hf_cups_ptype_large, |
315 | 15 | { "Can print up to 36x48 inches", "cups.ptype.large", FT_BOOLEAN, 32, |
316 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_LARGE, NULL, HFILL }}, |
317 | 15 | { &hf_cups_ptype_medium, |
318 | 15 | { "Can print up to 18x24 inches", "cups.ptype.medium", FT_BOOLEAN, 32, |
319 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_MEDIUM, NULL, HFILL }}, |
320 | 15 | { &hf_cups_ptype_small, |
321 | 15 | { "Can print up to 9x14 inches", "cups.ptype.small", FT_BOOLEAN, 32, |
322 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_SMALL, NULL, HFILL }}, |
323 | 15 | { &hf_cups_ptype_sort, |
324 | 15 | { "Can sort", "cups.ptype.sort", FT_BOOLEAN, 32, |
325 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_SORT, NULL, HFILL }}, |
326 | 15 | { &hf_cups_ptype_bind, |
327 | 15 | { "Can bind", "cups.ptype.bind", FT_BOOLEAN, 32, |
328 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_BIND, NULL, HFILL }}, |
329 | 15 | { &hf_cups_ptype_cover, |
330 | 15 | { "Can cover", "cups.ptype.cover", FT_BOOLEAN, 32, |
331 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_COVER, NULL, HFILL }}, |
332 | 15 | { &hf_cups_ptype_punch, |
333 | 15 | { "Can punch holes", "cups.ptype.punch", FT_BOOLEAN, 32, |
334 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_PUNCH, NULL, HFILL }}, |
335 | 15 | { &hf_cups_ptype_collate, |
336 | 15 | { "Can do fast collating", "cups.ptype.collate", FT_BOOLEAN, 32, |
337 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_COLLATE, NULL, HFILL }}, |
338 | 15 | { &hf_cups_ptype_copies, |
339 | 15 | { "Can do fast copies", "cups.ptype.copies", FT_BOOLEAN, 32, |
340 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_COPIES, NULL, HFILL }}, |
341 | 15 | { &hf_cups_ptype_staple, |
342 | 15 | { "Can staple", "cups.ptype.staple", FT_BOOLEAN, 32, |
343 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_STAPLE, NULL, HFILL }}, |
344 | 15 | { &hf_cups_ptype_duplex, |
345 | 15 | { "Can duplex", "cups.ptype.duplex", FT_BOOLEAN, 32, |
346 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_DUPLEX, NULL, HFILL }}, |
347 | 15 | { &hf_cups_ptype_color, |
348 | 15 | { "Can print color", "cups.ptype.color", FT_BOOLEAN, 32, |
349 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_COLOR, NULL, HFILL }}, |
350 | 15 | { &hf_cups_ptype_bw, |
351 | 15 | { "Can print black", "cups.ptype.bw", FT_BOOLEAN, 32, |
352 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_BW, NULL, HFILL }}, |
353 | 15 | { &hf_cups_ptype_remote, |
354 | 15 | { "Remote", "cups.ptype.remote", FT_BOOLEAN, 32, |
355 | 15 | TFS(&tfs_yes_no), CUPS_PRINTER_REMOTE, NULL, HFILL }}, |
356 | 15 | { &hf_cups_ptype_class, |
357 | 15 | { "Class", "cups.ptype.class", FT_BOOLEAN, 32, |
358 | 15 | TFS(&tfs_printer_class), CUPS_PRINTER_CLASS, NULL, HFILL }}, |
359 | 15 | { &hf_cups_state, |
360 | 15 | { "State", "cups.state", FT_UINT8, BASE_HEX, |
361 | 15 | VALS(cups_state_values), 0x0, NULL, HFILL }}, |
362 | 15 | { &hf_cups_uri, |
363 | 15 | { "URI", "cups.uri", FT_STRING, BASE_NONE, |
364 | 15 | NULL, 0x0, NULL, HFILL }}, |
365 | 15 | { &hf_cups_location, |
366 | 15 | { "Location", "cups.location", FT_STRING, BASE_NONE, |
367 | 15 | NULL, 0x0, NULL, HFILL }}, |
368 | 15 | { &hf_cups_information, |
369 | 15 | { "Information", "cups.information", FT_STRING, BASE_NONE, |
370 | 15 | NULL, 0x0, NULL, HFILL }}, |
371 | 15 | { &hf_cups_make_model, |
372 | 15 | { "Make and model", "cups.make_model", FT_STRING, BASE_NONE, |
373 | 15 | NULL, 0x0, NULL, HFILL }}, |
374 | 15 | }; |
375 | | |
376 | 15 | static int *ett[] = { |
377 | 15 | &ett_cups, |
378 | 15 | &ett_cups_ptype |
379 | 15 | }; |
380 | | |
381 | 15 | proto_cups = proto_register_protocol("Common Unix Printing System (CUPS) Browsing Protocol", "CUPS", "cups"); |
382 | 15 | cups_handle = register_dissector("cups", dissect_cups, proto_cups); |
383 | 15 | proto_register_field_array(proto_cups, hf, array_length(hf)); |
384 | 15 | proto_register_subtree_array(ett, array_length(ett)); |
385 | | |
386 | | /* compile patterns */ |
387 | 15 | ws_mempbrk_compile(&pbrk_whitespace, " \t\r\n"); |
388 | 15 | } |
389 | | |
390 | | void |
391 | | proto_reg_handoff_cups(void) |
392 | 15 | { |
393 | 15 | dissector_add_uint_with_preference("udp.port", UDP_PORT_CUPS, cups_handle); |
394 | 15 | } |
395 | | |
396 | | /* |
397 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
398 | | * |
399 | | * Local variables: |
400 | | * c-basic-offset: 4 |
401 | | * tab-width: 8 |
402 | | * indent-tabs-mode: nil |
403 | | * End: |
404 | | * |
405 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
406 | | * :indentSize=4:tabSize=8:noTabs=true: |
407 | | */ |