use codec::Codec;
use frame::{self, Reason, StreamId};
use bytes::Buf;
use futures::{Async, Poll};
use std::io;
use tokio_io::AsyncWrite;
#[derive(Debug)]
pub(super) struct GoAway {
close_now: bool,
going_away: Option<GoingAway>,
pending: Option<frame::GoAway>,
}
#[derive(Debug)]
struct GoingAway {
last_processed_id: StreamId,
reason: Reason,
}
impl GoAway {
pub fn new() -> Self {
GoAway {
close_now: false,
going_away: None,
pending: None,
}
}
pub fn go_away(&mut self, f: frame::GoAway) {
if let Some(ref going_away) = self.going_away {
assert!(
f.last_stream_id() <= going_away.last_processed_id,
"GOAWAY stream IDs shouldn't be higher; \
last_processed_id = {:?}, f.last_stream_id() = {:?}",
going_away.last_processed_id,
f.last_stream_id(),
);
}
self.going_away = Some(GoingAway {
last_processed_id: f.last_stream_id(),
reason: f.reason(),
});
self.pending = Some(f);
}
pub fn go_away_now(&mut self, f: frame::GoAway) {
self.close_now = true;
if let Some(ref going_away) = self.going_away {
if going_away.last_processed_id == f.last_stream_id()
&& going_away.reason == f.reason() {
return;
}
}
self.go_away(f);
}
pub fn is_going_away(&self) -> bool {
self.going_away.is_some()
}
pub fn going_away_reason(&self) -> Option<Reason> {
self.going_away
.as_ref()
.map(|g| g.reason)
}
pub fn should_close_now(&self) -> bool {
self.pending.is_none() && self.close_now
}
pub fn should_close_on_idle(&self) -> bool {
!self.close_now && self.going_away
.as_ref()
.map(|g| g.last_processed_id != StreamId::MAX)
.unwrap_or(false)
}
pub fn send_pending_go_away<T, B>(&mut self, dst: &mut Codec<T, B>) -> Poll<Option<Reason>, io::Error>
where
T: AsyncWrite,
B: Buf,
{
if let Some(frame) = self.pending.take() {
if !dst.poll_ready()?.is_ready() {
self.pending = Some(frame);
return Ok(Async::NotReady);
}
let reason = frame.reason();
dst.buffer(frame.into())
.ok()
.expect("invalid GOAWAY frame");
return Ok(Async::Ready(Some(reason)));
} else if self.should_close_now() {
return Ok(Async::Ready(self.going_away_reason()));
}
Ok(Async::Ready(None))
}
}