167 lines
6.0 KiB
Python
167 lines
6.0 KiB
Python
"""
|
|
Fibre Channel Data decoder for Chapter 10 data types
|
|
Supports Fibre Channel Format 0 (0x79)
|
|
"""
|
|
|
|
import struct
|
|
from typing import Dict, Any, Optional
|
|
from .base import DataTypeDecoder, DecodedPayload
|
|
|
|
|
|
class FibreChannelDecoder(DataTypeDecoder):
|
|
"""Decoder for Fibre Channel Data type (0x79)"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.data_type_base = 0x79
|
|
self.data_type_name = "Fibre Channel Data"
|
|
self.supported_formats = [0x79]
|
|
|
|
def can_decode(self, data_type: int) -> bool:
|
|
return data_type == 0x79
|
|
|
|
def get_data_type_name(self, data_type: int) -> str:
|
|
return "Fibre Channel Data Format 0"
|
|
|
|
def decode(self, payload: bytes, ch10_header: Dict[str, Any]) -> Optional[DecodedPayload]:
|
|
"""Decode Fibre Channel payload"""
|
|
decoded_data = {}
|
|
errors = []
|
|
|
|
# Parse IPH
|
|
iph = self._parse_intra_packet_header(payload)
|
|
if iph:
|
|
decoded_data.update(iph)
|
|
data_start = iph['data_start']
|
|
else:
|
|
data_start = 0
|
|
errors.append("Failed to parse intra-packet header")
|
|
|
|
# Parse FC frame header
|
|
if data_start + 24 <= len(payload):
|
|
# FC frame header (simplified)
|
|
fc_header = self._safe_unpack('<IIIIII', payload, data_start)
|
|
if fc_header:
|
|
decoded_data.update({
|
|
'fc_timestamp': fc_header[0],
|
|
'fc_frame_length': fc_header[1],
|
|
'fc_r_ctl': (fc_header[2] >> 24) & 0xFF,
|
|
'fc_d_id': fc_header[2] & 0xFFFFFF,
|
|
'fc_cs_ctl': (fc_header[3] >> 24) & 0xFF,
|
|
'fc_s_id': fc_header[3] & 0xFFFFFF,
|
|
'fc_type': (fc_header[4] >> 24) & 0xFF,
|
|
'fc_f_ctl': fc_header[4] & 0xFFFFFF,
|
|
'fc_seq_id': (fc_header[5] >> 24) & 0xFF,
|
|
'fc_df_ctl': (fc_header[5] >> 16) & 0xFF,
|
|
'fc_seq_cnt': fc_header[5] & 0xFFFF
|
|
})
|
|
|
|
# Decode R_CTL field
|
|
r_ctl = decoded_data['fc_r_ctl']
|
|
decoded_data['fc_r_ctl_description'] = self._decode_r_ctl(r_ctl)
|
|
|
|
# Decode Type field
|
|
fc_type = decoded_data['fc_type']
|
|
decoded_data['fc_type_description'] = self._decode_fc_type(fc_type)
|
|
|
|
# Extract payload
|
|
fc_payload_start = data_start + 24
|
|
frame_length = decoded_data['fc_frame_length']
|
|
if fc_payload_start + frame_length <= len(payload):
|
|
fc_payload = payload[fc_payload_start:fc_payload_start + frame_length]
|
|
decoded_data['fc_payload_length'] = len(fc_payload)
|
|
decoded_data['fc_payload_preview'] = fc_payload[:64].hex()
|
|
|
|
# Analyze payload based on type
|
|
if fc_type == 0x08: # SCSI FCP
|
|
scsi_data = self._parse_scsi_fcp(fc_payload)
|
|
decoded_data.update(scsi_data)
|
|
else:
|
|
errors.append("FC payload extends beyond packet")
|
|
else:
|
|
errors.append("Failed to parse FC header")
|
|
|
|
return DecodedPayload(
|
|
data_type=0x79,
|
|
data_type_name="Fibre Channel Data Format 0",
|
|
format_version=0,
|
|
decoded_data=decoded_data,
|
|
raw_payload=payload,
|
|
errors=errors,
|
|
metadata={'decoder': 'FibreChannelDecoder'}
|
|
)
|
|
|
|
def _decode_r_ctl(self, r_ctl: int) -> str:
|
|
"""Decode R_CTL field"""
|
|
r_ctl_types = {
|
|
0x00: "Device Data",
|
|
0x01: "Extended Link Data",
|
|
0x02: "FC-4 Link Data",
|
|
0x03: "Video Data",
|
|
0x20: "Basic Link Data",
|
|
0x21: "ACK_1",
|
|
0x22: "ACK_0",
|
|
0x23: "P_RJT",
|
|
0x24: "F_RJT",
|
|
0x25: "P_BSY",
|
|
0x26: "F_BSY"
|
|
}
|
|
return r_ctl_types.get(r_ctl, f"Unknown (0x{r_ctl:02x})")
|
|
|
|
def _decode_fc_type(self, fc_type: int) -> str:
|
|
"""Decode FC Type field"""
|
|
fc_types = {
|
|
0x00: "Basic Link Service",
|
|
0x01: "Extended Link Service",
|
|
0x04: "IP over FC",
|
|
0x05: "ATM over FC",
|
|
0x08: "SCSI FCP",
|
|
0x09: "SCSI GPP",
|
|
0x0A: "IPI-3 Master",
|
|
0x0B: "IPI-3 Slave",
|
|
0x0C: "IPI-3 Peer"
|
|
}
|
|
return fc_types.get(fc_type, f"Unknown (0x{fc_type:02x})")
|
|
|
|
def _parse_scsi_fcp(self, payload: bytes) -> Dict[str, Any]:
|
|
"""Parse SCSI FCP payload"""
|
|
scsi_data = {}
|
|
|
|
if len(payload) >= 32:
|
|
# FCP_CMND structure
|
|
lun = payload[0:8]
|
|
task_codes = payload[8]
|
|
task_mgmt = payload[9]
|
|
add_cdb_len = payload[10]
|
|
rddata = bool(payload[11] & 0x02)
|
|
wrdata = bool(payload[11] & 0x01)
|
|
|
|
scsi_data.update({
|
|
'scsi_lun': lun.hex(),
|
|
'scsi_task_codes': task_codes,
|
|
'scsi_task_mgmt': task_mgmt,
|
|
'scsi_rddata': rddata,
|
|
'scsi_wrdata': wrdata
|
|
})
|
|
|
|
# CDB starts at offset 12
|
|
if len(payload) >= 16:
|
|
cdb = payload[12:16]
|
|
scsi_data['scsi_cdb'] = cdb.hex()
|
|
if cdb[0] in [0x12, 0x00, 0x28, 0x2A]: # Common SCSI commands
|
|
scsi_data['scsi_command'] = self._decode_scsi_command(cdb[0])
|
|
|
|
return scsi_data
|
|
|
|
def _decode_scsi_command(self, opcode: int) -> str:
|
|
"""Decode SCSI command opcode"""
|
|
commands = {
|
|
0x00: "TEST UNIT READY",
|
|
0x12: "INQUIRY",
|
|
0x28: "READ(10)",
|
|
0x2A: "WRITE(10)",
|
|
0x35: "SYNCHRONIZE CACHE",
|
|
0x3C: "READ BUFFER"
|
|
}
|
|
return commands.get(opcode, f"Unknown (0x{opcode:02x})")
|