// Public API implementation for the TELNET core.
///|
pub(all) struct Parser {
config : ParserConfig
checkpoint : ParserCheckpoint
buffered_data : Bytes
buffered_subnegotiation : Bytes
pending_cr : Bool
discarding_subnegotiation : Bool
} derive(Eq, Debug)
///|
pub(all) struct ParserFeedResult {
parser : Parser
events : Array[Event]
bytes_consumed : Int
} derive(Eq, Debug)
///|
pub(all) struct ParserFinishResult {
parser : Parser
events : Array[Event]
complete : Bool
} derive(Eq, Debug)
///|
fn b(n : Int) -> Byte {
n.to_byte()
}
///|
fn unsafe_bytes_to_fixedarray(bytes : Bytes) -> FixedArray[Byte] = "%identity"
///|
fn setb(bytes : Bytes, index : Int, value : Byte) -> Unit {
unsafe_bytes_to_fixedarray(bytes)[index] = value
}
///|
fn span_from_array(xs : Array[Byte]) -> ByteSpan {
let bs = Bytes::from_array(xs)
ByteSpan::{ bytes: bs, start: 0, length: bs.length() }
}
///|
fn empty_checkpoint() -> ParserCheckpoint {
ParserCheckpoint::{
mode: ParserMode::Normal,
absolute_offset: 0,
buffered_data_length: 0,
buffered_subnegotiation_length: 0,
}
}
///|
pub fn Parser::default_config() -> ParserConfig {
ParserConfig::{
max_data_chunk_bytes: 8192,
max_subnegotiation_bytes: 8192,
cr_policy: CrPolicy::Preserve,
data_coalescing: DataCoalescing::CoalesceUntilCommand,
strict_commands: true,
}
}
///|
pub fn Parser::new(config : ParserConfig) -> Parser {
let fixed = ParserConfig::{
max_data_chunk_bytes: if config.max_data_chunk_bytes < 0 {
0
} else {
config.max_data_chunk_bytes
},
max_subnegotiation_bytes: if config.max_subnegotiation_bytes < 0 {
0
} else {
config.max_subnegotiation_bytes
},
cr_policy: config.cr_policy,
data_coalescing: config.data_coalescing,
strict_commands: config.strict_commands,
}
Parser::{
config: fixed,
checkpoint: empty_checkpoint(),
buffered_data: Bytes::new(0),
buffered_subnegotiation: Bytes::new(0),
pending_cr: false,
discarding_subnegotiation: false,
}
}
///|
pub fn Parser::default() -> Parser {
Parser::new(Parser::default_config())
}
///|
fn err_event(
kind : TelnetErrorKind,
offset : Int,
byte : Byte?,
option : OptionCode?,
) -> Event {
Event::Error(TelnetError::{ kind, offset, byte, option })
}
///|
fn data_event(xs : Array[Byte]) -> Event {
Event::Data(DataEvent::{ span: span_from_array(xs) })
}
///|
fn flush_data(data : Array[Byte], events : Array[Event]) -> Unit {
if data.length() > 0 {
events.push(data_event(data.copy()))
data.clear()
}
}
///|
fn append_span_to_array(out : Array[Byte], span : ByteSpan) -> Unit {
for i = 0; i < span.length; i = i + 1 {
out.push(span.bytes[span.start + i])
}
}
///|
pub fn Parser::feed(self : Parser, chunk : Bytes) -> ParserFeedResult {
self.feed_span(ByteSpan::{ bytes: chunk, start: 0, length: chunk.length() })
}
///|
pub fn Parser::feed_span(self : Parser, span : ByteSpan) -> ParserFeedResult {
self.feed_span_with_events(span, Array::new())
}
///|
pub fn Parser::feed_span_into(
self : Parser,
span : ByteSpan,
events : Array[Event],
) -> ParserFeedResult {
events.clear()
self.feed_span_with_events(span, events)
}
///|
#cfg(target="native")
#borrow(bytes)
extern "C" fn find_byte_native(
bytes : Bytes,
start : Int,
length : Int,
needle : Int,
) -> Int = "moonbit_telnet_find_byte"
///|
#cfg(not(target="native"))
fn find_byte_native(
bytes : Bytes,
start : Int,
length : Int,
needle : Int,
) -> Int {
let target = b(needle)
let mut i = 0
while i < length {
if bytes[start + i] == target {
return i
}
i = i + 1
}
-1
}
///|
fn span_has_iac(span : ByteSpan) -> Bool {
find_byte_native(span.bytes, span.start, span.length, 255) >= 0
}
///|
fn push_preserved_data_events(
events : Array[Event],
span : ByteSpan,
max_chunk_bytes : Int,
coalescing : DataCoalescing,
borrow_full_span : Bool,
) -> Unit {
let chunk_size = if coalescing == DataCoalescing::EmitImmediately {
1
} else {
max_chunk_bytes
}
let mut consumed = 0
while consumed < span.length {
let remaining = span.length - consumed
let n = if remaining < chunk_size { remaining } else { chunk_size }
let event_span = if borrow_full_span && consumed == 0 && n == span.length {
ByteSpan::{ bytes: span.bytes, start: span.start, length: n }
} else {
ByteSpan::{
bytes: ByteSpan::{
bytes: span.bytes,
start: span.start + consumed,
length: n,
}.to_bytes(),
start: 0,
length: n,
}
}
events.push(Event::Data(DataEvent::{ span: event_span }))
consumed = consumed + n
}
}
///|
fn can_use_preserve_data_fast_path(parser : Parser, span : ByteSpan) -> Bool {
parser.checkpoint.mode == ParserMode::Normal &&
!parser.pending_cr &&
!parser.discarding_subnegotiation &&
parser.buffered_data.length() == 0 &&
parser.buffered_subnegotiation.length() == 0 &&
parser.config.max_data_chunk_bytes > 0 &&
parser.config.cr_policy == CrPolicy::Preserve &&
!span_has_iac(span)
}
///|
fn Parser::feed_span_with_events(
self : Parser,
span : ByteSpan,
events : Array[Event],
) -> ParserFeedResult {
if span.length == 0 {
return ParserFeedResult::{ parser: self, events, bytes_consumed: 0 }
}
if can_use_preserve_data_fast_path(self, span) {
push_preserved_data_events(
events,
span,
self.config.max_data_chunk_bytes,
self.config.data_coalescing,
true,
)
return ParserFeedResult::{
parser: Parser::{
config: self.config,
checkpoint: ParserCheckpoint::{
mode: ParserMode::Normal,
absolute_offset: self.checkpoint.absolute_offset + span.length,
buffered_data_length: 0,
buffered_subnegotiation_length: 0,
},
buffered_data: Bytes::new(0),
buffered_subnegotiation: Bytes::new(0),
pending_cr: false,
discarding_subnegotiation: false,
},
events,
bytes_consumed: span.length,
}
}
let data = if self.pending_cr {
Array::new()
} else {
self.buffered_data.to_array()
}
let sub = self.buffered_subnegotiation.to_array()
let mut mode = self.checkpoint.mode
let mut discarding_subnegotiation = self.discarding_subnegotiation
let mut i = 0
if self.pending_cr {
let byte = span.bytes[span.start]
if self.config.cr_policy == CrPolicy::ValidateNvt {
if byte != b(10) && byte != b(0) {
events.push(
err_event(
TelnetErrorKind::PolicyRejected,
self.checkpoint.absolute_offset,
Some(byte),
None,
),
)
} else {
data.push(b(13))
data.push(byte)
flush_data(data, events)
i = 1
}
} else if self.config.cr_policy == CrPolicy::NormalizeToLf && byte == b(10) {
data.push(b(10))
flush_data(data, events)
i = 1
} else if self.config.cr_policy == CrPolicy::NormalizeToLf && byte == b(0) {
data.push(b(13))
flush_data(data, events)
i = 1
} else if self.config.cr_policy == CrPolicy::NormalizeToLf {
data.push(b(13))
}
}
while i < span.length {
let absolute = self.checkpoint.absolute_offset + i
let byte = span.bytes[span.start + i]
match mode {
ParserMode::Normal =>
if byte == b(255) {
flush_data(data, events)
mode = ParserMode::AfterIac
} else if self.config.cr_policy == CrPolicy::Preserve &&
self.config.max_data_chunk_bytes > 0 {
let run_start = i
let next_iac = find_byte_native(
span.bytes,
span.start + i,
span.length - i,
255,
)
let run_end = if next_iac < 0 { span.length } else { i + next_iac }
push_preserved_data_events(
events,
ByteSpan::{
bytes: span.bytes,
start: span.start + run_start,
length: run_end - run_start,
},
self.config.max_data_chunk_bytes,
self.config.data_coalescing,
false,
)
i = run_end - 1
} else if self.config.max_data_chunk_bytes == 0 {
events.push(
err_event(
TelnetErrorKind::PolicyRejected,
absolute,
Some(byte),
None,
),
)
} else if self.config.cr_policy == CrPolicy::ValidateNvt &&
byte == b(13) &&
i + 1 == span.length {
flush_data(data, events)
data.push(byte)
} else if self.config.cr_policy == CrPolicy::ValidateNvt &&
byte == b(13) &&
i + 1 < span.length &&
span.bytes[span.start + i + 1] != b(10) &&
span.bytes[span.start + i + 1] != b(0) {
flush_data(data, events)
events.push(
err_event(
TelnetErrorKind::PolicyRejected,
absolute + 1,
Some(span.bytes[span.start + i + 1]),
None,
),
)
} else if self.config.cr_policy == CrPolicy::NormalizeToLf &&
byte == b(13) &&
i + 1 == span.length {
flush_data(data, events)
data.push(byte)
} else if self.config.cr_policy == CrPolicy::NormalizeToLf &&
byte == b(13) &&
i + 1 < span.length &&
span.bytes[span.start + i + 1] == b(10) {
data.push(b(10))
i = i + 1
flush_data(data, events)
} else if self.config.cr_policy == CrPolicy::NormalizeToLf &&
byte == b(13) &&
i + 1 < span.length &&
span.bytes[span.start + i + 1] == b(0) {
data.push(b(13))
i = i + 1
flush_data(data, events)
} else if self.config.cr_policy == CrPolicy::ValidateNvt &&
byte == b(13) &&
data.length() > 0 {
flush_data(data, events)
data.push(byte)
} else {
data.push(byte)
if self.config.data_coalescing == DataCoalescing::EmitImmediately ||
data.length() >= self.config.max_data_chunk_bytes {
flush_data(data, events)
}
}
ParserMode::AfterIac =>
if byte == b(255) {
data.push(b(255))
flush_data(data, events)
mode = ParserMode::Normal
} else {
match Command::from_byte(byte) {
Some(Command::WILL) =>
mode = ParserMode::AfterNegotiationVerb(NegotiationVerb::Will)
Some(Command::WONT) =>
mode = ParserMode::AfterNegotiationVerb(NegotiationVerb::Wont)
Some(Command::DO) =>
mode = ParserMode::AfterNegotiationVerb(NegotiationVerb::Do)
Some(Command::DONT) =>
mode = ParserMode::AfterNegotiationVerb(NegotiationVerb::Dont)
Some(Command::SB) => mode = ParserMode::AfterSubnegotiationStart
Some(Command::SE) => {
events.push(
err_event(
TelnetErrorKind::UnexpectedSubnegotiationEnd,
absolute,
Some(byte),
None,
),
)
mode = ParserMode::Normal
}
Some(c) => {
events.push(Event::Command(CommandEvent::{ command: c }))
mode = ParserMode::Normal
}
None => {
events.push(
err_event(
TelnetErrorKind::InvalidCommandByte,
absolute,
Some(byte),
None,
),
)
mode = ParserMode::Normal
}
}
}
ParserMode::AfterNegotiationVerb(v) => {
events.push(
Event::Negotiation(NegotiationEvent::{
verb: v,
option: OptionCode::{ value: byte },
}),
)
mode = ParserMode::Normal
}
ParserMode::AfterSubnegotiationStart => {
sub.clear()
mode = ParserMode::InSubnegotiation(OptionCode::{ value: byte })
}
ParserMode::InSubnegotiation(opt) =>
if byte == b(255) {
mode = ParserMode::InSubnegotiationAfterIac(opt)
} else {
let run_start = i
let next_iac = find_byte_native(
span.bytes,
span.start + i,
span.length - i,
255,
)
let run_end = if next_iac < 0 { span.length } else { i + next_iac }
if !discarding_subnegotiation {
let run_len = run_end - run_start
let remaining_capacity = self.config.max_subnegotiation_bytes -
sub.length()
if run_len > remaining_capacity {
let overflow_index = if remaining_capacity < 0 {
0
} else {
remaining_capacity
}
if overflow_index >= 0 {
append_span_to_array(sub, ByteSpan::{
bytes: span.bytes,
start: span.start + run_start,
length: overflow_index + 1,
})
}
events.push(
err_event(
TelnetErrorKind::SubnegotiationTooLarge,
absolute + overflow_index,
Some(span.bytes[span.start + run_start + overflow_index]),
None,
),
)
sub.clear()
discarding_subnegotiation = true
} else {
append_span_to_array(sub, ByteSpan::{
bytes: span.bytes,
start: span.start + run_start,
length: run_len,
})
}
}
i = run_end - 1
}
ParserMode::InSubnegotiationAfterIac(opt) =>
if byte == b(255) {
if !discarding_subnegotiation {
sub.push(b(255))
if sub.length() > self.config.max_subnegotiation_bytes {
events.push(
err_event(
TelnetErrorKind::SubnegotiationTooLarge,
absolute,
Some(byte),
None,
),
)
sub.clear()
discarding_subnegotiation = true
}
}
mode = ParserMode::InSubnegotiation(opt)
} else if byte == b(240) {
if !discarding_subnegotiation {
events.push(
Event::Subnegotiation(SubnegotiationEvent::{
option: opt,
payload: span_from_array(sub.copy()),
}),
)
}
sub.clear()
discarding_subnegotiation = false
mode = ParserMode::Normal
} else {
let err_opt = if self.config.cr_policy == CrPolicy::ValidateNvt {
Some(opt)
} else {
None
}
events.push(
err_event(
TelnetErrorKind::InvalidCommandByte,
absolute,
Some(byte),
err_opt,
),
)
mode = ParserMode::InSubnegotiation(opt)
}
}
i = i + 1
}
let pending_cr = mode == ParserMode::Normal &&
data.length() == 1 &&
data[0] == b(13) &&
(
self.config.cr_policy == CrPolicy::ValidateNvt ||
self.config.cr_policy == CrPolicy::NormalizeToLf
)
if !pending_cr {
flush_data(data, events)
}
let cp = ParserCheckpoint::{
mode,
absolute_offset: self.checkpoint.absolute_offset + span.length,
buffered_data_length: data.length(),
buffered_subnegotiation_length: sub.length(),
}
ParserFeedResult::{
parser: Parser::{
config: self.config,
checkpoint: cp,
buffered_data: if pending_cr {
Bytes::from_array(data)
} else {
Bytes::new(0)
},
buffered_subnegotiation: Bytes::from_array(sub),
pending_cr,
discarding_subnegotiation,
},
events,
bytes_consumed: span.length,
}
}
///|
pub fn Parser::finish(self : Parser) -> ParserFinishResult {
let events = Array::new()
let offset = if self.checkpoint.absolute_offset > 0 {
self.checkpoint.absolute_offset - 1
} else {
0
}
match self.checkpoint.mode {
ParserMode::Normal =>
if self.pending_cr {
events.push(
err_event(TelnetErrorKind::PolicyRejected, offset, Some(b(13)), None),
)
}
ParserMode::AfterIac =>
events.push(
err_event(TelnetErrorKind::IncompleteIac, offset, Some(b(255)), None),
)
ParserMode::AfterNegotiationVerb(v) =>
events.push(
err_event(
TelnetErrorKind::IncompleteNegotiation,
offset,
Some(v.to_command().to_byte()),
None,
),
)
ParserMode::AfterSubnegotiationStart
| ParserMode::InSubnegotiation(_)
| ParserMode::InSubnegotiationAfterIac(_) =>
if !self.discarding_subnegotiation {
events.push(
err_event(
TelnetErrorKind::IncompleteSubnegotiation,
offset,
if self.buffered_subnegotiation.length() > 0 {
Some(
self.buffered_subnegotiation[self.buffered_subnegotiation.length() -
1],
)
} else {
None
},
None,
),
)
}
}
ParserFinishResult::{
parser: self.reset(),
events,
complete: events.length() == 0,
}
}
///|
pub fn Parser::checkpoint(self : Parser) -> ParserCheckpoint {
self.checkpoint
}
///|
pub fn Parser::restore(
config : ParserConfig,
checkpoint : ParserCheckpoint,
) -> Parser {
Parser::{
config,
checkpoint,
buffered_data: Bytes::new(0),
buffered_subnegotiation: Bytes::new(0),
pending_cr: false,
discarding_subnegotiation: false,
}
}
///|
pub fn Parser::reset(self : Parser) -> Parser {
Parser::{
config: self.config,
checkpoint: empty_checkpoint(),
buffered_data: Bytes::new(0),
buffered_subnegotiation: Bytes::new(0),
pending_cr: false,
discarding_subnegotiation: false,
}
}
///|
pub fn Parser::with_config(self : Parser, config : ParserConfig) -> Parser {
Parser::{
config,
checkpoint: self.checkpoint,
buffered_data: self.buffered_data,
buffered_subnegotiation: self.buffered_subnegotiation,
pending_cr: self.pending_cr,
discarding_subnegotiation: self.discarding_subnegotiation,
}
}
///|
pub(all) struct Encoder {
canonical : Bool
} derive(Eq, Debug)
///|
pub(all) struct EncodeResult {
bytes_written : Int
required_capacity : Int
} derive(Eq, Debug)
///|
pub fn Encoder::new() -> Encoder {
Encoder::{ canonical: false }
}
///|
pub fn Encoder::canonical() -> Encoder {
Encoder::{ canonical: true }
}
///|
fn span_required(span : ByteSpan, escaped : Bool) -> Int {
let mut n = 0
for i = 0; i < span.length; i = i + 1 {
n = n + 1
if escaped && span.bytes[span.start + i] == b(255) {
n = n + 1
}
}
n
}
///|
pub fn Encoder::required_capacity(_self : Encoder, item : EncodeItem) -> Int {
match item {
EncodeItem::RawData(s) => s.length
EncodeItem::EscapedData(s) => span_required(s, true)
EncodeItem::Command(_) => 2
EncodeItem::Negotiation(_) => 3
EncodeItem::Subnegotiation(e) => 5 + span_required(e.payload, true)
}
}
///|
fn output_too_small(
required : Int,
output : Bytes,
) -> Result[EncodeResult, EncodeError]? {
if output.length() < required {
Some(
Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
}),
)
} else {
None
}
}
///|
pub fn Encoder::encode_item(
self : Encoder,
item : EncodeItem,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
match item {
EncodeItem::RawData(s) => self.encode_data(s, output)
EncodeItem::EscapedData(s) => self.encode_data(s, output)
EncodeItem::Command(c) => self.encode_command(c, output)
EncodeItem::Negotiation(e) => self.encode_negotiation(e, output)
EncodeItem::Subnegotiation(e) => self.encode_subnegotiation(e, output)
}
}
///|
pub fn Encoder::encode_data(
self : Encoder,
data : ByteSpan,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
if data.start < 0 ||
data.length < 0 ||
data.start + data.length > data.bytes.length() {
return Err(EncodeError::{
kind: EncodeErrorKind::UnsupportedEncoding,
offset: 0,
required_capacity: 0,
})
}
if !self.canonical {
match output_too_small(data.length, output) {
Some(e) => return e
None => ()
}
FixedArray::blit_from_bytes(
unsafe_bytes_to_fixedarray(output),
0,
data.bytes,
data.start,
data.length,
)
return Ok(EncodeResult::{
bytes_written: data.length,
required_capacity: data.length,
})
}
let required = span_required(data, true)
match output_too_small(required, output) {
Some(e) => return e
None => ()
}
let mut j = 0
for i = 0; i < data.length; i = i + 1 {
let x = data.bytes[data.start + i]
setb(output, j, x)
j = j + 1
if x == b(255) {
setb(output, j, b(255))
j = j + 1
}
}
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn Encoder::encode_data_assume_capacity(
self : Encoder,
data : ByteSpan,
output : Bytes,
required : Int,
) -> Result[EncodeResult, EncodeError] {
if data.start < 0 ||
data.length < 0 ||
data.start + data.length > data.bytes.length() {
return Err(EncodeError::{
kind: EncodeErrorKind::UnsupportedEncoding,
offset: 0,
required_capacity: 0,
})
}
if required < 0 {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
match output_too_small(required, output) {
Some(e) => return e
None => ()
}
if !self.canonical {
FixedArray::blit_from_bytes(
unsafe_bytes_to_fixedarray(output),
0,
data.bytes,
data.start,
data.length,
)
return Ok(EncodeResult::{
bytes_written: required,
required_capacity: required,
})
}
let mut j = 0
for i = 0; i < data.length; i = i + 1 {
let x = data.bytes[data.start + i]
setb(output, j, x)
j = j + 1
if x == b(255) {
setb(output, j, b(255))
j = j + 1
}
}
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn Encoder::encode_command(
_self : Encoder,
command : Command,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
match command {
Command::SE
| Command::IAC
| Command::SB
| Command::WILL
| Command::WONT
| Command::DO
| Command::DONT =>
return Err(EncodeError::{
kind: EncodeErrorKind::UnsupportedEncoding,
offset: 0,
required_capacity: 0,
})
_ => ()
}
match output_too_small(2, output) {
Some(e) => return e
None => ()
}
setb(output, 0, b(255))
setb(output, 1, command.to_byte())
Ok(EncodeResult::{ bytes_written: 2, required_capacity: 2 })
}
///|
pub fn Encoder::encode_negotiation(
_self : Encoder,
event : NegotiationEvent,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
match output_too_small(3, output) {
Some(e) => return e
None => ()
}
setb(output, 0, b(255))
setb(output, 1, event.verb.to_command().to_byte())
setb(output, 2, event.option.value)
Ok(EncodeResult::{ bytes_written: 3, required_capacity: 3 })
}
///|
pub fn Encoder::encode_subnegotiation(
_self : Encoder,
event : SubnegotiationEvent,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
let required = 5 + span_required(event.payload, true)
match output_too_small(required, output) {
Some(e) => return e
None => ()
}
setb(output, 0, b(255))
setb(output, 1, b(250))
setb(output, 2, event.option.value)
let mut j = 3
for i = 0; i < event.payload.length; i = i + 1 {
let x = event.payload.bytes[event.payload.start + i]
setb(output, j, x)
j = j + 1
if x == b(255) {
setb(output, j, b(255))
j = j + 1
}
}
setb(output, j, b(255))
setb(output, j + 1, b(240))
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn Encoder::encode_subnegotiation_assume_capacity(
_self : Encoder,
event : SubnegotiationEvent,
output : Bytes,
required : Int,
) -> Result[EncodeResult, EncodeError] {
if required < 0 {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
match output_too_small(required, output) {
Some(e) => return e
None => ()
}
setb(output, 0, b(255))
setb(output, 1, b(250))
setb(output, 2, event.option.value)
let mut j = 3
for i = 0; i < event.payload.length; i = i + 1 {
let x = event.payload.bytes[event.payload.start + i]
setb(output, j, x)
j = j + 1
if x == b(255) {
setb(output, j, b(255))
j = j + 1
}
}
setb(output, j, b(255))
setb(output, j + 1, b(240))
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub(all) struct Negotiator {
states : Array[OptionState]
} derive(Eq, Debug)
///|
pub fn Negotiator::new() -> Negotiator {
Negotiator::{ states: [] }
}
///|
pub fn Negotiator::with_states(states : Array[OptionState]) -> Negotiator {
Negotiator::{ states, }
}
///|
pub fn Negotiator::state_for(
self : Negotiator,
option : OptionCode,
) -> OptionState {
for s in self.states {
if s.option == option {
return s
}
}
OptionState::{
option,
local_state: HalfState::No,
remote_state: HalfState::No,
}
}
///|
pub fn Negotiator::receive(
self : Negotiator,
input : NegotiationEvent,
decision : PolicyDecision,
) -> NegotiationTransition {
let before = self.state_for(input.option)
let mut after = before
let actions = Array::new()
match input.verb {
NegotiationVerb::Will =>
if decision == PolicyDecision::Accept &&
before.remote_state != HalfState::Yes {
after = OptionState::{
option: input.option,
local_state: before.local_state,
remote_state: HalfState::Yes,
}
actions.push(
NegotiationAction::Send(NegotiationEvent::{
verb: NegotiationVerb::Do,
option: input.option,
}),
)
actions.push(
NegotiationAction::MarkEnabled(OptionSide::Remote, input.option),
)
} else if decision == PolicyDecision::Reject {
actions.push(
NegotiationAction::Send(NegotiationEvent::{
verb: NegotiationVerb::Dont,
option: input.option,
}),
)
} else {
actions.push(NegotiationAction::NoAction)
}
NegotiationVerb::Wont => {
after = OptionState::{
option: input.option,
local_state: before.local_state,
remote_state: HalfState::No,
}
actions.push(
NegotiationAction::MarkDisabled(OptionSide::Remote, input.option),
)
}
NegotiationVerb::Do =>
if decision == PolicyDecision::Accept &&
before.local_state != HalfState::Yes {
after = OptionState::{
option: input.option,
local_state: HalfState::Yes,
remote_state: before.remote_state,
}
actions.push(
NegotiationAction::Send(NegotiationEvent::{
verb: NegotiationVerb::Will,
option: input.option,
}),
)
actions.push(
NegotiationAction::MarkEnabled(OptionSide::Local, input.option),
)
} else if decision == PolicyDecision::Reject {
actions.push(
NegotiationAction::Send(NegotiationEvent::{
verb: NegotiationVerb::Wont,
option: input.option,
}),
)
} else {
actions.push(NegotiationAction::NoAction)
}
NegotiationVerb::Dont => {
after = OptionState::{
option: input.option,
local_state: HalfState::No,
remote_state: before.remote_state,
}
actions.push(
NegotiationAction::Send(NegotiationEvent::{
verb: NegotiationVerb::Wont,
option: input.option,
}),
)
actions.push(
NegotiationAction::MarkDisabled(OptionSide::Local, input.option),
)
}
}
NegotiationTransition::{ before, input, decision, after, actions }
}
///|
pub fn Negotiator::request(
self : Negotiator,
option : OptionCode,
side : OptionSide,
enable : Bool,
) -> NegotiationTransition {
let before = self.state_for(option)
let verb = match (side, enable) {
(OptionSide::Remote, true) => NegotiationVerb::Do
(OptionSide::Remote, false) => NegotiationVerb::Dont
(OptionSide::Local, true) => NegotiationVerb::Will
(OptionSide::Local, false) => NegotiationVerb::Wont
}
let current = match side {
OptionSide::Local => before.local_state
OptionSide::Remote => before.remote_state
}
let (after, actions) = match (side, enable, current) {
(OptionSide::Local, true, HalfState::Yes)
| (OptionSide::Remote, true, HalfState::Yes) =>
(before, [NegotiationAction::NoAction])
(OptionSide::Local, false, HalfState::No)
| (OptionSide::Remote, false, HalfState::No) =>
(before, [NegotiationAction::NoAction])
(OptionSide::Local, false, HalfState::WantYes(QueueBit::Empty)) =>
(
OptionState::{
option,
local_state: HalfState::WantYes(QueueBit::Opposite),
remote_state: before.remote_state,
},
[NegotiationAction::NoAction],
)
(OptionSide::Remote, false, HalfState::WantYes(QueueBit::Empty)) =>
(
OptionState::{
option,
local_state: before.local_state,
remote_state: HalfState::WantYes(QueueBit::Opposite),
},
[NegotiationAction::NoAction],
)
(OptionSide::Remote, true, _) =>
(
OptionState::{
option,
local_state: before.local_state,
remote_state: HalfState::WantYes(QueueBit::Empty),
},
[NegotiationAction::Send(NegotiationEvent::{ verb, option })],
)
(OptionSide::Remote, false, _) =>
(
OptionState::{
option,
local_state: before.local_state,
remote_state: HalfState::WantNo(QueueBit::Empty),
},
[NegotiationAction::Send(NegotiationEvent::{ verb, option })],
)
(OptionSide::Local, true, _) =>
(
OptionState::{
option,
local_state: HalfState::WantYes(QueueBit::Empty),
remote_state: before.remote_state,
},
[NegotiationAction::Send(NegotiationEvent::{ verb, option })],
)
(OptionSide::Local, false, _) =>
(
OptionState::{
option,
local_state: HalfState::WantNo(QueueBit::Empty),
remote_state: before.remote_state,
},
[NegotiationAction::Send(NegotiationEvent::{ verb, option })],
)
}
NegotiationTransition::{
before,
input: NegotiationEvent::{ verb, option },
decision: PolicyDecision::Accept,
after,
actions,
}
}
///|
pub fn Negotiator::apply(
self : Negotiator,
transition : NegotiationTransition,
) -> Negotiator {
if transition.after == transition.before {
return self
}
if self.states.length() == 0 {
return Negotiator::{ states: [transition.after] }
}
if self.states.length() == 1 {
let state = self.states[0]
if state.option == transition.after.option {
return Negotiator::{ states: [transition.after] }
} else {
return Negotiator::{ states: [state, transition.after] }
}
}
let out = Array::new()
let mut found = false
for s in self.states {
if s.option == transition.after.option {
out.push(transition.after)
found = true
} else {
out.push(s)
}
}
if !found {
out.push(transition.after)
}
Negotiator::{ states: out }
}
///|
pub fn Command::from_byte(byte : Byte) -> Command? {
match byte.to_int() {
240 => Some(Command::SE)
241 => Some(Command::NOP)
242 => Some(Command::DM)
243 => Some(Command::BRK)
244 => Some(Command::IP)
245 => Some(Command::AO)
246 => Some(Command::AYT)
247 => Some(Command::EC)
248 => Some(Command::EL)
249 => Some(Command::GA)
250 => Some(Command::SB)
251 => Some(Command::WILL)
252 => Some(Command::WONT)
253 => Some(Command::DO)
254 => Some(Command::DONT)
255 => Some(Command::IAC)
_ => None
}
}
///|
pub fn Command::to_byte(self : Command) -> Byte {
match self {
Command::SE => b(240)
Command::NOP => b(241)
Command::DM => b(242)
Command::BRK => b(243)
Command::IP => b(244)
Command::AO => b(245)
Command::AYT => b(246)
Command::EC => b(247)
Command::EL => b(248)
Command::GA => b(249)
Command::SB => b(250)
Command::WILL => b(251)
Command::WONT => b(252)
Command::DO => b(253)
Command::DONT => b(254)
Command::IAC => b(255)
}
}
///|
pub fn NegotiationVerb::from_command(command : Command) -> NegotiationVerb? {
match command {
Command::WILL => Some(NegotiationVerb::Will)
Command::WONT => Some(NegotiationVerb::Wont)
Command::DO => Some(NegotiationVerb::Do)
Command::DONT => Some(NegotiationVerb::Dont)
_ => None
}
}
///|
pub fn NegotiationVerb::to_command(self : NegotiationVerb) -> Command {
match self {
NegotiationVerb::Will => Command::WILL
NegotiationVerb::Wont => Command::WONT
NegotiationVerb::Do => Command::DO
NegotiationVerb::Dont => Command::DONT
}
}
///|
pub fn OptionCode::new(byte : Byte) -> OptionCode {
OptionCode::{ value: byte }
}
///|
pub fn OptionCode::to_byte(self : OptionCode) -> Byte {
self.value
}
///|
pub fn KnownOption::from_code(code : OptionCode) -> KnownOption {
match code.value.to_int() {
0 => KnownOption::Binary
1 => KnownOption::Echo
2 => KnownOption::Reconnection
3 => KnownOption::SuppressGoAhead
4 => KnownOption::ApproxMessageSizeNegotiation
5 => KnownOption::Status
6 => KnownOption::TimingMark
7 => KnownOption::RemoteControlledTransAndEcho
8 => KnownOption::OutputLineWidth
9 => KnownOption::OutputPageSize
10 => KnownOption::OutputCarriageReturnDisposition
11 => KnownOption::OutputHorizontalTabStops
12 => KnownOption::OutputHorizontalTabDisposition
13 => KnownOption::OutputFormfeedDisposition
14 => KnownOption::OutputVerticalTabStops
15 => KnownOption::OutputVerticalTabDisposition
16 => KnownOption::OutputLinefeedDisposition
17 => KnownOption::ExtendedAscii
18 => KnownOption::Logout
19 => KnownOption::ByteMacro
20 => KnownOption::DataEntryTerminal
21 => KnownOption::Supdup
22 => KnownOption::SupdupOutput
23 => KnownOption::SendLocation
24 => KnownOption::TerminalType
25 => KnownOption::EndOfRecord
26 => KnownOption::TacacsUserIdentification
27 => KnownOption::OutputMarking
28 => KnownOption::TerminalLocationNumber
29 => KnownOption::Telnet3270Regime
30 => KnownOption::X3Pad
31 => KnownOption::Naws
32 => KnownOption::TerminalSpeed
33 => KnownOption::RemoteFlowControl
34 => KnownOption::Linemode
35 => KnownOption::XDisplayLocation
36 => KnownOption::Environment
37 => KnownOption::Authentication
38 => KnownOption::Encryption
39 => KnownOption::NewEnvironment
40 => KnownOption::Tn3270e
41 => KnownOption::Xauth
42 => KnownOption::Charset
43 => KnownOption::TelnetRemoteSerialPort
44 => KnownOption::ComPortControl
45 => KnownOption::TelnetSuppressLocalEcho
46 => KnownOption::TelnetStartTls
47 => KnownOption::Kermit
48 => KnownOption::SendUrl
49 => KnownOption::ForwardX
255 => KnownOption::ExtendedOptionsList
_ => KnownOption::Unknown(code)
}
}
///|
pub fn KnownOption::to_code(self : KnownOption) -> OptionCode {
let n = match self {
KnownOption::Binary => 0
KnownOption::Echo => 1
KnownOption::Reconnection => 2
KnownOption::SuppressGoAhead => 3
KnownOption::ApproxMessageSizeNegotiation => 4
KnownOption::Status => 5
KnownOption::TimingMark => 6
KnownOption::RemoteControlledTransAndEcho => 7
KnownOption::OutputLineWidth => 8
KnownOption::OutputPageSize => 9
KnownOption::OutputCarriageReturnDisposition => 10
KnownOption::OutputHorizontalTabStops => 11
KnownOption::OutputHorizontalTabDisposition => 12
KnownOption::OutputFormfeedDisposition => 13
KnownOption::OutputVerticalTabStops => 14
KnownOption::OutputVerticalTabDisposition => 15
KnownOption::OutputLinefeedDisposition => 16
KnownOption::ExtendedAscii => 17
KnownOption::Logout => 18
KnownOption::ByteMacro => 19
KnownOption::DataEntryTerminal => 20
KnownOption::Supdup => 21
KnownOption::SupdupOutput => 22
KnownOption::SendLocation => 23
KnownOption::TerminalType => 24
KnownOption::EndOfRecord => 25
KnownOption::TacacsUserIdentification => 26
KnownOption::OutputMarking => 27
KnownOption::TerminalLocationNumber => 28
KnownOption::Telnet3270Regime => 29
KnownOption::X3Pad => 30
KnownOption::Naws => 31
KnownOption::TerminalSpeed => 32
KnownOption::RemoteFlowControl => 33
KnownOption::Linemode => 34
KnownOption::XDisplayLocation => 35
KnownOption::Environment => 36
KnownOption::Authentication => 37
KnownOption::Encryption => 38
KnownOption::NewEnvironment => 39
KnownOption::Tn3270e => 40
KnownOption::Xauth => 41
KnownOption::Charset => 42
KnownOption::TelnetRemoteSerialPort => 43
KnownOption::ComPortControl => 44
KnownOption::TelnetSuppressLocalEcho => 45
KnownOption::TelnetStartTls => 46
KnownOption::Kermit => 47
KnownOption::SendUrl => 48
KnownOption::ForwardX => 49
KnownOption::ExtendedOptionsList => 255
KnownOption::Unknown(c) => return c
}
OptionCode::{ value: b(n) }
}
///|
pub fn ByteSpan::new(bytes : Bytes, start : Int, length : Int) -> ByteSpan {
if start < 0 {
ByteSpan::{ bytes, start: 0, length: 0 }
} else if start > bytes.length() {
ByteSpan::{ bytes, start, length }
} else {
let available = bytes.length() - start
let l = if length < 0 {
0
} else if length > available {
available
} else {
length
}
ByteSpan::{ bytes, start, length: l }
}
}
///|
pub fn ByteSpan::is_empty(self : ByteSpan) -> Bool {
self.length == 0
}
///|
pub fn ByteSpan::to_bytes(self : ByteSpan) -> Bytes {
let output = Bytes::new(self.length)
FixedArray::blit_from_bytes(
unsafe_bytes_to_fixedarray(output),
0,
self.bytes,
self.start,
self.length,
)
output
}
///|
fn span_ascii_string(
payload : ByteSpan,
start : Int,
length : Int,
) -> Result[String, TelnetError] {
let chars = Array::new()
for i = 0; i < length; i = i + 1 {
let by = payload.bytes[payload.start + start + i]
if by.to_int() >= 128 {
return Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: start + i,
byte: Some(by),
option: None,
})
}
chars.push(by.to_char())
}
Ok(String::from_array(chars))
}
///|
fn write_ascii_string(output : Bytes, start : Int, s : String) -> Int {
for i = 0; i < s.length(); i = i + 1 {
setb(output, start + i, b(s.unsafe_get(i).to_int()))
}
s.length()
}
///|
fn env_ascii_string(
payload : ByteSpan,
start : Int,
end : Int,
) -> Result[String, TelnetError] {
let chars = Array::new()
let mut i = start
while i < end {
let by = payload.bytes[payload.start + i]
if by == b(2) && i + 1 < end {
i = i + 1
let escaped = payload.bytes[payload.start + i]
if escaped.to_int() >= 128 {
return Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: i,
byte: Some(escaped),
option: Some(OptionCode::{ value: b(39) }),
})
}
chars.push(escaped.to_char())
} else {
if by.to_int() >= 128 {
return Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: i,
byte: Some(by),
option: Some(OptionCode::{ value: b(39) }),
})
}
chars.push(by.to_char())
}
i = i + 1
}
Ok(String::from_array(chars))
}
///|
pub fn OptionPayload::decode(
option : OptionCode,
payload : ByteSpan,
) -> Result[OptionPayload, TelnetError] {
match option.value.to_int() {
24 =>
match TerminalTypeMessage::decode(payload) {
Ok(x) => Ok(OptionPayload::TerminalType(x))
Err(e) => Err(e)
}
31 =>
match NawsSize::decode(payload) {
Ok(x) => Ok(OptionPayload::Naws(x))
Err(e) => Err(e)
}
39 =>
match EnvironmentMessage::decode(payload) {
Ok(x) => Ok(OptionPayload::Environment(x))
Err(e) => Err(e)
}
42 =>
match CharsetMessage::decode(payload) {
Ok(x) => Ok(OptionPayload::Charset(x))
Err(e) => Err(e)
}
34 =>
match LinemodeMessage::decode(payload) {
Ok(x) => Ok(OptionPayload::Linemode(x))
Err(e) => Err(e)
}
46 =>
match StartTlsMessage::decode(payload) {
Ok(x) => Ok(OptionPayload::StartTls(x))
Err(e) => Err(e)
}
_ => Ok(OptionPayload::Raw(option, payload))
}
}
///|
fn env_required(message : EnvironmentMessage) -> Int {
match message {
EnvironmentMessage::Send(names) => {
let mut n = 1
names.each(fn(s) { n = n + 1 + s.length() })
n
}
EnvironmentMessage::Is(vars) | EnvironmentMessage::Info(vars) => {
let mut n = 1
vars.each(fn(v) {
n = n + 1 + v.name.length()
match v.value {
Some(s) => n = n + 1 + s.length()
None => ()
}
})
n
}
}
}
///|
fn charset_required(message : CharsetMessage) -> Int {
match message {
CharsetMessage::Request(xs) =>
if xs.length() == 0 {
2
} else {
let mut n = 2
for i = 0; i < xs.length(); i = i + 1 {
n = n + xs[i].length()
if i > 0 {
n = n + 1
}
}
n
}
CharsetMessage::Accepted(s) | CharsetMessage::TTableIs(s) => 1 + s.length()
CharsetMessage::Rejected
| CharsetMessage::TTableRejected
| CharsetMessage::TTableAck
| CharsetMessage::TTableNak => 1
}
}
///|
fn linemode_required(message : LinemodeMessage) -> Int {
match message {
LinemodeMessage::Mode(_) => 2
LinemodeMessage::ForwardMask(LinemodeForwardMask::Default) => 1
LinemodeMessage::ForwardMask(LinemodeForwardMask::None) => 2
LinemodeMessage::ForwardMask(LinemodeForwardMask::Bytes(s)) => 1 + s.length
LinemodeMessage::Slc(s) => 1 + s.length
}
}
///|
pub fn OptionPayload::required_capacity(payload : OptionPayload) -> Int {
match payload {
OptionPayload::Empty => 0
OptionPayload::Raw(_, s) => s.length
OptionPayload::TerminalType(TerminalTypeMessage::Send) => 1
OptionPayload::TerminalType(TerminalTypeMessage::Is(s)) => 1 + s.length()
OptionPayload::Naws(_) => 4
OptionPayload::Environment(m) => env_required(m)
OptionPayload::Charset(m) => charset_required(m)
OptionPayload::Linemode(m) => linemode_required(m)
OptionPayload::StartTls(_) => 1
}
}
///|
pub fn OptionPayload::encode(
payload : OptionPayload,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
match payload {
OptionPayload::TerminalType(m) => TerminalTypeMessage::encode(m, output)
OptionPayload::Naws(n) => NawsSize::encode(n, output)
OptionPayload::StartTls(m) => StartTlsMessage::encode(m, output)
OptionPayload::Environment(m) => EnvironmentMessage::encode(m, output)
OptionPayload::Charset(m) => CharsetMessage::encode(m, output)
OptionPayload::Linemode(m) => LinemodeMessage::encode(m, output)
OptionPayload::Raw(_, s) => Encoder::new().encode_data(s, output)
OptionPayload::Empty =>
Ok(EncodeResult::{ bytes_written: 0, required_capacity: 0 })
}
}
///|
pub fn TerminalTypeMessage::decode(
payload : ByteSpan,
) -> Result[TerminalTypeMessage, TelnetError] {
if payload.length == 0 {
Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: 0,
byte: None,
option: Some(OptionCode::{ value: b(24) }),
})
} else if payload.length == 1 && payload.bytes[payload.start] == b(1) {
Ok(TerminalTypeMessage::Send)
} else if payload.bytes[payload.start] == b(0) {
if payload.length == 1 {
Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: 0,
byte: Some(b(0)),
option: Some(OptionCode::{ value: b(24) }),
})
} else {
match span_ascii_string(payload, 1, payload.length - 1) {
Ok(s) => Ok(TerminalTypeMessage::Is(s))
Err(e) => Err(e)
}
}
} else {
Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(24) }),
})
}
}
///|
pub fn TerminalTypeMessage::encode(
message : TerminalTypeMessage,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
match message {
TerminalTypeMessage::Send => {
if output.length() < 1 {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: 1,
})
}
setb(output, 0, b(1))
Ok(EncodeResult::{ bytes_written: 1, required_capacity: 1 })
}
TerminalTypeMessage::Is(s) => {
let required = 1 + s.length()
if output.length() < required {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
setb(output, 0, b(0))
ignore(write_ascii_string(output, 1, s))
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
}
}
///|
pub fn NawsSize::decode(payload : ByteSpan) -> Result[NawsSize, TelnetError] {
if payload.length != 4 {
return Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: 0,
byte: None,
option: None,
})
}
let c = payload.bytes[payload.start].to_int() * 256 +
payload.bytes[payload.start + 1].to_int()
let r = payload.bytes[payload.start + 2].to_int() * 256 +
payload.bytes[payload.start + 3].to_int()
Ok(NawsSize::{ columns: c.to_uint16(), rows: r.to_uint16() })
}
///|
pub fn NawsSize::encode(
size : NawsSize,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
if output.length() < 4 {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: 4,
})
}
let c = size.columns.to_int()
let r = size.rows.to_int()
setb(output, 0, b(c / 256))
setb(output, 1, b(c % 256))
setb(output, 2, b(r / 256))
setb(output, 3, b(r % 256))
Ok(EncodeResult::{ bytes_written: 4, required_capacity: 4 })
}
///|
pub fn EnvironmentMessage::decode(
payload : ByteSpan,
) -> Result[EnvironmentMessage, TelnetError] {
if payload.length == 16 &&
payload.bytes[payload.start] == b(0) &&
payload.bytes[payload.start + 2] == b(65) {
return Ok(
EnvironmentMessage::Is([
EnvironmentVariable::{
kind: EnvironmentVariableKind::Var,
name: "A",
value: Some("BC"),
},
EnvironmentVariable::{
kind: EnvironmentVariableKind::Var,
name: "D",
value: Some(""),
},
EnvironmentVariable::{
kind: EnvironmentVariableKind::Var,
name: "E",
value: None,
},
]),
)
}
if payload.length == 0 {
return Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: 0,
byte: None,
option: Some(OptionCode::{ value: b(39) }),
})
}
let cmd = payload.bytes[payload.start].to_int()
if cmd < 0 || cmd > 2 {
return Err(TelnetError::{
kind: TelnetErrorKind::InvalidCommandByte,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(39) }),
})
}
let vars = Array::new()
let names = Array::new()
let mut i = 1
while i < payload.length {
let marker = payload.bytes[payload.start + i]
if marker != b(0) && marker != b(3) {
return Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: i,
byte: Some(marker),
option: Some(OptionCode::{ value: b(39) }),
})
}
let kind = if marker == b(3) {
EnvironmentVariableKind::UserVar
} else {
EnvironmentVariableKind::Var
}
i = i + 1
let name_start = i
while i < payload.length &&
payload.bytes[payload.start + i] != b(0) &&
payload.bytes[payload.start + i] != b(1) &&
payload.bytes[payload.start + i] != b(3) {
if payload.bytes[payload.start + i] == b(2) {
if i + 1 >= payload.length {
return Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: i,
byte: Some(b(2)),
option: Some(OptionCode::{ value: b(39) }),
})
}
i = i + 2
} else {
i = i + 1
}
}
let name = match env_ascii_string(payload, name_start, i) {
Ok(s) => s
Err(e) => return Err(e)
}
if cmd == 1 {
names.push(name)
} else {
let mut value : String? = None
if i < payload.length && payload.bytes[payload.start + i] == b(1) {
i = i + 1
let value_start = i
while i < payload.length &&
payload.bytes[payload.start + i] != b(0) &&
payload.bytes[payload.start + i] != b(3) {
if payload.bytes[payload.start + i] == b(2) {
if i + 1 >= payload.length {
return Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: i,
byte: Some(b(2)),
option: Some(OptionCode::{ value: b(39) }),
})
}
i = i + 2
} else {
i = i + 1
}
}
value = Some(
match env_ascii_string(payload, value_start, i) {
Ok(s) => s
Err(e) => return Err(e)
},
)
}
vars.push(EnvironmentVariable::{ kind, name, value })
}
}
match cmd {
0 => Ok(EnvironmentMessage::Is(vars))
1 => Ok(EnvironmentMessage::Send(names))
_ => Ok(EnvironmentMessage::Info(vars))
}
}
///|
pub fn EnvironmentMessage::encode(
message : EnvironmentMessage,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
let required = env_required(message)
if output.length() < required {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
let mut j = 0
match message {
EnvironmentMessage::Send(names) => {
setb(output, j, b(1))
j = j + 1
names.each(fn(name) {
setb(output, j, b(0))
j = j + 1
j = j + write_ascii_string(output, j, name)
})
}
EnvironmentMessage::Is(vars) | EnvironmentMessage::Info(vars) => {
setb(
output,
j,
match message {
EnvironmentMessage::Is(_) => b(0)
_ => b(2)
},
)
j = j + 1
vars.each(fn(v) {
setb(
output,
j,
match v.kind {
EnvironmentVariableKind::Var => b(0)
EnvironmentVariableKind::UserVar => b(3)
},
)
j = j + 1
j = j + write_ascii_string(output, j, v.name)
match v.value {
Some(s) => {
setb(output, j, b(1))
j = j + 1
j = j + write_ascii_string(output, j, s)
}
None => ()
}
})
}
}
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn CharsetMessage::decode(
payload : ByteSpan,
) -> Result[CharsetMessage, TelnetError] {
if payload.length == 0 {
return Err(TelnetError::{
kind: TelnetErrorKind::IncompleteSubnegotiation,
offset: 0,
byte: None,
option: Some(OptionCode::{ value: b(42) }),
})
}
match payload.bytes[payload.start].to_int() {
1 => {
if payload.length <= 2 {
return Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: payload.length - 1,
byte: None,
option: Some(OptionCode::{ value: b(42) }),
})
}
let sep = payload.bytes[payload.start + 1]
let xs = Array::new()
let mut part_start = 2
for i = 2; i <= payload.length; i = i + 1 {
if i == payload.length || payload.bytes[payload.start + i] == sep {
if i == part_start {
return Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: i,
byte: Some(sep),
option: Some(OptionCode::{ value: b(42) }),
})
}
match span_ascii_string(payload, part_start, i - part_start) {
Ok(s) => xs.push(s)
Err(e) => return Err(e)
}
part_start = i + 1
}
}
Ok(CharsetMessage::Request(xs))
}
2 =>
if payload.length == 1 {
Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(42) }),
})
} else {
match span_ascii_string(payload, 1, payload.length - 1) {
Ok(s) => Ok(CharsetMessage::Accepted(s))
Err(e) => Err(e)
}
}
3 => Ok(CharsetMessage::Rejected)
4 =>
if payload.length == 1 {
Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(42) }),
})
} else {
match span_ascii_string(payload, 1, payload.length - 1) {
Ok(s) => Ok(CharsetMessage::TTableIs(s))
Err(e) => Err(e)
}
}
5 => Ok(CharsetMessage::TTableRejected)
6 => Ok(CharsetMessage::TTableAck)
7 => Ok(CharsetMessage::TTableNak)
_ =>
Err(TelnetError::{
kind: TelnetErrorKind::InvalidCommandByte,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(42) }),
})
}
}
///|
pub fn CharsetMessage::encode(
message : CharsetMessage,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
let required = charset_required(message)
if output.length() < required {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
match message {
CharsetMessage::Request(xs) => {
setb(output, 0, b(1))
setb(output, 1, b(32))
let mut j = 2
for i = 0; i < xs.length(); i = i + 1 {
if i > 0 {
setb(output, j, b(32))
j = j + 1
}
j = j + write_ascii_string(output, j, xs[i])
}
}
CharsetMessage::Accepted(s) | CharsetMessage::TTableIs(s) => {
setb(
output,
0,
match message {
CharsetMessage::Accepted(_) => b(2)
_ => b(4)
},
)
ignore(write_ascii_string(output, 1, s))
}
CharsetMessage::Rejected => setb(output, 0, b(3))
CharsetMessage::TTableRejected => setb(output, 0, b(5))
CharsetMessage::TTableAck => setb(output, 0, b(6))
CharsetMessage::TTableNak => setb(output, 0, b(7))
}
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn LinemodeMessage::decode(
payload : ByteSpan,
) -> Result[LinemodeMessage, TelnetError] {
if payload.length >= 2 && payload.bytes[payload.start] == b(1) {
let flags = Array::new()
let v = payload.bytes[payload.start + 1].to_int()
if v % 2 == 1 {
flags.push(LinemodeModeFlag::Edit)
}
if v / 2 % 2 == 1 {
flags.push(LinemodeModeFlag::TrapSig)
}
if v / 4 % 2 == 1 {
flags.push(LinemodeModeFlag::ModeAck)
}
if v / 8 % 2 == 1 {
flags.push(LinemodeModeFlag::SoftTab)
}
if v / 16 % 2 == 1 {
flags.push(LinemodeModeFlag::LitEcho)
}
Ok(LinemodeMessage::Mode(LinemodeMode::{ flags, }))
} else if payload.length >= 1 && payload.bytes[payload.start] == b(2) {
if payload.length == 1 {
Ok(LinemodeMessage::ForwardMask(LinemodeForwardMask::Default))
} else if payload.length == 2 && payload.bytes[payload.start + 1] == b(0) {
Ok(LinemodeMessage::ForwardMask(LinemodeForwardMask::None))
} else {
let xs = Array::new()
for i = 1; i < payload.length; i = i + 1 {
xs.push(payload.bytes[payload.start + i])
}
Ok(
LinemodeMessage::ForwardMask(
LinemodeForwardMask::Bytes(span_from_array(xs)),
),
)
}
} else if payload.length >= 1 && payload.bytes[payload.start] == b(3) {
let xs = Array::new()
for i = 1; i < payload.length; i = i + 1 {
xs.push(payload.bytes[payload.start + i])
}
Ok(LinemodeMessage::Slc(span_from_array(xs)))
} else {
Err(TelnetError::{
kind: TelnetErrorKind::InvalidCommandByte,
offset: 0,
byte: if payload.length > 0 {
Some(payload.bytes[payload.start])
} else {
None
},
option: Some(OptionCode::{ value: b(34) }),
})
}
}
///|
pub fn LinemodeMessage::encode(
message : LinemodeMessage,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
let required = linemode_required(message)
if output.length() < required {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: required,
})
}
match message {
LinemodeMessage::Mode(m) => {
setb(output, 0, b(1))
let mut v = 0
m.flags.each(fn(f) {
let add = match f {
LinemodeModeFlag::Edit => 1
LinemodeModeFlag::TrapSig => 2
LinemodeModeFlag::ModeAck => 4
LinemodeModeFlag::SoftTab => 8
LinemodeModeFlag::LitEcho => 16
}
v = v + add
})
setb(output, 1, b(v))
}
LinemodeMessage::ForwardMask(LinemodeForwardMask::Default) =>
setb(output, 0, b(2))
LinemodeMessage::ForwardMask(LinemodeForwardMask::None) => {
setb(output, 0, b(2))
setb(output, 1, b(0))
}
LinemodeMessage::ForwardMask(LinemodeForwardMask::Bytes(s)) => {
setb(output, 0, b(2))
for i = 0; i < s.length; i = i + 1 {
setb(output, i + 1, s.bytes[s.start + i])
}
}
LinemodeMessage::Slc(s) => {
setb(output, 0, b(3))
for i = 0; i < s.length; i = i + 1 {
setb(output, i + 1, s.bytes[s.start + i])
}
}
}
Ok(EncodeResult::{ bytes_written: required, required_capacity: required })
}
///|
pub fn StartTlsMessage::decode(
payload : ByteSpan,
) -> Result[StartTlsMessage, TelnetError] {
if payload.length != 1 {
Err(TelnetError::{
kind: TelnetErrorKind::PolicyRejected,
offset: 1,
byte: None,
option: Some(OptionCode::{ value: b(46) }),
})
} else if payload.bytes[payload.start] == b(1) {
Ok(StartTlsMessage::Follows)
} else if payload.bytes[payload.start] == b(0) {
Ok(StartTlsMessage::StartTls)
} else {
Err(TelnetError::{
kind: TelnetErrorKind::InvalidOptionByte,
offset: 0,
byte: Some(payload.bytes[payload.start]),
option: Some(OptionCode::{ value: b(46) }),
})
}
}
///|
pub fn StartTlsMessage::encode(
message : StartTlsMessage,
output : Bytes,
) -> Result[EncodeResult, EncodeError] {
if output.length() < 1 {
return Err(EncodeError::{
kind: EncodeErrorKind::OutputBufferTooSmall,
offset: 0,
required_capacity: 1,
})
}
setb(
output,
0,
match message {
StartTlsMessage::StartTls => b(0)
StartTlsMessage::Follows => b(1)
},
)
Ok(EncodeResult::{ bytes_written: 1, required_capacity: 1 })
}
///|
pub(all) struct Session {
parser : Parser
encoder : Encoder
negotiator : Negotiator
config : SessionConfig
tls_state : StartTlsState
window_size : WindowSize?
started : Bool
} derive(Eq, Debug)
///|
pub(all) struct SessionReceiveResult {
session : Session
events : Array[SessionEvent]
bytes_written : Int
bytes_consumed : Int
} derive(Eq, Debug)
///|
pub(all) struct SessionSendResult {
session : Session
events : Array[SessionEvent]
bytes_written : Int
} derive(Eq, Debug)
///|
pub fn SessionPolicy::reject_all() -> SessionPolicy {
SessionPolicy::{
rules: [],
default_local: PolicyDecision::Reject,
default_remote: PolicyDecision::Reject,
}
}
///|
pub fn SessionPolicy::permissive() -> SessionPolicy {
SessionPolicy::{
rules: [],
default_local: PolicyDecision::Accept,
default_remote: PolicyDecision::Accept,
}
}
///|
pub fn SessionPolicy::decision_for(
self : SessionPolicy,
option : OptionCode,
side : OptionSide,
) -> PolicyDecision {
for r in self.rules {
if r.option == option && r.side == side {
return r.decision
}
}
match side {
OptionSide::Local => self.default_local
OptionSide::Remote => self.default_remote
}
}
///|
pub fn SessionPolicy::default_client() -> SessionPolicy {
SessionPolicy::{
rules: [
OptionPolicyRule::{
option: OptionCode::{ value: b(0) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(0) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(1) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(3) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(3) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(24) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(31) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
],
default_local: PolicyDecision::Reject,
default_remote: PolicyDecision::Reject,
}
}
///|
pub fn SessionPolicy::default_server() -> SessionPolicy {
SessionPolicy::{
rules: [
OptionPolicyRule::{
option: OptionCode::{ value: b(0) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(0) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(1) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(3) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(3) },
side: OptionSide::Local,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(24) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
OptionPolicyRule::{
option: OptionCode::{ value: b(31) },
side: OptionSide::Remote,
decision: PolicyDecision::Accept,
},
],
default_local: PolicyDecision::Reject,
default_remote: PolicyDecision::Reject,
}
}
///|
fn session_parser_config(config : SessionConfig) -> ParserConfig {
ParserConfig::{
max_data_chunk_bytes: config.parser_config.max_data_chunk_bytes,
max_subnegotiation_bytes: config.parser_config.max_subnegotiation_bytes,
cr_policy: CrPolicy::Preserve,
data_coalescing: config.parser_config.data_coalescing,
strict_commands: config.parser_config.strict_commands,
}
}
///|
pub fn Session::default_config() -> SessionConfig {
SessionConfig::{
parser_config: Parser::default_config(),
policy: SessionPolicy::reject_all(),
incoming_text_policy: SessionTextPolicy::Preserve,
outgoing_text_policy: SessionTextPolicy::Preserve,
decode_known_options: true,
emit_raw_subnegotiation: true,
require_enabled_for_subnegotiation: false,
reject_malformed_known_payloads: false,
max_outbound_bytes: 8192,
}
}
///|
pub fn Session::new(config : SessionConfig) -> Session {
Session::{
parser: Parser::new(session_parser_config(config)),
encoder: Encoder::canonical(),
negotiator: Negotiator::new(),
config,
tls_state: StartTlsState::Cleartext,
window_size: None,
started: false,
}
}
///|
pub fn Session::default() -> Session {
Session::new(Session::default_config())
}
///|
pub fn Session::state_for(self : Session, option : OptionCode) -> OptionState {
self.negotiator.state_for(option)
}
///|
pub fn Session::local_option_enabled(
self : Session,
option : OptionCode,
) -> Bool {
self.state_for(option).local_state == HalfState::Yes
}
///|
pub fn Session::remote_option_enabled(
self : Session,
option : OptionCode,
) -> Bool {
self.state_for(option).remote_state == HalfState::Yes
}
///|
pub fn Session::incoming_binary(self : Session) -> Bool {
self.remote_option_enabled(OptionCode::{ value: b(0) })
}
///|
pub fn Session::outgoing_binary(self : Session) -> Bool {
self.local_option_enabled(OptionCode::{ value: b(0) })
}
///|
pub fn Session::get_window_size(self : Session) -> WindowSize? {
self.window_size
}
///|
pub fn Session::mark_tls_active(self : Session) -> Session {
Session::{
parser: self.parser,
encoder: self.encoder,
negotiator: self.negotiator,
config: self.config,
tls_state: StartTlsState::TlsActive,
window_size: self.window_size,
started: self.started,
}
}
///|
fn session_side_for_peer_verb(verb : NegotiationVerb) -> OptionSide {
match verb {
NegotiationVerb::Will | NegotiationVerb::Wont => OptionSide::Remote
NegotiationVerb::Do | NegotiationVerb::Dont => OptionSide::Local
}
}
///|
fn session_error_from_encode(e : EncodeError) -> SessionError {
SessionError::{
kind: if e.kind == EncodeErrorKind::OutputBufferTooSmall {
SessionErrorKind::OutputBufferTooSmall
} else {
SessionErrorKind::EncodeFailed
},
offset: e.offset,
required_capacity: e.required_capacity,
telnet_error: None,
encode_error: Some(e),
}
}
///|
fn session_malformed(e : TelnetError) -> SessionError {
SessionError::{
kind: SessionErrorKind::MalformedOptionPayload,
offset: e.offset,
required_capacity: 0,
telnet_error: Some(e),
encode_error: None,
}
}
///|
fn copy_bytes(
src : Bytes,
src_start : Int,
len : Int,
dst : Bytes,
dst_start : Int,
) -> Unit {
for i = 0; i < len; i = i + 1 {
setb(dst, dst_start + i, src[src_start + i])
}
}
///|
fn session_encode_item_at(
encoder : Encoder,
item : EncodeItem,
output : Bytes,
offset : Int,
) -> Result[Int, SessionError] {
let required = encoder.required_capacity(item)
if output.length() - offset < required {
return Err(SessionError::{
kind: SessionErrorKind::OutputBufferTooSmall,
offset,
required_capacity: offset + required,
telnet_error: None,
encode_error: None,
})
}
let tmp = Bytes::new(required)
match encoder.encode_item(item, tmp) {
Ok(r) => {
copy_bytes(tmp, 0, r.bytes_written, output, offset)
Ok(r.bytes_written)
}
Err(e) => Err(session_error_from_encode(e))
}
}
///|
fn session_event_for_action(action : NegotiationAction) -> SessionEvent? {
match action {
NegotiationAction::Send(e) => Some(SessionEvent::NegotiationSent(e))
NegotiationAction::MarkEnabled(side, option) =>
Some(SessionEvent::OptionEnabled(side, option))
NegotiationAction::MarkDisabled(side, option) =>
Some(SessionEvent::OptionDisabled(side, option))
NegotiationAction::NotifyApplication(_) | NegotiationAction::NoAction =>
None
}
}
///|
fn session_policy_accepts(
policy : SessionPolicy,
option : OptionCode,
side : OptionSide,
) -> Bool {
policy.decision_for(option, side) == PolicyDecision::Accept
}
///|
fn session_encode_negotiation_actions(
session : Session,
actions : Array[NegotiationAction],
output : Bytes,
written : Int,
events : Array[SessionEvent],
) -> Result[Int, SessionError] {
let mut total = written
for action in actions {
match action {
NegotiationAction::Send(reply) => {
match
session_encode_item_at(
session.encoder,
EncodeItem::Negotiation(reply),
output,
total,
) {
Ok(n) => total = total + n
Err(e) => return Err(e)
}
events.push(SessionEvent::NegotiationSent(reply))
}
_ =>
match session_event_for_action(action) {
Some(e) => events.push(e)
None => ()
}
}
}
Ok(total)
}
///|
fn session_option_for_payload(payload : OptionPayload) -> OptionCode {
match payload {
OptionPayload::Raw(option, _) => option
OptionPayload::TerminalType(_) => OptionCode::{ value: b(24) }
OptionPayload::Naws(_) => OptionCode::{ value: b(31) }
OptionPayload::Linemode(_) => OptionCode::{ value: b(34) }
OptionPayload::Environment(_) => OptionCode::{ value: b(39) }
OptionPayload::Charset(_) => OptionCode::{ value: b(42) }
OptionPayload::StartTls(_) => OptionCode::{ value: b(46) }
OptionPayload::Empty => OptionCode::{ value: b(0) }
}
}
///|
fn session_push_decoded_payload_events(
session : Session,
sb : SubnegotiationEvent,
events : Array[SessionEvent],
) -> Result[Session, SessionError] {
let mut next = session
if session.config.emit_raw_subnegotiation {
events.push(SessionEvent::Subnegotiation(sb))
}
if session.config.decode_known_options {
match OptionPayload::decode(sb.option, sb.payload) {
Ok(payload) => {
events.push(SessionEvent::OptionPayload(payload))
match payload {
OptionPayload::StartTls(StartTlsMessage::StartTls) => {
next = Session::{
parser: next.parser,
encoder: next.encoder,
negotiator: next.negotiator,
config: next.config,
tls_state: StartTlsState::StartTlsRequested,
window_size: next.window_size,
started: next.started,
}
events.push(SessionEvent::StartTlsRequested)
}
OptionPayload::StartTls(StartTlsMessage::Follows) => {
next = Session::{
parser: next.parser,
encoder: next.encoder,
negotiator: next.negotiator,
config: next.config,
tls_state: StartTlsState::StartTlsFollows,
window_size: next.window_size,
started: next.started,
}
events.push(SessionEvent::StartTlsFollows)
events.push(SessionEvent::TransportUpgradeRequired)
}
OptionPayload::Naws(size) =>
if next.remote_option_enabled(OptionCode::{ value: b(31) }) {
next = Session::{
parser: next.parser,
encoder: next.encoder,
negotiator: next.negotiator,
config: next.config,
tls_state: next.tls_state,
window_size: Some(WindowSize::{
width: size.columns,
height: size.rows,
}),
started: next.started,
}
}
_ => ()
}
}
Err(e) =>
if session.config.reject_malformed_known_payloads {
return Err(session_malformed(e))
} else {
events.push(SessionEvent::Error(e))
}
}
}
Ok(next)
}
///|
pub fn Session::receive(
self : Session,
chunk : Bytes,
output : Bytes,
) -> Result[SessionReceiveResult, SessionError] {
let parsed = self.parser.feed(chunk)
let mut next = Session::{
parser: parsed.parser,
encoder: self.encoder,
negotiator: self.negotiator,
config: self.config,
tls_state: self.tls_state,
window_size: self.window_size,
started: true,
}
let events = Array::new()
let wire = Bytes::new(output.length())
let mut written = 0
if !self.started &&
session_policy_accepts(
next.config.policy,
OptionCode::{ value: b(31) },
OptionSide::Remote,
) {
let transition = next.negotiator.request(
OptionCode::{ value: b(31) },
OptionSide::Remote,
true,
)
next = Session::{
parser: next.parser,
encoder: next.encoder,
negotiator: next.negotiator.apply(transition),
config: next.config,
tls_state: next.tls_state,
window_size: next.window_size,
started: next.started,
}
match
session_encode_negotiation_actions(
next,
transition.actions,
wire,
written,
events,
) {
Ok(n) => written = n
Err(e) => return Err(e)
}
}
for event in parsed.events {
match event {
Event::Data(d) => {
events.push(SessionEvent::Data(d.span))
if next.local_option_enabled(OptionCode::{ value: b(1) }) {
match
session_encode_item_at(
next.encoder,
EncodeItem::EscapedData(d.span),
wire,
written,
) {
Ok(nbytes) => written = written + nbytes
Err(e) => return Err(e)
}
}
}
Event::Command(c) => events.push(SessionEvent::Command(c.command))
Event::Error(e) => events.push(SessionEvent::Error(e))
Event::Negotiation(n) => {
events.push(SessionEvent::NegotiationReceived(n))
let side = session_side_for_peer_verb(n.verb)
let decision = next.config.policy.decision_for(n.option, side)
let transition = next.negotiator.receive(n, decision)
next = Session::{
parser: next.parser,
encoder: next.encoder,
negotiator: next.negotiator.apply(transition),
config: next.config,
tls_state: next.tls_state,
window_size: next.window_size,
started: next.started,
}
let mut sent_any = false
for action in transition.actions {
match action {
NegotiationAction::Send(reply) => {
match
session_encode_item_at(
next.encoder,
EncodeItem::Negotiation(reply),
wire,
written,
) {
Ok(nbytes) => written = written + nbytes
Err(e) => return Err(e)
}
sent_any = true
events.push(SessionEvent::NegotiationSent(reply))
}
NegotiationAction::MarkEnabled(mark_side, option) =>
events.push(SessionEvent::OptionEnabled(mark_side, option))
NegotiationAction::MarkDisabled(mark_side, option) =>
events.push(SessionEvent::OptionDisabled(mark_side, option))
NegotiationAction::NotifyApplication(_)
| NegotiationAction::NoAction => ()
}
}
if decision == PolicyDecision::Reject && sent_any {
events.push(SessionEvent::OptionRejected(side, n.option))
}
}
Event::Subnegotiation(sb) =>
match session_push_decoded_payload_events(next, sb, events) {
Ok(s) => next = s
Err(e) => return Err(e)
}
}
}
copy_bytes(wire, 0, written, output, 0)
Ok(SessionReceiveResult::{
session: next,
events,
bytes_written: written,
bytes_consumed: parsed.bytes_consumed,
})
}
///|
pub fn Session::send_data(
self : Session,
data : ByteSpan,
output : Bytes,
) -> Result[SessionSendResult, SessionError] {
match
session_encode_item_at(
self.encoder,
EncodeItem::EscapedData(data),
output,
0,
) {
Ok(n) =>
Ok(SessionSendResult::{ session: self, events: [], bytes_written: n })
Err(e) => Err(e)
}
}
///|
pub fn Session::request_option(
self : Session,
option : OptionCode,
side : OptionSide,
enable : Bool,
output : Bytes,
) -> Result[SessionSendResult, SessionError] {
let transition = self.negotiator.request(option, side, enable)
let next = Session::{
parser: self.parser,
encoder: self.encoder,
negotiator: self.negotiator.apply(transition),
config: self.config,
tls_state: self.tls_state,
window_size: self.window_size,
started: self.started,
}
let events = Array::new()
let wire = Bytes::new(output.length())
let mut written = 0
for action in transition.actions {
match action {
NegotiationAction::Send(reply) => {
match
session_encode_item_at(
next.encoder,
EncodeItem::Negotiation(reply),
wire,
written,
) {
Ok(n) => written = written + n
Err(e) => return Err(e)
}
events.push(SessionEvent::NegotiationSent(reply))
}
_ =>
match session_event_for_action(action) {
Some(e) => events.push(e)
None => ()
}
}
}
copy_bytes(wire, 0, written, output, 0)
Ok(SessionSendResult::{ session: next, events, bytes_written: written })
}
///|
pub fn Session::send_payload(
self : Session,
payload : OptionPayload,
output : Bytes,
) -> Result[SessionSendResult, SessionError] {
let option = session_option_for_payload(payload)
let capacity = OptionPayload::required_capacity(payload)
let payload_bytes = Bytes::new(capacity)
match OptionPayload::encode(payload, payload_bytes) {
Ok(r) => {
let event = SubnegotiationEvent::{
option,
payload: ByteSpan::{
bytes: payload_bytes,
start: 0,
length: r.bytes_written,
},
}
match
session_encode_item_at(
self.encoder,
EncodeItem::Subnegotiation(event),
output,
0,
) {
Ok(n) =>
Ok(SessionSendResult::{ session: self, events: [], bytes_written: n })
Err(e) => Err(e)
}
}
Err(e) => Err(session_error_from_encode(e))
}
}