blob: 4119b20765f30dde6c0ca83b66cff6a0daafa95f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! An implementation that extends the generic control protocol to comply with LCP.
use {
crate::ppp::{
self, ControlProtocol as PppControlProtocol, FrameError, FrameTransmitter, ProtocolError,
ProtocolState, CODE_CODE_REJECT, CODE_CONFIGURE_ACK, CODE_CONFIGURE_NAK,
CODE_CONFIGURE_REJECT, CODE_CONFIGURE_REQUEST, CODE_TERMINATE_ACK, CODE_TERMINATE_REQUEST,
DEFAULT_MAX_FRAME, PROTOCOL_LINK_CONTROL,
},
packet::{Buf, GrowBuffer, InnerPacketBuilder, ParsablePacket, ParseBuffer, Serializer},
ppp_packet::{
link,
records::options::{Options, OptionsSerializer},
CodeRejectPacket, ConfigurationPacket, ControlProtocolPacket, ControlProtocolPacketBuilder,
EchoDiscardPacket, EchoDiscardPacketBuilder, PppPacketBuilder, ProtocolRejectPacket,
ProtocolRejectPacketBuilder, TerminationPacket,
},
};
/// The code byte pattern used in a Protocol-Rej packet.
const CODE_PROTOCOL_REJECT: u8 = 8;
/// The code byte pattern used in an Echo-Request packet.
const CODE_ECHO_REQUEST: u8 = 9;
/// The code byte pattern used in an Echo-Reply packet.
const CODE_ECHO_REPLY: u8 = 10;
/// The code byte pattern used in an Discard-Request packet.
const CODE_DISCARD_REQUEST: u8 = 11;
/// An implementation of the PPP Link Control Protocol.
#[derive(Debug, Copy, Clone)]
pub struct ControlProtocol;
impl ppp::ControlProtocol for ControlProtocol {
type Option = link::ControlOption;
const PROTOCOL_IDENTIFIER: u16 = PROTOCOL_LINK_CONTROL;
fn unacceptable_options(received: &[link::ControlOption]) -> Vec<link::ControlOption> {
received
.iter()
.filter(|option| match option {
link::ControlOption::MagicNumber(magic_number) => *magic_number == 0,
_ => true,
})
.cloned()
.collect::<Vec<_>>()
}
fn parse_options(buf: &[u8]) -> Option<Vec<link::ControlOption>> {
Options::<_, link::ControlOptionsImpl>::parse(buf)
.ok()
.map(|options| options.iter().collect())
}
fn serialize_options(options: &[link::ControlOption]) -> ::packet::Buf<Vec<u8>> {
crate::flatten_either(
OptionsSerializer::<link::ControlOptionsImpl, link::ControlOption, _>::new(
options.iter(),
)
.into_serializer()
.serialize_vec_outer()
.ok()
.unwrap(),
)
}
}
/// Update the current LCP state given the current time by performing restarts.
pub async fn update<T>(
resumable_state: ProtocolState<ControlProtocol>,
transmitter: &T,
time: std::time::Instant,
) -> Result<ProtocolState<ControlProtocol>, ProtocolError<ControlProtocol>>
where
T: FrameTransmitter,
{
resumable_state.restart(transmitter, time).await
}
/// Process an incoming LCP packet, driving the state machine forward, producing a new state or an
/// error.
pub async fn receive<T, B>(
resumable_state: ProtocolState<ControlProtocol>,
transmitter: &T,
mut buf: ::packet::Buf<B>,
time: std::time::Instant,
) -> Result<ProtocolState<ControlProtocol>, ProtocolError<ControlProtocol>>
where
T: FrameTransmitter,
B: AsRef<[u8]> + AsMut<[u8]>,
{
let control_packet = if let Ok(control_packet) = buf.parse::<ControlProtocolPacket<_>>() {
control_packet
} else {
return Ok(resumable_state);
};
let code = control_packet.code();
let identifier = control_packet.identifier();
match code {
CODE_CONFIGURE_REQUEST
| CODE_CONFIGURE_ACK
| CODE_CONFIGURE_NAK
| CODE_CONFIGURE_REJECT => {
if buf.parse::<ConfigurationPacket<_>>().is_err() {
return Ok(resumable_state);
}
let options = if let Some(options) = ControlProtocol::parse_options(buf.as_ref()) {
options
} else {
return Ok(resumable_state);
};
match code {
CODE_CONFIGURE_REQUEST => {
resumable_state.rx_configure_req(transmitter, &options, identifier, time).await
}
CODE_CONFIGURE_ACK => {
resumable_state.rx_configure_ack(transmitter, &options, identifier, time).await
}
CODE_CONFIGURE_NAK | CODE_CONFIGURE_REJECT => {
resumable_state.rx_configure_rej(transmitter, &options, identifier, time).await
}
_ => unreachable!(),
}
}
CODE_TERMINATE_REQUEST | CODE_TERMINATE_ACK => {
if buf.parse::<TerminationPacket<_>>().is_err() {
return Ok(resumable_state);
}
resumable_state.rx_terminate_req(transmitter, identifier).await
}
CODE_CODE_REJECT => {
if buf.parse::<CodeRejectPacket<_>>().is_err() {
return Ok(resumable_state);
}
Err(ProtocolError::FatalCodeRej(buf.as_ref().to_vec()))
}
CODE_PROTOCOL_REJECT => {
let protocol_rej_packet =
if let Ok(protocol_rej_packet) = buf.parse::<ProtocolRejectPacket<_>>() {
protocol_rej_packet
} else {
return Ok(resumable_state);
};
Err(ProtocolError::FatalProtocolRej(protocol_rej_packet.rejected_protocol()))
}
CODE_DISCARD_REQUEST | CODE_ECHO_REPLY => {
let echo_discard_packet =
if let Ok(echo_discard_packet) = buf.parse::<EchoDiscardPacket<_>>() {
echo_discard_packet
} else {
return Ok(resumable_state);
};
let _magic_number = echo_discard_packet.magic_number();
Ok(resumable_state)
}
CODE_ECHO_REQUEST => {
if buf.parse::<EchoDiscardPacket<_>>().is_err() {
return Ok(resumable_state);
}
if let ProtocolState::Opened(ref opened) = &resumable_state {
let magic_number = opened
.local_options()
.iter()
.filter_map(|option| match option {
link::ControlOption::MagicNumber(magic_number) => Some(*magic_number),
_ => None,
})
.next()
.unwrap_or(0);
tx_echo_reply(transmitter, magic_number, identifier).await?;
}
Ok(resumable_state)
}
_ => {
let metadata = control_packet.parse_metadata();
buf.undo_parse(metadata);
ppp::tx_code_rej::<ControlProtocol, _, _>(transmitter, buf, identifier).await?;
Ok(resumable_state)
}
}
}
/// Serialize and transmit a Protocol-Rej packet with the provided rejected protocol and protocol
/// packet body.
pub async fn tx_protocol_rej<T, B>(
transmitter: &T,
buf: B,
rejected_protocol: u16,
identifier: u8,
) -> Result<(), FrameError>
where
T: FrameTransmitter,
B: ::packet::BufferMut,
{
let frame = buf
.encapsulate(ProtocolRejectPacketBuilder::new(rejected_protocol))
.encapsulate(ControlProtocolPacketBuilder::new(CODE_PROTOCOL_REJECT, identifier))
.encapsulate(PppPacketBuilder::new(PROTOCOL_LINK_CONTROL))
.serialize_vec_outer()
.ok()
.unwrap();
// truncate the end bytes
let frame: &[u8] = frame.as_ref();
let bound = std::cmp::min(frame.len(), DEFAULT_MAX_FRAME as usize);
transmitter.tx_frame(&frame[..bound]).await
}
/// Serialize and transmit an Echo-Reply packet with the provided magic number and identifier.
async fn tx_echo_reply<T>(
transmitter: &T,
magic_number: u32,
identifier: u8,
) -> Result<(), FrameError>
where
T: FrameTransmitter,
{
let frame = Buf::new(&mut [], ..)
.encapsulate(EchoDiscardPacketBuilder::new(magic_number))
.encapsulate(ControlProtocolPacketBuilder::new(CODE_ECHO_REPLY, identifier))
.encapsulate(PppPacketBuilder::new(PROTOCOL_LINK_CONTROL))
.serialize_vec_outer()
.ok()
.unwrap();
transmitter.tx_frame(frame.as_ref()).await
}