///|
pub(all) suberror DcpParseError {
PacketTooShort(Int)
UnexpectedEtherType(Int)
TruncatedPayload(expected~ : Int, actual~ : Int)
TruncatedBlock(at~ : Int, len~ : Int)
} derive(Eq, Debug, ToJson)
///|
pub(all) struct DeviceVendorBlock {
block_info : Int
value : String
} derive(Eq)
///|
pub(all) struct ResponseNameOfStationBlock {
block_info : Int
value : String
} derive(Eq)
///|
pub(all) struct AliasNameBlock {
block_info : Int
value : String
} derive(Eq)
///|
pub(all) struct DeviceIDBlock {
block_info : Int
vendor_id : Int
device_id : Int
} derive(Eq)
///|
pub(all) struct DeviceInstanceBlock {
block_info : Int
value : Int
} derive(Eq)
///|
pub(all) struct OEMDeviceIDBlock {
block_info : Int
vendor_id : Int
device_id : Int
} derive(Eq)
///|
pub(all) struct DeviceRoleBlock {
block_info : Int
is_device : Bool
is_controller : Bool
is_multidevice : Bool
is_supervisor : Bool
} derive(Eq)
///|
pub(all) struct DeviceOptionPair {
option : Byte
suboption : Byte
} derive(Eq)
///|
pub(all) struct DeviceOptionsBlock {
block_info : Int
options : Array[DeviceOptionPair]
} derive(Eq)
///|
pub(all) struct ResponseIPParameterBlock {
block_info : Int
address : @frame.IPv4Address
mask : @frame.IPv4Address
gateway : @frame.IPv4Address
} derive(Eq)
///|
pub(all) struct DeviceInitiativeBlock {
block_info : Int
device_initiative : Int
} derive(Eq)
///|
pub(all) struct ControlResponseBlock {
failed_option : Byte
failed_suboption : Byte
block_error : Byte
} derive(Eq)
///|
pub(all) enum ResponseBlock {
NameOfStation(ResponseNameOfStationBlock)
AliasName(AliasNameBlock)
DeviceVendor(DeviceVendorBlock)
DeviceID(DeviceIDBlock)
DeviceInstance(DeviceInstanceBlock)
OEMDeviceID(OEMDeviceIDBlock)
DeviceRole(DeviceRoleBlock)
DeviceOptions(DeviceOptionsBlock)
IPParameter(ResponseIPParameterBlock)
DeviceInitiative(DeviceInitiativeBlock)
ControlResponse(ControlResponseBlock)
Unknown(option~ : Byte, suboption~ : Byte, payload~ : Bytes)
} derive(Eq)
///|
pub(all) struct DcpResponse {
frame_id : Int
service_id : Byte
service_type : Byte
xid : Int
source : @frame.MacAddress
blocks : Array[ResponseBlock]
} derive(Eq)
///|
pub(all) struct DeviceSummary {
source : @frame.MacAddress
station_name : String?
vendor_name : String?
alias_name : String?
vendor_id : Int?
device_id : Int?
device_instance : Int?
oem_vendor_id : Int?
oem_device_id : Int?
ip_address : @frame.IPv4Address?
subnet_mask : @frame.IPv4Address?
gateway : @frame.IPv4Address?
is_device : Bool
is_controller : Bool
is_multidevice : Bool
is_supervisor : Bool
options : Array[DeviceOptionPair]
device_initiative : Int?
control_response : ControlResponseBlock?
} derive(Eq)
///|
fn parse_name_of_station_block(payload : Bytes) -> ResponseNameOfStationBlock {
ResponseNameOfStationBlock::{
block_info: read_u16_be(payload, 0),
value: @ascii.decode_lossy(payload.view(start=2)),
}
}
///|
fn parse_device_vendor_block(payload : Bytes) -> DeviceVendorBlock {
DeviceVendorBlock::{
block_info: read_u16_be(payload, 0),
value: @ascii.decode_lossy(payload.view(start=2)),
}
}
///|
fn parse_alias_name_block(payload : Bytes) -> AliasNameBlock {
AliasNameBlock::{
block_info: read_u16_be(payload, 0),
value: @ascii.decode_lossy(payload.view(start=2)),
}
}
///|
fn parse_device_id_block(payload : Bytes) -> DeviceIDBlock {
DeviceIDBlock::{
block_info: read_u16_be(payload, 0),
vendor_id: read_u16_be(payload, 2),
device_id: read_u16_be(payload, 4),
}
}
///|
fn parse_device_instance_block(payload : Bytes) -> DeviceInstanceBlock {
DeviceInstanceBlock::{
block_info: read_u16_be(payload, 0),
value: read_u16_be(payload, 2),
}
}
///|
fn parse_oem_device_id_block(payload : Bytes) -> OEMDeviceIDBlock {
OEMDeviceIDBlock::{
block_info: read_u16_be(payload, 0),
vendor_id: read_u16_be(payload, 2),
device_id: read_u16_be(payload, 4),
}
}
///|
fn parse_device_role_block(payload : Bytes) -> DeviceRoleBlock {
let flags = payload[2]
DeviceRoleBlock::{
block_info: read_u16_be(payload, 0),
is_device: flags.land(1) != 0,
is_controller: flags.land(2) != 0,
is_multidevice: flags.land(4) != 0,
is_supervisor: flags.land(8) != 0,
}
}
///|
fn parse_device_options_block(payload : Bytes) -> DeviceOptionsBlock {
let options : Array[DeviceOptionPair] = []
for index = 2; index < payload.length(); {
options.push(DeviceOptionPair::{
option: payload[index],
suboption: payload[index + 1],
})
continue index + 2
}
DeviceOptionsBlock::{ block_info: read_u16_be(payload, 0), options }
}
///|
fn parse_ip_parameter_block(payload : Bytes) -> ResponseIPParameterBlock raise {
ResponseIPParameterBlock::{
block_info: read_u16_be(payload, 0),
address: @frame.IPv4Address::new(bytes_slice(payload, 2, 4)),
mask: @frame.IPv4Address::new(bytes_slice(payload, 6, 4)),
gateway: @frame.IPv4Address::new(bytes_slice(payload, 10, 4)),
}
}
///|
fn parse_device_initiative_block(payload : Bytes) -> DeviceInitiativeBlock {
DeviceInitiativeBlock::{
block_info: read_u16_be(payload, 0),
device_initiative: read_u16_be(payload, 2),
}
}
///|
fn parse_control_response_block(payload : Bytes) -> ControlResponseBlock {
ControlResponseBlock::{
failed_option: payload[0],
failed_suboption: payload[1],
block_error: payload[2],
}
}
///|
fn parse_response_block(
option : Byte,
suboption : Byte,
payload : Bytes,
) -> ResponseBlock raise {
if option == dcp_option_device && suboption == 1 {
DeviceVendor(parse_device_vendor_block(payload))
} else if option == dcp_option_device &&
suboption == dcp_suboption_name_of_station {
NameOfStation(parse_name_of_station_block(payload))
} else if option == dcp_option_device &&
suboption == dcp_suboption_device_alias {
AliasName(parse_alias_name_block(payload))
} else if option == dcp_option_device && suboption == 3 {
DeviceID(parse_device_id_block(payload))
} else if option == dcp_option_device &&
suboption == dcp_suboption_device_instance {
DeviceInstance(parse_device_instance_block(payload))
} else if option == dcp_option_device &&
suboption == dcp_suboption_device_oem_id {
OEMDeviceID(parse_oem_device_id_block(payload))
} else if option == dcp_option_device && suboption == 4 {
DeviceRole(parse_device_role_block(payload))
} else if option == dcp_option_device && suboption == 5 {
DeviceOptions(parse_device_options_block(payload))
} else if option == dcp_option_ip && suboption == dcp_suboption_ip_parameter {
IPParameter(parse_ip_parameter_block(payload))
} else if option == dcp_option_initiative &&
suboption == dcp_suboption_device_initiative {
DeviceInitiative(parse_device_initiative_block(payload))
} else if option == dcp_option_control &&
suboption == dcp_suboption_control_response {
ControlResponse(parse_control_response_block(payload))
} else {
Unknown(option~, suboption~, payload~)
}
}
///|
pub fn parse_response(packet : Bytes) -> DcpResponse raise {
guard packet.length() >= 26 else {
raise DcpParseError::PacketTooShort(packet.length())
}
let ether_type = read_u16_be(packet, 12)
guard ether_type == ether_type_profinet else {
raise DcpParseError::UnexpectedEtherType(ether_type)
}
let payload_len = read_u16_be(packet, 24)
let actual_payload_len = packet.length() - 26
guard actual_payload_len >= payload_len else {
raise DcpParseError::TruncatedPayload(
expected=payload_len,
actual=actual_payload_len,
)
}
let blocks : Array[ResponseBlock] = []
let payload_end = 26 + payload_len
for cursor = 26; cursor < payload_end; {
guard cursor + 4 <= payload_end else {
raise DcpParseError::TruncatedBlock(at=cursor, len=payload_end - cursor)
}
let option = packet[cursor]
let suboption = packet[cursor + 1]
let block_len = read_u16_be(packet, cursor + 2)
let padding = if block_len % 2 == 1 { 1 } else { 0 }
let padded_len = block_len + 4 + padding
guard cursor + padded_len <= payload_end else {
raise DcpParseError::TruncatedBlock(at=cursor, len=block_len)
}
let payload = bytes_slice(packet, cursor + 4, block_len)
blocks.push(parse_response_block(option, suboption, payload))
continue cursor + padded_len
}
DcpResponse::{
frame_id: read_u16_be(packet, 14),
service_id: packet[16],
service_type: packet[17],
xid: read_u32_be(packet, 18),
source: @frame.MacAddress::new(bytes_slice(packet, 6, 6)),
blocks,
}
}
///|
pub fn response_service_type_label(service_type : Byte) -> String {
match service_type.to_int() {
0 => "request"
1 => "success"
other => "response-" + other.to_string()
}
}
///|
pub fn response_block_info_label(block_info : Int) -> String {
if block_info == 0 {
"ok"
} else {
"status-" + block_info.to_string()
}
}
///|
pub fn control_response_error_label(block_error : Byte) -> String {
match block_error.to_int() {
0 => "no_error"
1 => "option_not_supported"
2 => "suboption_or_block_qualifier_not_supported"
3 => "suboption_not_set"
4 => "resource_error"
5 => "set_not_possible_local"
6 => "set_not_possible_in_operation"
other => "unknown_error_" + other.to_string()
}
}
///|
pub fn response_block_name(block : ResponseBlock) -> String {
match block {
NameOfStation(_) => "name_of_station"
AliasName(_) => "alias_name"
DeviceVendor(_) => "device_vendor"
DeviceID(_) => "device_id"
DeviceInstance(_) => "device_instance"
OEMDeviceID(_) => "oem_device_id"
DeviceRole(_) => "device_role"
DeviceOptions(_) => "device_options"
IPParameter(_) => "ip_parameter"
DeviceInitiative(_) => "device_initiative"
ControlResponse(_) => "control_response"
Unknown(option~, suboption~, ..) =>
"unknown(" +
option.to_int().to_string() +
":" +
suboption.to_int().to_string() +
")"
}
}
///|
pub fn response_block_info(block : ResponseBlock) -> Int {
match block {
NameOfStation(data) => data.block_info
AliasName(data) => data.block_info
DeviceVendor(data) => data.block_info
DeviceID(data) => data.block_info
DeviceInstance(data) => data.block_info
OEMDeviceID(data) => data.block_info
DeviceRole(data) => data.block_info
DeviceOptions(data) => data.block_info
IPParameter(data) => data.block_info
DeviceInitiative(data) => data.block_info
ControlResponse(_) => 0
Unknown(payload~, ..) =>
if payload.length() >= 2 {
read_u16_be(payload, 0)
} else {
0
}
}
}
///|
pub fn summarize_response(response : DcpResponse) -> DeviceSummary {
let mut station_name : String? = None
let mut vendor_name : String? = None
let mut alias_name : String? = None
let mut vendor_id : Int? = None
let mut device_id : Int? = None
let mut device_instance : Int? = None
let mut oem_vendor_id : Int? = None
let mut oem_device_id : Int? = None
let mut ip_address : @frame.IPv4Address? = None
let mut subnet_mask : @frame.IPv4Address? = None
let mut gateway : @frame.IPv4Address? = None
let mut is_device = false
let mut is_controller = false
let mut is_multidevice = false
let mut is_supervisor = false
let options : Array[DeviceOptionPair] = []
let mut device_initiative : Int? = None
let mut control_response : ControlResponseBlock? = None
for block in response.blocks {
match block {
NameOfStation(data) => station_name = Some(data.value)
AliasName(data) => alias_name = Some(data.value)
DeviceVendor(data) => vendor_name = Some(data.value)
DeviceID(data) => {
vendor_id = Some(data.vendor_id)
device_id = Some(data.device_id)
}
DeviceInstance(data) => device_instance = Some(data.value)
OEMDeviceID(data) => {
oem_vendor_id = Some(data.vendor_id)
oem_device_id = Some(data.device_id)
}
DeviceRole(data) => {
is_device = data.is_device
is_controller = data.is_controller
is_multidevice = data.is_multidevice
is_supervisor = data.is_supervisor
}
DeviceOptions(data) =>
for pair in data.options {
options.push(pair)
}
IPParameter(data) => {
ip_address = Some(data.address)
subnet_mask = Some(data.mask)
gateway = Some(data.gateway)
}
DeviceInitiative(data) => device_initiative = Some(data.device_initiative)
ControlResponse(data) => control_response = Some(data)
_ => ()
}
}
DeviceSummary::{
source: response.source,
station_name,
vendor_name,
alias_name,
vendor_id,
device_id,
device_instance,
oem_vendor_id,
oem_device_id,
ip_address,
subnet_mask,
gateway,
is_device,
is_controller,
is_multidevice,
is_supervisor,
options,
device_initiative,
control_response,
}
}