sccp-protocol 0.2.0

Cisco SCCP (Skinny Client Control Protocol) codec/server
Documentation
use std::net::Ipv6Addr;

use super::*;
use crate::PhoneSoftKeyPosition;
use crate::message::values::{
    AnnouncementPlayMode, EndOfAnnouncementAck, IpAddressType, RFC2833_TELEPHONE_EVENT_PAYLOAD,
    ReceiveTransmit,
};
use crate::message::wire::Frame;
use crate::message::{
    ClientMessage, MediaTransmissionAck, RegistrationMessage, ServerMessage, wire_id,
};
use crate::types::{
    BlfSpeedDialDefinition, FeatureDefinition, LineAppearance, LineDefinition, ServiceDefinition,
    SpeedDialDefinition,
};

mod blf;
mod call;
mod media;
mod registration;
mod services;
mod ui;

mod support {
    pub(super) use super::*;
}
fn definition() -> DeviceDefinition {
    definition_for("SEP001122334455")
}

fn definition_for(device_id: &str) -> DeviceDefinition {
    DeviceDefinition {
        id: DeviceId::new(device_id).unwrap(),
        description: "Test phone".into(),
        transport: StationTransportRequirement::Either,
        signaling_qos: None,
        buttons: vec![ButtonDefinition::Line(LineAppearance::new(
            1,
            LineDefinition {
                number: "1001".into(),
                display_name: "Desk 1001".into(),
            },
        ))],
        soft_keys: SoftKeyProfile::default(),
        ui: Default::default(),
    }
}

fn multicast_test_state(protocol: ProtocolVersion) -> SessionState {
    let device = definition();
    let registration = DeviceRegistration {
        id: device.id.clone(),
        peer: "127.0.0.1:2000".parse().unwrap(),
        transport: StationTransport::Clear,
        reported_address: Some(Ipv4Addr::LOCALHOST),
        reported_ipv6_address: None,
        device_type: DeviceType::Undefined,
        protocol,
        firmware: "test".into(),
    };
    let mut state = SessionState::new(
        device,
        registration,
        PhoneFeatures::default(),
        SessionGeneration::new(1).unwrap(),
    );
    state.media_capabilities = vec![MediaCapability {
        codec: Codec::Pcmu,
        max_frames_per_packet: 2,
        codec_parameters: [0; 8],
    }]
    .into();
    state
}

fn multicast_route(address: IpAddr, codec: Codec) -> MulticastMediaRoute {
    MulticastMediaRoute {
        address,
        port: 5004,
        codec,
        packet_millis: 20,
    }
}

const fn test_rtp_payload_number(value: u8) -> crate::RtpPayloadNumber {
    match crate::RtpPayloadNumber::new(value as u32) {
        Ok(payload_number) => payload_number,
        Err(_) => panic!("test RTP payload number is out of range"),
    }
}

fn video_receive_descriptor(
    conference_id: u32,
    source: MediaEndpointAddress,
) -> MultimediaReceiveDescriptor {
    MultimediaReceiveDescriptor {
        conference_id: ConferenceId::new(conference_id),
        payload: MultimediaPayload::from_wire(
            0,
            test_rtp_payload_number(97),
            [0xa5; crate::MULTIMEDIA_CAPABILITY_BYTES],
            Codec::H264,
            MultimediaPayloadDirection::Receive,
            ProtocolVersion::V22,
        ),
        conference_creator: false,
        encryption: None,
        stream_passthrough_id: conference_id + 100,
        associated_stream_id: 0,
        source,
        requested_address_type: IpAddressType::Ipv4AndIpv6,
    }
}

fn video_transmit_descriptor(
    conference_id: u32,
    endpoint: MediaEndpointAddress,
) -> MultimediaTransmitDescriptor {
    MultimediaTransmitDescriptor {
        conference_id: ConferenceId::new(conference_id),
        endpoint,
        payload: MultimediaPayload::from_wire(
            0,
            test_rtp_payload_number(98),
            [0x5a; crate::MULTIMEDIA_CAPABILITY_BYTES],
            Codec::H264,
            MultimediaPayloadDirection::Transmit,
            ProtocolVersion::V22,
        ),
        traffic_class: MediaTrafficClass::from_wire(136),
        encryption: None,
        stream_passthrough_id: conference_id + 200,
        associated_stream_id: 0,
    }
}

