2012-09-10 14:19:07 +00:00
/* packet-bthid.c
* Routines for Bluetooth HID dissection
*
* Copyright 2012 , Michal Labedzki for Tieto Corporation
*
* Wireshark - Network traffic analyzer
* By Gerald Combs < gerald @ wireshark . org >
* Copyright 1998 Gerald Combs
*
2018-02-12 11:23:27 +00:00
* SPDX - License - Identifier : GPL - 2.0 - or - later
2012-09-10 14:19:07 +00:00
*/
# include "config.h"
# include <epan/packet.h>
# include <epan/prefs.h>
2013-03-18 05:51:01 +00:00
# include <epan/expert.h>
2012-09-10 14:19:07 +00:00
# include "packet-btl2cap.h"
# include "packet-btsdp.h"
static int proto_bthid = - 1 ;
static int hf_bthid_transaction_type = - 1 ;
static int hf_bthid_parameter_reserved = - 1 ;
static int hf_bthid_parameter_reserved_31 = - 1 ;
static int hf_bthid_parameter_reserved_32 = - 1 ;
static int hf_bthid_parameter_reserved_2 = - 1 ;
static int hf_bthid_parameter_result_code = - 1 ;
static int hf_bthid_parameter_control_operation = - 1 ;
static int hf_bthid_parameter_size = - 1 ;
static int hf_bthid_protocol = - 1 ;
static int hf_bthid_idle_rate = - 1 ;
static int hf_bthid_parameter_report_type = - 1 ;
static int hf_bthid_report_id = - 1 ;
static int hf_bthid_buffer_size = - 1 ;
static int hf_bthid_protocol_code = - 1 ;
static int hf_bthid_data = - 1 ;
static gint ett_bthid = - 1 ;
2013-07-09 01:18:52 +00:00
static expert_field ei_bthid_parameter_control_operation_deprecated = EI_INIT ;
static expert_field ei_bthid_transaction_type_deprecated = EI_INIT ;
2014-03-03 11:12:03 +00:00
static dissector_handle_t bthid_handle ;
2015-01-28 16:14:48 +00:00
static dissector_handle_t usb_hid_boot_keyboard_input_report_handle ;
static dissector_handle_t usb_hid_boot_keyboard_output_report_handle ;
static dissector_handle_t usb_hid_boot_mouse_input_report_handle ;
2014-03-03 11:12:03 +00:00
2013-03-18 05:51:01 +00:00
static gboolean show_deprecated = FALSE ;
2012-09-10 14:19:07 +00:00
static const value_string transaction_type_vals [ ] = {
{ 0x00 , " HANDSHAKE " } ,
{ 0x01 , " HID_CONTROL " } ,
{ 0x02 , " reserved " } ,
{ 0x03 , " reserved " } ,
{ 0x04 , " GET_REPORT " } ,
{ 0x05 , " SET_REPORT " } ,
{ 0x06 , " GET_PROTOCOL " } ,
{ 0x07 , " SET_PROTOCOL " } ,
{ 0x08 , " GET_IDLE " } ,
{ 0x09 , " SET_IDLE " } ,
{ 0x0A , " DATA " } ,
{ 0x0B , " DATC " } ,
{ 0x0C , " reserved " } ,
{ 0x0D , " reserved " } ,
{ 0x0E , " reserved " } ,
{ 0x0F , " reserved " } ,
{ 0 , NULL }
} ;
static const value_string report_type_vals [ ] = {
{ 0x00 , " Other " } ,
{ 0x01 , " Input " } ,
{ 0x02 , " Output " } ,
{ 0x03 , " Feature " } ,
{ 0 , NULL }
} ;
static const value_string result_code_vals [ ] = {
{ 0x00 , " Successful " } ,
{ 0x01 , " Not Ready " } ,
{ 0x02 , " Error, Invalid Report ID " } ,
{ 0x03 , " Error, Unsupported Request " } ,
{ 0x04 , " Error, Invalid Parameters " } ,
{ 0x0E , " Error, Unknown " } ,
{ 0x0F , " Error, Fatal " } ,
{ 0 , NULL }
} ;
static const value_string control_operation_vals [ ] = {
{ 0x00 , " NOP " } ,
{ 0x01 , " Hard Reset " } ,
{ 0x02 , " Soft Reset " } ,
{ 0x03 , " Suspend " } ,
{ 0x04 , " Exit Suspend " } ,
{ 0x05 , " Virtual Cable Unplug " } ,
{ 0 , NULL }
} ;
static const value_string size_vals [ ] = {
{ 0x00 , " Buffer equal to report size " } ,
{ 0x01 , " BufferSize field follows the Report ID " } ,
{ 0 , NULL }
} ;
static const value_string protocol_vals [ ] = {
{ 0x00 , " Report " } ,
{ 0x01 , " Boot " } ,
{ 0 , NULL }
} ;
static const value_string protocol_code_vals [ ] = {
{ 0x00 , " None " } ,
{ 0x01 , " Keyboard " } ,
{ 0x02 , " Mouse " } ,
{ 0 , NULL }
} ;
2013-03-19 13:51:52 +00:00
void proto_register_bthid ( void ) ;
void proto_reg_handoff_bthid ( void ) ;
2012-09-10 14:19:07 +00:00
2013-03-18 05:51:01 +00:00
static gint
dissect_hid_data ( tvbuff_t * tvb , packet_info * pinfo , proto_tree * tree ,
gint offset , guint report_type )
2012-09-10 14:19:07 +00:00
{
unsigned int protocol_code ;
proto_tree_add_item ( tree , hf_bthid_protocol_code , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
protocol_code = tvb_get_guint8 ( tvb , offset ) ;
2013-02-21 21:04:59 +00:00
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - %s " , val_to_str_const ( protocol_code , protocol_code_vals , " unknown type " ) ) ;
2012-09-10 14:19:07 +00:00
offset + = 1 ;
switch ( protocol_code ) {
case 0x01 : /* Keyboard */
if ( report_type = = 0x02 ) { /* Output - LEDs */
2015-01-28 16:14:48 +00:00
offset + = call_dissector_with_data ( usb_hid_boot_keyboard_output_report_handle , tvb_new_subset_remaining ( tvb , offset ) , pinfo , tree , NULL ) ;
2012-09-10 14:19:07 +00:00
break ;
} else if ( report_type ! = 0x01 ) { /* is not Input (Keys) */
break ;
}
2015-01-28 16:14:48 +00:00
offset + = call_dissector_with_data ( usb_hid_boot_keyboard_input_report_handle , tvb_new_subset_remaining ( tvb , offset ) , pinfo , tree , NULL ) ;
2012-09-10 14:19:07 +00:00
break ;
case 0x02 : /* Mouse */
2015-01-28 16:14:48 +00:00
offset + = call_dissector_with_data ( usb_hid_boot_mouse_input_report_handle , tvb_new_subset_remaining ( tvb , offset ) , pinfo , tree , NULL ) ;
2012-09-10 14:19:07 +00:00
break ;
}
return offset ;
}
2013-07-03 16:43:21 +00:00
static int
dissect_bthid ( tvbuff_t * tvb , packet_info * pinfo , proto_tree * tree , void * data _U_ )
2012-09-10 14:19:07 +00:00
{
proto_item * ti ;
proto_tree * bthid_tree ;
2013-03-18 05:51:01 +00:00
gint offset = 0 ;
guint transaction_type ;
guint parameter ;
guint protocol ;
guint idle_rate ;
guint8 control_operation ;
proto_item * pitem ;
2012-09-10 14:19:07 +00:00
2013-11-02 19:20:04 +00:00
ti = proto_tree_add_item ( tree , proto_bthid , tvb , offset , - 1 , ENC_NA ) ;
bthid_tree = proto_item_add_subtree ( ti , ett_bthid ) ;
2012-09-10 14:19:07 +00:00
col_set_str ( pinfo - > cinfo , COL_PROTOCOL , " HID " ) ;
col_clear ( pinfo - > cinfo , COL_INFO ) ;
switch ( pinfo - > p2p_dir ) {
2013-03-30 21:47:27 +00:00
case P2P_DIR_SENT :
2013-10-29 14:09:20 +00:00
col_set_str ( pinfo - > cinfo , COL_INFO , " Sent " ) ;
2013-03-30 21:47:27 +00:00
break ;
case P2P_DIR_RECV :
2013-10-29 14:09:20 +00:00
col_set_str ( pinfo - > cinfo , COL_INFO , " Rcvd " ) ;
2013-03-30 21:47:27 +00:00
break ;
default :
2015-01-18 09:44:04 +00:00
col_set_str ( pinfo - > cinfo , COL_INFO , " UnknownDirection " ) ;
2013-03-30 21:47:27 +00:00
break ;
2012-09-10 14:19:07 +00:00
}
2013-03-18 05:51:01 +00:00
pitem = proto_tree_add_item ( bthid_tree , hf_bthid_transaction_type , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
2012-09-10 14:19:07 +00:00
transaction_type = tvb_get_guint8 ( tvb , offset ) ;
parameter = transaction_type & 0x0F ;
transaction_type = transaction_type > > 4 ;
2013-10-29 14:09:20 +00:00
col_append_str ( pinfo - > cinfo , COL_INFO , val_to_str_const ( transaction_type , transaction_type_vals , " Unknown TransactionType " ) ) ;
2012-09-10 14:19:07 +00:00
switch ( transaction_type ) {
case 0x00 : /* HANDSHAKE */
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_result_code , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
2013-02-21 21:04:59 +00:00
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Result Code: %s " , val_to_str_const ( parameter , result_code_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
break ;
case 0x01 : /* HID_CONTROL */
2013-03-18 05:51:01 +00:00
pitem = proto_tree_add_item ( bthid_tree , hf_bthid_parameter_control_operation , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
control_operation = tvb_get_guint8 ( tvb , offset ) ;
2013-02-21 21:04:59 +00:00
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Control Operation: %s " , val_to_str_const ( parameter , control_operation_vals , " reserved " ) ) ;
2013-03-18 05:51:01 +00:00
if ( control_operation < 3 & & show_deprecated )
2013-07-09 01:18:52 +00:00
expert_add_info ( pinfo , pitem , & ei_bthid_parameter_control_operation_deprecated ) ;
2012-09-10 14:19:07 +00:00
offset + = 1 ;
break ;
case 0x04 : /* GET_REPORT */
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_size , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved_2 , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_report_type , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Size: %s, Report Type: %s " ,
2013-02-21 21:04:59 +00:00
val_to_str_const ( parameter > > 3 , size_vals , " reserved " ) ,
val_to_str_const ( parameter & 0x03 , report_type_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
2013-03-18 05:51:01 +00:00
/* XXX: This is workaround, this should come from SDP:
" This field is required in Report Protocol Mode when any Report ID
Global Items are declared in the report descriptor , and in
Boot Protocol Mode . Otherwise the field does not exist . "
*/
2015-06-22 21:40:23 +00:00
if ( ( ( parameter > > 3 ) & & tvb_reported_length_remaining ( tvb , offset ) > = 3 ) | |
( ! ( parameter > > 3 ) & & tvb_reported_length_remaining ( tvb , offset ) > = 1 ) ) {
2012-09-10 14:19:07 +00:00
proto_tree_add_item ( bthid_tree , hf_bthid_report_id , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
}
2013-03-18 05:51:01 +00:00
if ( parameter > > 3 ) {
proto_tree_add_item ( bthid_tree , hf_bthid_buffer_size , tvb , offset , 2 , ENC_LITTLE_ENDIAN ) ;
2012-09-10 14:19:07 +00:00
offset + = 2 ;
}
break ;
case 0x05 : /* SET_REPORT */
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved_32 , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_report_type , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Report Type: %s " ,
2013-02-21 21:04:59 +00:00
val_to_str_const ( parameter & 0x03 , report_type_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
/* playload */
2013-03-18 05:51:01 +00:00
proto_tree_add_item ( bthid_tree , hf_bthid_data , tvb , offset , - 1 , ENC_NA ) ;
2015-06-22 21:40:23 +00:00
offset + = tvb_captured_length_remaining ( tvb , offset ) ;
2012-09-10 14:19:07 +00:00
break ;
case 0x06 : /* GET_PROTOCOL */
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
proto_tree_add_item ( bthid_tree , hf_bthid_protocol , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
protocol = tvb_get_guint8 ( tvb , offset ) & 0x01 ;
offset + = 1 ;
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Protocol: %s " ,
2013-02-21 21:04:59 +00:00
val_to_str_const ( protocol , protocol_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
break ;
case 0x07 : /* SET_PROTOCOL */
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved_31 , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
proto_tree_add_item ( bthid_tree , hf_bthid_protocol , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Protocol: %s " ,
2013-02-21 21:04:59 +00:00
val_to_str_const ( parameter & 0x01 , protocol_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
break ;
case 0x08 : /* GET_IDLE */
case 0x09 : /* SET_IDLE */
2013-03-18 05:51:01 +00:00
if ( show_deprecated )
2013-07-09 01:18:52 +00:00
expert_add_info ( pinfo , pitem , & ei_bthid_transaction_type_deprecated ) ;
2013-03-18 05:51:01 +00:00
2012-09-10 14:19:07 +00:00
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
pitem = proto_tree_add_item ( bthid_tree , hf_bthid_idle_rate , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
idle_rate = tvb_get_guint8 ( tvb , offset ) ;
proto_item_append_text ( pitem , " (%u.%03u ms) " , idle_rate * 4 / 1000 , idle_rate * 4 % 1000 ) ;
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - Idle Rate: %u.%03u ms " , idle_rate * 4 / 1000 , idle_rate * 4 % 1000 ) ;
offset + = 1 ;
break ;
case 0x0B : /* DATC */
2013-03-18 05:51:01 +00:00
if ( show_deprecated )
2013-07-09 01:18:52 +00:00
expert_add_info ( pinfo , pitem , & ei_bthid_transaction_type_deprecated ) ;
2017-03-05 15:20:15 +00:00
/* FALL THROUGH */
2013-03-18 05:51:01 +00:00
case 0x0A : /* DATA */
2012-09-10 14:19:07 +00:00
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_reserved_32 , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
proto_tree_add_item ( bthid_tree , hf_bthid_parameter_report_type , tvb , offset , 1 , ENC_BIG_ENDIAN ) ;
offset + = 1 ;
2013-02-21 21:04:59 +00:00
col_append_fstr ( pinfo - > cinfo , COL_INFO , " - %s " , val_to_str_const ( parameter , report_type_vals , " reserved " ) ) ;
2012-09-10 14:19:07 +00:00
/* playload */
offset = dissect_hid_data ( tvb , pinfo , bthid_tree , offset , parameter & 0x03 ) ;
break ;
}
2013-07-03 16:43:21 +00:00
return offset ;
2012-09-10 14:19:07 +00:00
}
void
proto_register_bthid ( void )
{
module_t * module ;
2013-07-09 01:18:52 +00:00
expert_module_t * expert_bthid ;
2012-09-10 14:19:07 +00:00
static hf_register_info hf [ ] = {
{ & hf_bthid_transaction_type ,
{ " Transaction Type " , " bthid.transaction_type " ,
FT_UINT8 , BASE_HEX , VALS ( transaction_type_vals ) , 0xF0 ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_reserved ,
{ " Parameter reserved " , " bthid.parameter.reserved " ,
FT_UINT8 , BASE_HEX , NULL , 0x0F ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_reserved_32 ,
{ " Parameter reserved " , " bthid.parameter.reserved_32 " ,
FT_UINT8 , BASE_HEX , NULL , 0x0C ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_reserved_31 ,
{ " Parameter reserved " , " bthid.parameter.reserved_31 " ,
FT_UINT8 , BASE_HEX , NULL , 0x0E ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_reserved_2 ,
{ " Parameter reserved " , " bthid.parameter.reserved_2 " ,
FT_UINT8 , BASE_HEX , NULL , 0x04 ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_report_type ,
{ " Report Type " , " bthid.parameter.report_type " ,
FT_UINT8 , BASE_HEX , VALS ( report_type_vals ) , 0x03 ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_size ,
{ " Size " , " bthid.parameter.size " ,
FT_UINT8 , BASE_HEX , VALS ( size_vals ) , 0x08 ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_result_code ,
{ " Result Code " , " bthid.result_code " ,
FT_UINT8 , BASE_HEX , VALS ( result_code_vals ) , 0x0F ,
NULL , HFILL }
} ,
{ & hf_bthid_parameter_control_operation ,
{ " Control Operation " , " bthid.control_operation " ,
FT_UINT8 , BASE_HEX , VALS ( control_operation_vals ) , 0x0F ,
NULL , HFILL }
} ,
{ & hf_bthid_protocol ,
{ " Protocol " , " bthid.protocol " ,
FT_UINT8 , BASE_HEX , VALS ( protocol_vals ) , 0x01 ,
NULL , HFILL }
} ,
{ & hf_bthid_idle_rate ,
{ " Idle Rate " , " bthid.idle_rate " ,
FT_UINT8 , BASE_DEC , NULL , 0x00 ,
NULL , HFILL }
} ,
{ & hf_bthid_report_id ,
{ " Report Id " , " bthid.report_id " ,
2013-03-18 05:51:01 +00:00
FT_UINT8 , BASE_HEX , VALS ( protocol_code_vals ) , 0x00 ,
2012-09-10 14:19:07 +00:00
NULL , HFILL }
} ,
{ & hf_bthid_buffer_size ,
{ " Buffer Size " , " bthid.buffer_size " ,
FT_UINT16 , BASE_HEX , NULL , 0x00 ,
NULL , HFILL }
} ,
{ & hf_bthid_protocol_code ,
{ " Protocol Code " , " bthid.data.protocol_code " ,
FT_UINT8 , BASE_HEX , VALS ( protocol_code_vals ) , 0x00 ,
NULL , HFILL }
} ,
{ & hf_bthid_data ,
{ " Data " , " bthid.data " ,
FT_NONE , BASE_NONE , NULL , 0x00 ,
NULL , HFILL }
} ,
} ;
static gint * ett [ ] = {
& ett_bthid
} ;
2013-07-09 01:18:52 +00:00
static ei_register_info ei [ ] = {
{ & ei_bthid_parameter_control_operation_deprecated , { " bthid.control_operation.deprecated " , PI_PROTOCOL , PI_WARN , " This value of Control Operation is deprecated by HID 1.1 " , EXPFILL } } ,
{ & ei_bthid_transaction_type_deprecated , { " bthid.transaction_type.deprecated " , PI_PROTOCOL , PI_WARN , " This Transaction Type is deprecated by HID 1.1 " , EXPFILL } } ,
} ;
2013-03-18 06:23:22 +00:00
proto_bthid = proto_register_protocol ( " Bluetooth HID Profile " , " BT HID " , " bthid " ) ;
2015-12-09 04:04:01 +00:00
bthid_handle = register_dissector ( " bthid " , dissect_bthid , proto_bthid ) ;
2012-09-10 14:19:07 +00:00
proto_register_field_array ( proto_bthid , hf , array_length ( hf ) ) ;
proto_register_subtree_array ( ett , array_length ( ett ) ) ;
2013-07-09 01:18:52 +00:00
expert_bthid = expert_register_protocol ( proto_bthid ) ;
expert_register_field_array ( expert_bthid , ei , array_length ( ei ) ) ;
2012-09-10 14:19:07 +00:00
2018-01-05 13:00:44 +00:00
module = prefs_register_protocol_subtree ( " Bluetooth " , proto_bthid , NULL ) ;
2012-09-10 14:19:07 +00:00
prefs_register_static_text_preference ( module , " hid.version " ,
2013-03-18 05:51:01 +00:00
" Bluetooth Profile HID version: 1.1 " ,
2012-09-10 14:19:07 +00:00
" Version of profile supported by this dissector. " ) ;
2013-03-18 05:51:01 +00:00
prefs_register_bool_preference ( module , " hid.deprecated " ,
" Show what is deprecated in HID 1.1 " ,
" Show what is deprecated in HID 1.1 " , & show_deprecated ) ;
2012-09-10 14:19:07 +00:00
}
void
proto_reg_handoff_bthid ( void )
{
2016-03-16 13:02:52 +00:00
usb_hid_boot_keyboard_input_report_handle = find_dissector_add_dependency ( " usbhid.boot_report.keyboard.input " , proto_bthid ) ;
usb_hid_boot_keyboard_output_report_handle = find_dissector_add_dependency ( " usbhid.boot_report.keyboard.output " , proto_bthid ) ;
usb_hid_boot_mouse_input_report_handle = find_dissector_add_dependency ( " usbhid.boot_report.mouse.input " , proto_bthid ) ;
2015-01-28 16:14:48 +00:00
2015-07-13 17:01:48 +00:00
dissector_add_string ( " bluetooth.uuid " , " 11 " , bthid_handle ) ;
dissector_add_string ( " bluetooth.uuid " , " 1124 " , bthid_handle ) ;
2012-09-10 14:19:07 +00:00
dissector_add_uint ( " btl2cap.psm " , BTL2CAP_PSM_HID_CTRL , bthid_handle ) ;
dissector_add_uint ( " btl2cap.psm " , BTL2CAP_PSM_HID_INTR , bthid_handle ) ;
2014-06-20 16:43:28 +00:00
dissector_add_for_decode_as ( " btl2cap.cid " , bthid_handle ) ;
2012-09-10 14:19:07 +00:00
}
2012-10-09 10:36:04 +00:00
/*
* Editor modelines - http : //www.wireshark.org/tools/modelines.html
*
* Local variables :
* c - basic - offset : 4
* tab - width : 8
* indent - tabs - mode : nil
* End :
*
* vi : set shiftwidth = 4 tabstop = 8 expandtab :
* : indentSize = 4 : tabSize = 8 : noTabs = true :
*/