blob: 6ee228456154728e5f9a869ab48ac4ed961f885e [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library http2.src.settings;
import 'dart:async';
import '../error_handler.dart';
import '../frames/frames.dart';
import '../hpack/hpack.dart';
import '../sync_errors.dart';
/// The settings a remote peer can choose to set.
class ActiveSettings {
/// Allows the sender to inform the remote endpoint of the maximum size of the
/// header compression table used to decode header blocks, in octets. The
/// encoder can select any size equal to or less than this value by using
/// signaling specific to the header compression format inside a header block.
/// The initial value is 4,096 octets.
int headerTableSize;
/// This setting can be use to disable server push (Section 8.2). An endpoint
/// MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a
/// value of 0. An endpoint that has both set this parameter to 0 and had it
/// acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a
/// connection error (Section 5.4.1) of type PROTOCOL_ERROR.
///
/// The initial value is 1, which indicates that server push is permitted.
/// Any value other than 0 or 1 MUST be treated as a connection error
/// (Section 5.4.1) of type PROTOCOL_ERROR.
bool enablePush;
/// Indicates the maximum number of concurrent streams that the sender will
/// allow. This limit is directional: it applies to the number of streams that
/// the sender permits the receiver to create. Initially there is no limit to
/// this value. It is recommended that this value be no smaller than 100, so
/// as to not unnecessarily limit parallelism.
///
/// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as
/// special by endpoints. A zero value does prevent the creation of new
/// streams, however this can also happen for any limit that is exhausted with
/// active streams. Servers SHOULD only set a zero value for short durations;
/// if a server does not wish to accept requests, closing the connection is
/// more appropriate.
int maxConcurrentStreams;
/// Indicates the sender's initial window size (in octets) for stream level
/// flow control. The initial value is 2^16-1 (65,535) octets.
///
/// This setting affects the window size of all streams, including existing
/// streams, see Section 6.9.2.
/// Values above the maximum flow control window size of 231-1 MUST be treated
/// as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR.
int initialWindowSize;
/// Indicates the size of the largest frame payload that the sender is willing
/// to receive, in octets.
///
/// The initial value is 2^14 (16,384) octets. The value advertised by an
/// endpoint MUST be between this initial value and the maximum allowed frame
/// size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range
/// MUST be treated as a connection error (Section 5.4.1) of type
/// PROTOCOL_ERROR.
int maxFrameSize;
/// This advisory setting informs a peer of the maximum size of header list
/// that the sender is prepared to accept, in octets. The value is based on
/// the uncompressed size of header fields, including the length of the name
/// and value in octets plus an overhead of 32 octets for each header field.
///
/// For any given request, a lower limit than what is advertised MAY be
/// enforced. The initial value of this setting is unlimited.
int maxHeaderListSize;
ActiveSettings(
{this.headerTableSize: 4096,
this.enablePush: true,
this.maxConcurrentStreams,
this.initialWindowSize: (1 << 16) - 1,
this.maxFrameSize: (1 << 14),
this.maxHeaderListSize});
}
/// Handles remote and local connection [Setting]s.
///
/// Incoming [SettingsFrame]s will be handled here to update the peer settings.
/// Changes to [_toBeAcknowledgedSettings] can be made, the peer will then be
/// notified of the setting changes it should use.
class SettingsHandler extends Object with TerminatableMixin {
/// Certain settings changes can change the maximum allowed dynamic table
/// size used by the HPack encoder.
final HPackEncoder _hpackEncoder;
final FrameWriter _frameWriter;
/// A list of outstanding setting changes.
final List<List<Setting>> _toBeAcknowledgedSettings = [];
/// A list of completers for outstanding setting changes.
final List<Completer> _toBeAcknowledgedCompleters = [];
/// The local settings, which the remote side ACKed to obey.
final ActiveSettings _acknowledgedSettings;
/// The peer settings, which we ACKed and are obeying.
final ActiveSettings _peerSettings;
final _onInitialWindowSizeChangeController =
new StreamController<int>.broadcast(sync: true);
/// Events are fired when a SettingsFrame changes the initial size
/// of stream windows.
Stream<int> get onInitialWindowSizeChange =>
_onInitialWindowSizeChangeController.stream;
SettingsHandler(this._hpackEncoder, this._frameWriter,
this._acknowledgedSettings, this._peerSettings);
/// The settings for this endpoint of the connection which the remote peer
/// has ACKed and uses.
ActiveSettings get acknowledgedSettings => _acknowledgedSettings;
/// The settings for the remote endpoint of the connection which this
/// endpoint should use.
ActiveSettings get peerSettings => _peerSettings;
/// Handles an incoming [SettingsFrame] which can be an ACK or a settings
/// change.
void handleSettingsFrame(SettingsFrame frame) {
ensureNotTerminatedSync(() {
assert(frame.header.streamId == 0);
if (frame.hasAckFlag) {
assert(frame.header.length == 0);
if (_toBeAcknowledgedSettings.isEmpty) {
// NOTE: The specification does not say anything about ACKed settings
// which were never sent to the other side. We consider this definitly
// an error.
throw new ProtocolException(
'Received an acknowledged settings frame which did not have a '
'outstanding settings request.');
}
List<Setting> settingChanges = _toBeAcknowledgedSettings.removeAt(0);
Completer completer = _toBeAcknowledgedCompleters.removeAt(0);
_modifySettings(_acknowledgedSettings, settingChanges, false);
completer.complete();
} else {
_modifySettings(_peerSettings, frame.settings, true);
_frameWriter.writeSettingsAckFrame();
}
});
}
void onTerminated(error) {
_toBeAcknowledgedSettings.clear();
_toBeAcknowledgedCompleters
.forEach((Completer c) => c.completeError(error));
}
Future changeSettings(List<Setting> changes) {
return ensureNotTerminatedAsync(() {
// TODO: Have a timeout: When ACK doesn't get back in a reasonable time
// frame we should quit with ErrorCode.SETTINGS_TIMEOUT.
var completer = new Completer();
_toBeAcknowledgedSettings.add(changes);
_toBeAcknowledgedCompleters.add(completer);
_frameWriter.writeSettingsFrame(changes);
return completer.future;
});
}
void _modifySettings(
ActiveSettings base, List<Setting> changes, bool peerSettings) {
for (var setting in changes) {
switch (setting.identifier) {
case Setting.SETTINGS_ENABLE_PUSH:
if (setting.value == 0) {
base.enablePush = false;
} else if (setting.value == 1) {
base.enablePush = true;
} else {
throw new ProtocolException(
'The push setting can be only set to 0 or 1.');
}
break;
case Setting.SETTINGS_HEADER_TABLE_SIZE:
base.headerTableSize = setting.value;
if (peerSettings) {
_hpackEncoder.updateMaxSendingHeaderTableSize(base.headerTableSize);
}
break;
case Setting.SETTINGS_MAX_HEADER_LIST_SIZE:
// TODO: Propagate this signal to the HPackContext.
base.maxHeaderListSize = setting.value;
break;
case Setting.SETTINGS_MAX_CONCURRENT_STREAMS:
// NOTE: We will not force closing of existing streams if the limit is
// lower than the current number of open streams. But we will prevent
// new streams from being created if the number of existing streams
// is above this limit.
base.maxConcurrentStreams = setting.value;
break;
case Setting.SETTINGS_INITIAL_WINDOW_SIZE:
if (setting.value < (1 << 31)) {
int difference = setting.value - base.initialWindowSize;
_onInitialWindowSizeChangeController.add(difference);
base.initialWindowSize = setting.value;
} else {
throw new FlowControlException('Invalid initial window size.');
}
break;
default:
// Spec says to ignore unknown settings.
break;
}
}
}
}