diff --git a/api/envoy/api/v2/core/protocol.proto b/api/envoy/api/v2/core/protocol.proto index 53b6ae8746794..b7acac7375080 100644 --- a/api/envoy/api/v2/core/protocol.proto +++ b/api/envoy/api/v2/core/protocol.proto @@ -59,6 +59,12 @@ message HttpProtocolOptions { // maximum number of request headers allowed is 100. Requests that exceed this limit will receive // a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. google.protobuf.UInt32Value max_headers_count = 2 [(validate.rules).uint32 = {gte: 1}]; + + // Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + // reset independent of any other timeouts. If not specified, this value is not set. + // The current implementation implements this timeout on downstream connections only. + // [#comment:TODO(shikugawa): add this functionality to upstream.] + google.protobuf.Duration max_stream_duration = 4; } // [#next-free-field: 6] diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index b3263a1fdde6e..b7ad4b7d8bd64 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -67,6 +67,12 @@ message HttpProtocolOptions { // maximum number of request headers allowed is 100. Requests that exceed this limit will receive // a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. google.protobuf.UInt32Value max_headers_count = 2 [(validate.rules).uint32 = {gte: 1}]; + + // Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + // reset independent of any other timeouts. If not specified, this value is not set. + // The current implementation implements this timeout on downstream connections only. + // [#comment:TODO(shikugawa): add this functionality to upstream.] + google.protobuf.Duration max_stream_duration = 4; } // [#next-free-field: 6] diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index 6d46fb937bd0b..d58c4bc359b86 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -56,6 +56,7 @@ statistics: downstream_rq_ws_on_non_ws_route, Counter, Total WebSocket upgrade requests rejected by non WebSocket routes downstream_rq_time, Histogram, Total time for request and response (milliseconds) downstream_rq_idle_timeout, Counter, Total requests closed due to idle timeout + downstream_rq_max_duration_reached, Counter, Total requests closed due to max duration reached downstream_rq_timeout, Counter, Total requests closed due to a timeout on the request path downstream_rq_overload_close, Counter, Total requests closed due to Envoy overload rs_too_large, Counter, Total response errors due to buffering an overly large body diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index 33af5873b8885..65f3e9efce2c0 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -53,6 +53,16 @@ context request/stream is interchangeable. is the amount of time that the connection manager will allow a stream to exist with no upstream or downstream activity. The default stream idle timeout is *5 minutes*. This timeout is strongly recommended for streaming APIs (requests or responses that never end). +* The HTTP protocol :ref:`max_stream_duration ` + is defined in a generic message used by the HTTP connection manager. The max stream duration is the + maximum time that a stream's lifetime will span. You can use this functionality when you want to reset + HTTP request/response streams periodically. You can't use :ref:`request_timeout + ` + in this situation because this timer will be disarmed if a response header is received on the request/response streams. + + .. attention:: + + The current implementation implements this timeout on downstream connections only. Route timeouts ^^^^^^^^^^^^^^ diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 29f51dd27b668..0ec169332ad51 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,6 +24,7 @@ Version history * http: fixing a bug in HTTP/1.0 responses where Connection: keep-alive was not appended for connections which were kept alive. * http: fixed a bug that could send extra METADATA frames and underflow memory when encoding METADATA frames on a connection that was dispatching data. * http: connection header sanitizing has been modified to always sanitize if there is no upgrade, including when an h2c upgrade attempt has been removed. +* http: added :ref:`max_stream_duration ` to specify the duration of existing streams. See :ref:`connection and stream timeouts `. * listener filters: listener filter extensions use the "envoy.filters.listener" name space. A mapping of extension names is available in the :ref:`deprecated ` documentation. * listeners: fixed issue where :ref:`TLS inspector listener filter ` could have been bypassed by a client using only TLS 1.3. diff --git a/generated_api_shadow/envoy/api/v2/core/protocol.proto b/generated_api_shadow/envoy/api/v2/core/protocol.proto index 53b6ae8746794..b7acac7375080 100644 --- a/generated_api_shadow/envoy/api/v2/core/protocol.proto +++ b/generated_api_shadow/envoy/api/v2/core/protocol.proto @@ -59,6 +59,12 @@ message HttpProtocolOptions { // maximum number of request headers allowed is 100. Requests that exceed this limit will receive // a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. google.protobuf.UInt32Value max_headers_count = 2 [(validate.rules).uint32 = {gte: 1}]; + + // Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + // reset independent of any other timeouts. If not specified, this value is not set. + // The current implementation implements this timeout on downstream connections only. + // [#comment:TODO(shikugawa): add this functionality to upstream.] + google.protobuf.Duration max_stream_duration = 4; } // [#next-free-field: 6] diff --git a/generated_api_shadow/envoy/config/core/v3/protocol.proto b/generated_api_shadow/envoy/config/core/v3/protocol.proto index b3263a1fdde6e..b7ad4b7d8bd64 100644 --- a/generated_api_shadow/envoy/config/core/v3/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v3/protocol.proto @@ -67,6 +67,12 @@ message HttpProtocolOptions { // maximum number of request headers allowed is 100. Requests that exceed this limit will receive // a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. google.protobuf.UInt32Value max_headers_count = 2 [(validate.rules).uint32 = {gte: 1}]; + + // Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + // reset independent of any other timeouts. If not specified, this value is not set. + // The current implementation implements this timeout on downstream connections only. + // [#comment:TODO(shikugawa): add this functionality to upstream.] + google.protobuf.Duration max_stream_duration = 4; } // [#next-free-field: 6] diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 0e16332c73d2e..3264e57dccc53 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -59,6 +59,7 @@ namespace Http { COUNTER(downstream_rq_too_large) \ COUNTER(downstream_rq_total) \ COUNTER(downstream_rq_tx_reset) \ + COUNTER(downstream_rq_max_duration_reached) \ COUNTER(downstream_rq_ws_on_non_ws_route) \ COUNTER(rs_too_large) \ GAUGE(downstream_cx_active, Accumulate) \ @@ -282,6 +283,11 @@ class ConnectionManagerConfig { */ virtual std::chrono::milliseconds delayedCloseTimeout() const PURE; + /** + * @return maximum duration time to keep alive stream + */ + virtual absl::optional maxStreamDuration() const PURE; + /** * @return Router::RouteConfigProvider* the configuration provider used to acquire a route * config for each request flow. Pointer ownership is _not_ transferred to the caller of diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 010c3fb9b9fa4..438c309aaa298 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -230,6 +230,10 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { } void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { + if (stream.max_stream_duration_timer_) { + stream.max_stream_duration_timer_->disableTimer(); + stream.max_stream_duration_timer_ = nullptr; + } if (stream.stream_idle_timer_ != nullptr) { stream.stream_idle_timer_->disableTimer(); stream.stream_idle_timer_ = nullptr; @@ -602,6 +606,15 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect request_timer_->enableTimer(request_timeout_ms_, this); } + const auto max_stream_duration = connection_manager_.config_.maxStreamDuration(); + if (max_stream_duration.has_value() && max_stream_duration.value().count()) { + max_stream_duration_timer_ = + connection_manager.read_callbacks_->connection().dispatcher().createTimer( + [this]() -> void { onStreamMaxDurationReached(); }); + max_stream_duration_timer_->enableTimer(connection_manager_.config_.maxStreamDuration().value(), + this); + } + stream_info_.setRequestedServerName( connection_manager_.read_callbacks_->connection().requestedServerName()); } @@ -673,6 +686,12 @@ void ConnectionManagerImpl::ActiveStream::onRequestTimeout() { absl::nullopt, StreamInfo::ResponseCodeDetails::get().RequestOverallTimeout); } +void ConnectionManagerImpl::ActiveStream::onStreamMaxDurationReached() { + ENVOY_STREAM_LOG(debug, "Stream max duration time reached", *this); + connection_manager_.stats_.named_.downstream_rq_max_duration_reached_.inc(); + connection_manager_.doEndStream(*this); +} + void ConnectionManagerImpl::ActiveStream::addStreamDecoderFilterWorker( StreamDecoderFilterSharedPtr filter, bool dual_filter) { ActiveStreamDecoderFilterPtr wrapper(new ActiveStreamDecoderFilter(*this, filter, dual_filter)); diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 7b17dd46625ac..bb3292dc0b876 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -659,7 +659,8 @@ class ConnectionManagerImpl : Logger::Loggable, void resetIdleTimer(); // Per-stream request timeout callback void onRequestTimeout(); - + // Per-stream alive duration reached. + void onStreamMaxDurationReached(); bool hasCachedRoute() { return cached_route_.has_value() && cached_route_.value(); } friend std::ostream& operator<<(std::ostream& os, const ActiveStream& s) { @@ -702,6 +703,8 @@ class ConnectionManagerImpl : Logger::Loggable, Event::TimerPtr stream_idle_timer_; // Per-stream request timeout. Event::TimerPtr request_timer_; + // Per-stream alive duration. + Event::TimerPtr max_stream_duration_timer_; std::chrono::milliseconds idle_timeout_ms_{}; State state_; StreamInfo::StreamInfoImpl stream_info_; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 830a18c655ab7..02c7e4468f4e9 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -185,6 +185,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( idle_timeout_(PROTOBUF_GET_OPTIONAL_MS(config.common_http_protocol_options(), idle_timeout)), max_connection_duration_( PROTOBUF_GET_OPTIONAL_MS(config.common_http_protocol_options(), max_connection_duration)), + max_stream_duration_( + PROTOBUF_GET_OPTIONAL_MS(config.common_http_protocol_options(), max_stream_duration)), stream_idle_timeout_( PROTOBUF_GET_MS_OR_DEFAULT(config, stream_idle_timeout, StreamIdleTimeoutMs)), request_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, request_timeout, RequestTimeoutMs)), diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 0ebd0ee920254..6bf6f4b44f5ae 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -116,6 +116,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + absl::optional maxStreamDuration() const override { + return max_stream_duration_; + } Router::RouteConfigProvider* routeConfigProvider() override { return route_config_provider_.get(); } @@ -198,6 +201,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, const uint32_t max_request_headers_count_; absl::optional idle_timeout_; absl::optional max_connection_duration_; + absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_; std::chrono::milliseconds request_timeout_; Router::RouteConfigProviderSharedPtr route_config_provider_; diff --git a/source/server/http/admin.h b/source/server/http/admin.h index e24bb8f2179ef..abbac234f3c44 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -124,6 +124,9 @@ class AdminImpl : public Admin, std::chrono::milliseconds streamIdleTimeout() const override { return {}; } std::chrono::milliseconds requestTimeout() const override { return {}; } std::chrono::milliseconds delayedCloseTimeout() const override { return {}; } + absl::optional maxStreamDuration() const override { + return max_stream_duration_; + } Router::RouteConfigProvider* routeConfigProvider() override { return &route_config_provider_; } Config::ConfigProvider* scopedRouteConfigProvider() override { return &scoped_route_config_provider_; @@ -457,6 +460,7 @@ class AdminImpl : public Admin, const uint32_t max_request_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; absl::optional idle_timeout_; absl::optional max_connection_duration_; + absl::optional max_stream_duration_; absl::optional user_agent_; Http::SlowDateProviderImpl date_provider_; std::vector set_current_client_cert_details_; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 44cb4756a1377..a4f7e7e867b5e 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -104,6 +104,9 @@ class FuzzConfig : public ConnectionManagerConfig { absl::optional maxConnectionDuration() const override { return max_connection_duration_; } + absl::optional maxStreamDuration() const override { + return max_stream_duration_; + } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } @@ -170,6 +173,7 @@ class FuzzConfig : public ConnectionManagerConfig { uint32_t max_request_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; absl::optional idle_timeout_; absl::optional max_connection_duration_; + absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 2d24670d6a307..c7890bcedbb53 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -301,6 +301,9 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } + absl::optional maxStreamDuration() const override { + return max_stream_duration_; + } bool use_srds_{}; Router::RouteConfigProvider* routeConfigProvider() override { if (use_srds_) { @@ -375,6 +378,7 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; + absl::optional max_stream_duration_{}; NiceMock random_; NiceMock local_info_; NiceMock factory_context_; @@ -2397,6 +2401,74 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin EXPECT_EQ(0U, stats_.named_.downstream_rq_timeout_.value()); } +TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationDisabledIfSetToZero) { + max_stream_duration_ = std::chrono::milliseconds(0); + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, createTimer_).Times(0); + conn_manager_->newStream(response_encoder_); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); // kick off request +} + +TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationValidlyConfigured) { + max_stream_duration_ = std::chrono::milliseconds(10); + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { + Event::MockTimer* duration_timer = setUpTimer(); + + EXPECT_CALL(*duration_timer, enableTimer(max_stream_duration_.value(), _)); + conn_manager_->newStream(response_encoder_); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); // kick off request +} + +TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationCallbackResetStream) { + max_stream_duration_ = std::chrono::milliseconds(10); + setup(false, ""); + Event::MockTimer* duration_timer = setUpTimer(); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*duration_timer, enableTimer(max_stream_duration_.value(), _)).Times(1); + conn_manager_->newStream(response_encoder_); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); // kick off request + + EXPECT_CALL(*duration_timer, disableTimer()); + duration_timer->invokeCallback(); + + EXPECT_EQ(1U, stats_.named_.downstream_rq_max_duration_reached_.value()); + EXPECT_EQ(1U, stats_.named_.downstream_rq_rx_reset_.value()); +} + +TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationCallbackNotCalledIfResetStreamValidly) { + max_stream_duration_ = std::chrono::milliseconds(5000); + setup(false, ""); + Event::MockTimer* duration_timer = setUpTimer(); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*duration_timer, enableTimer(max_stream_duration_.value(), _)).Times(1); + conn_manager_->newStream(response_encoder_); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); // kick off request + + EXPECT_CALL(*duration_timer, disableTimer()); + conn_manager_->onEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(0U, stats_.named_.downstream_rq_max_duration_reached_.value()); + EXPECT_EQ(1U, stats_.named_.downstream_rq_rx_reset_.value()); +} + TEST_F(HttpConnectionManagerImplTest, RejectWebSocketOnNonWebSocketRoute) { setup(false, ""); RequestDecoder* decoder = nullptr; diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index ffa321d48170d..b2301a03648a0 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -65,6 +65,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(absl::optional, idleTimeout, (), (const)); MOCK_METHOD(bool, isRoutable, (), (const)); MOCK_METHOD(absl::optional, maxConnectionDuration, (), (const)); + MOCK_METHOD(absl::optional, maxStreamDuration, (), (const)); MOCK_METHOD(std::chrono::milliseconds, streamIdleTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, requestTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, delayedCloseTimeout, (), (const)); diff --git a/test/config/utility.cc b/test/config/utility.cc index 10d0249336511..c22b07f52f62d 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -611,6 +611,16 @@ void ConfigHelper::setDownstreamMaxConnectionDuration(std::chrono::milliseconds }); } +void ConfigHelper::setDownstreamMaxStreamDuration(std::chrono::milliseconds timeout) { + addConfigModifier( + [timeout]( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_common_http_protocol_options()->mutable_max_stream_duration()->MergeFrom( + ProtobufUtil::TimeUtil::MillisecondsToDuration(timeout.count())); + }); +} + void ConfigHelper::setConnectTimeout(std::chrono::milliseconds timeout) { RELEASE_ASSERT(!finalized_, ""); diff --git a/test/config/utility.h b/test/config/utility.h index e476019198668..785f3a050fedb 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -123,6 +123,9 @@ class ConfigHelper { // Set the max connection duration for downstream connections through the HttpConnectionManager. void setDownstreamMaxConnectionDuration(std::chrono::milliseconds max_connection_duration); + // Set the max stream duration for downstream connections through the HttpConnectionManager. + void setDownstreamMaxStreamDuration(std::chrono::milliseconds max_stream_duration); + // Set the connect timeout on upstream connections. void setConnectTimeout(std::chrono::milliseconds timeout); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 905e84a94a2af..c43c96e537c52 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1493,6 +1493,25 @@ TEST_P(ProtocolIntegrationTest, ConnDurationTimeoutNoHttpRequest) { test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); } +TEST_P(DownstreamProtocolIntegrationTest, BasicMaxStreamTimeout) { + config_helper_.setDownstreamMaxStreamDuration(std::chrono::milliseconds(500)); + initialize(); + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = codec_client_->startRequest(default_request_headers_); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); + response->waitForReset(); + EXPECT_FALSE(response->complete()); +} + // Make sure that invalid authority headers get blocked at or before the HCM. TEST_P(DownstreamProtocolIntegrationTest, InvalidAuthority) { initialize();