fn mixed_definition() -> DeviceDefinition {
    let mut device = definition();
    device.buttons.extend([
        ButtonDefinition::SpeedDial(SpeedDialDefinition {
            instance: 1,
            number: "2001".into(),
            display_name: "Reception".into(),
        }),
        ButtonDefinition::Feature(FeatureDefinition {
            instance: 1,
            label: "DND".into(),
            feature: ButtonType::DoNotDisturb,
        }),
        ButtonDefinition::Service(ServiceDefinition {
            instance: 1,
            label: "Directory".into(),
            url: "http://services.invalid/directory".into(),
        }),
        ButtonDefinition::Unused,
        ButtonDefinition::BlfSpeedDial(BlfSpeedDialDefinition {
            instance: 2,
            number: "2002".into(),
            display_name: "Warehouse".into(),
        }),
    ]);
    device
}

fn profile_with(mode: KeyMode, actions: Vec<SoftKey>) -> SoftKeyProfile {
    let default = SoftKeyProfile::default();
    SoftKeyProfile::new(KeyMode::ALL_KNOWN.iter().copied().map(|candidate| {
        if candidate == mode {
            (candidate, actions.clone())
        } else {
            (candidate, default.actions(candidate).to_vec())
        }
    }))
    .unwrap()
}

fn session_call(call_id: u64) -> SessionCall {
    SessionCall {
        call_id: CallId(call_id),
        wire_reference: call_id as u32,
        line_instance: 1,
        media: CallMedia::new(Codec::Pcmu),
        video_receive: VideoReceive::default(),
        video_transmit: VideoTransmit::default(),
        state: CallState::Connected,
        history_disposition: CallHistoryDisposition::Placed,
        dialed_number: String::new(),
        statistics_directory_number: String::new(),
        transfer_role: None,
    }
}

fn register_bytes(protocol: ProtocolVersion) -> Vec<u8> {
    register_bytes_for_device_type(protocol, 115)
}

fn register_bytes_with_features(protocol: ProtocolVersion, features: PhoneFeatures) -> Vec<u8> {
    register_bytes_for_device_with_features(protocol, 115, "SEP001122334455", features)
}

fn register_bytes_for_device_type(protocol: ProtocolVersion, device_type: u32) -> Vec<u8> {
    register_bytes_for_device(protocol, device_type, "SEP001122334455")
}

fn register_bytes_for_device(
    protocol: ProtocolVersion,
    device_type: u32,
    device_id: &str,
) -> Vec<u8> {
    register_bytes_for_device_with_features(
        protocol,
        device_type,
        device_id,
        PhoneFeatures::empty(),
    )
}

fn register_bytes_for_device_with_features(
    protocol: ProtocolVersion,
    device_type: u32,
    device_id: &str,
    features: PhoneFeatures,
) -> Vec<u8> {
    let mut payload = vec![0_u8; 124];
    let device_id = device_id.as_bytes();
    assert!(device_id.len() <= 16);
    payload[..device_id.len()].copy_from_slice(device_id);
    payload[24..28].copy_from_slice(&[127, 0, 0, 1]);
    payload[28..32].copy_from_slice(&device_type.to_le_bytes());
    payload[40..44].copy_from_slice(&(protocol.wire() | features.bits()).to_le_bytes());
    payload[92..101].copy_from_slice(b"SCCP42.9-");
    Frame::new(0, wire_id::REGISTER, payload).encode().unwrap()
}

fn capability_update_bytes(
    protocol: ProtocolVersion,
    audio_codec: Codec,
    video_codec: Codec,
    marker: u32,
) -> Vec<u8> {
    const AUDIO_OFFSET: usize = 312;
    const VIDEO_OFFSET: usize = 600;

    fn put(payload: &mut [u8], offset: usize, value: u32) {
        payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
    }

    let mut payload = vec![0; 2_380];
    put(&mut payload, 0, 1);
    put(&mut payload, 4, 1);
    put(&mut payload, AUDIO_OFFSET, audio_codec.wire_value());
    put(&mut payload, AUDIO_OFFSET + 4, marker);
    payload[AUDIO_OFFSET + 8..AUDIO_OFFSET + 16].copy_from_slice(&marker.to_le_bytes().repeat(2));

    put(&mut payload, VIDEO_OFFSET, video_codec.wire_value());
    put(
        &mut payload,
        VIDEO_OFFSET + 4,
        (ReceiveTransmit::RECEIVE | ReceiveTransmit::TRANSMIT).bits(),
    );
    put(&mut payload, VIDEO_OFFSET + 8, 1);
    for (index, value) in [marker, 5, 4_000, 128, 2, 7].into_iter().enumerate() {
        put(&mut payload, VIDEO_OFFSET + 12 + index * 4, value);
    }
    put(
        &mut payload,
        VIDEO_OFFSET + 108,
        u32::from(EncryptionCapability::Capable),
    );
    for (index, value) in [66, 31, 120, 240, 360, marker].into_iter().enumerate() {
        put(&mut payload, VIDEO_OFFSET + 112 + index * 4, value);
    }
    put(
        &mut payload,
        VIDEO_OFFSET + 136,
        u32::from(IpAddressType::Ipv4AndIpv6),
    );
    Frame::new(protocol.wire(), wire_id::UPDATE_CAPABILITIES_V3, payload)
        .encode()
        .unwrap()
}

