Files
StreamLens/analyzer/protocols/decoders/fibre_channel.py
2025-07-28 08:14:15 -04:00

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})")