async fn read_until_message(
    phone: &mut dyn StationIo,
    decoder: &mut FrameDecoder,
    message_id: u32,
) -> Vec<Frame> {
    let mut frames = Vec::new();
    let mut buffer = [0_u8; 2048];
    while !frames
        .iter()
        .any(|frame: &Frame| frame.message_id == message_id)
    {
        let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
            .await
            .expect("timed out waiting for SCCP response")
            .expect("could not read SCCP response");
        assert_ne!(count, 0, "SCCP session closed while waiting for response");
        frames.extend(decoder.push(&buffer[..count]).unwrap());
    }
    frames
}

async fn read_until_server_message(
    phone: &mut dyn StationIo,
    decoder: &mut FrameDecoder,
    protocol: ProtocolVersion,
    predicate: impl Fn(&ServerMessage) -> bool,
) -> Vec<ServerMessage> {
    let mut messages = Vec::new();
    let mut buffer = [0_u8; 2048];
    while !messages.iter().any(&predicate) {
        let count = tokio::time::timeout(Duration::from_secs(1), phone.read(&mut buffer))
            .await
            .expect("timed out waiting for SCCP response")
            .expect("could not read SCCP response");
        assert_ne!(count, 0, "SCCP session closed while waiting for response");
        messages.extend(
            decoder
                .push(&buffer[..count])
                .unwrap()
                .into_iter()
                .map(|frame| ServerMessage::decode(frame, protocol).unwrap()),
        );
    }
    messages
}

fn open_receive_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
    frames
        .iter()
        .find_map(
            |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
                ServerMessage::OpenReceiveChannel {
                    passthrough_party_id,
                    ..
                } => Some(passthrough_party_id),
                _ => None,
            },
        )
        .expect("transaction omitted OpenReceiveChannel")
}

fn start_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
    frames
        .iter()
        .find_map(
            |frame| match ServerMessage::decode(frame.clone(), protocol).ok()? {
                ServerMessage::StartMediaTransmission {
                    passthrough_party_id,
                    ..
                } => Some(passthrough_party_id),
                _ => None,
            },
        )
        .expect("transaction omitted StartMediaTransmission")
}

fn coupled_media_request_party(frames: &[Frame], protocol: ProtocolVersion) -> u32 {
    let receive = open_receive_request_party(frames, protocol);
    let transmit = start_media_request_party(frames, protocol);
    assert_ne!(receive, 0);
    assert_eq!(receive, transmit, "coupled request identities diverged");
    receive
}

fn test_connection_statistics(directory_number: &str, call_reference: u32) -> ConnectionStatistics {
    ConnectionStatistics {
        directory_number: directory_number.into(),
        call_reference,
        processing: StatisticsProcessing::Clear,
        packets_sent: 120,
        octets_sent: 9_600,
        packets_received: 118,
        octets_received: 9_440,
        packets_lost: 2,
        jitter_millis: 6,
        latency_millis: 17,
        quality: crate::ConnectionQualityStatistics::new(b"MLQK=4.4".to_vec()).unwrap(),
    }
}

#[derive(Debug)]
struct RecordingSocketQos {
    applied: Arc<std::sync::Mutex<Vec<SignalingQos>>>,
    fail: bool,
}

impl StationSocketQos for RecordingSocketQos {
    fn apply(&self, qos: SignalingQos) -> SocketQosReport {
        self.applied.lock().unwrap().push(qos);
        if self.fail {
            SocketQosReport::failed(
                SocketQosMark::SocketPriority,
                std::io::Error::new(std::io::ErrorKind::Unsupported, "test platform"),
            )
        } else {
            SocketQosReport::default()
        }
    }
}