public async Task <IConnection> CompletePreambleAsync(TimeSpan timeout) { var timeoutHelper = new TimeoutHelper(timeout); var parent = this; if (!transportSettings.MessageEncoderFactory.Encoder.IsContentTypeSupported(Decoder.ContentType)) { SendFault(FramingEncodingString.ContentTypeInvalidFault, ref timeoutHelper); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.Format( SR.ContentTypeMismatch, Decoder.ContentType, parent.transportSettings.MessageEncoderFactory.Encoder.ContentType))); } IStreamUpgradeChannelBindingProvider channelBindingProvider = null; StreamUpgradeAcceptor upgradeAcceptor = null; if (transportSettings.Upgrade != null) { channelBindingProvider = transportSettings.Upgrade.GetProperty <IStreamUpgradeChannelBindingProvider>(); upgradeAcceptor = transportSettings.Upgrade.CreateUpgradeAcceptor(); } var currentConnection = Connection; UpgradeState upgradeState = UpgradeState.None; while (true) { if (size == 0 && CanReadAndDecode(upgradeState)) { size = await currentConnection.ReadAsync(0, connectionBuffer.Length, timeoutHelper.RemainingTime()); if (size == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(Decoder.CreatePrematureEOFException()); } } while (true) { if (CanReadAndDecode(upgradeState)) { int bytesRead = Decoder.Decode(connectionBuffer, offset, size); if (bytesRead > 0) { offset += bytesRead; size -= bytesRead; } } switch (Decoder.CurrentState) { case ServerSingletonDecoder.State.UpgradeRequest: switch (upgradeState) { case UpgradeState.None: //change the state so that we don't read/decode until it is safe ChangeUpgradeState(ref upgradeState, UpgradeState.VerifyingUpgradeRequest); break; case UpgradeState.VerifyingUpgradeRequest: if (upgradeAcceptor == null) { SendFault(FramingEncodingString.UpgradeInvalidFault, ref timeoutHelper); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new ProtocolException(SR.Format(SR.UpgradeRequestToNonupgradableService, Decoder.Upgrade))); } if (!upgradeAcceptor.CanUpgrade(Decoder.Upgrade)) { SendFault(FramingEncodingString.UpgradeInvalidFault, ref timeoutHelper); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.Format(SR.UpgradeProtocolNotSupported, Decoder.Upgrade))); } ChangeUpgradeState(ref upgradeState, UpgradeState.WritingUpgradeAck); // accept upgrade await currentConnection.WriteAsync(ServerSingletonEncoder.UpgradeResponseBytes, 0, ServerSingletonEncoder.UpgradeResponseBytes.Length, true, timeoutHelper.RemainingTime()); ChangeUpgradeState(ref upgradeState, UpgradeState.UpgradeAckSent); break; case UpgradeState.UpgradeAckSent: IConnection connectionToUpgrade = currentConnection; if (size > 0) { connectionToUpgrade = new PreReadConnection(connectionToUpgrade, connectionBuffer, offset, size); } ChangeUpgradeState(ref upgradeState, UpgradeState.BeginUpgrade); break; case UpgradeState.BeginUpgrade: try { currentConnection = await InitialServerConnectionReader.UpgradeConnectionAsync(currentConnection, upgradeAcceptor, transportSettings); connectionBuffer = currentConnection.AsyncReadBuffer; if (channelBindingProvider != null && channelBindingProvider.IsChannelBindingSupportEnabled && channelBindingToken == null) //first one wins in the case of multiple upgrades. { channelBindingToken = channelBindingProvider.GetChannelBinding(upgradeAcceptor, ChannelBindingKind.Endpoint); } ChangeUpgradeState(ref upgradeState, UpgradeState.EndUpgrade); ChangeUpgradeState(ref upgradeState, UpgradeState.UpgradeComplete); } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } WriteAuditFailure(upgradeAcceptor as StreamSecurityUpgradeAcceptor, exception); throw; } break; case UpgradeState.UpgradeComplete: //Client is doing more than one upgrade, reset the state ChangeUpgradeState(ref upgradeState, UpgradeState.VerifyingUpgradeRequest); break; } break; case ServerSingletonDecoder.State.Start: SetupSecurityIfNecessary(upgradeAcceptor); if (upgradeState == UpgradeState.UpgradeComplete || //We have done at least one upgrade, but we are now done. upgradeState == UpgradeState.None) //no upgrade, just send the preample end bytes { ChangeUpgradeState(ref upgradeState, UpgradeState.WritingPreambleEnd); // we've finished the preamble. Ack and return. await currentConnection.WriteAsync(ServerSessionEncoder.AckResponseBytes, 0, ServerSessionEncoder.AckResponseBytes.Length, true, timeoutHelper.RemainingTime()); //terminal state ChangeUpgradeState(ref upgradeState, UpgradeState.PreambleEndSent); } //we are done, this.currentConnection is the upgraded connection return(currentConnection); } if (size == 0) { break; } } } }
// perform security handshake and ACK connection protected override async Task OnOpenAsync(CancellationToken token) { bool success = false; try { // TODO: Sort out the timeout here var timeoutHelper = new TimeoutHelper(TimeSpan.FromSeconds(30)); // first validate our content type ValidateContentType(ref timeoutHelper); // next read any potential upgrades and finish consuming the preamble for (;;) { if (size == 0) { offset = 0; size = await Connection.ReadAsync(0, connectionBuffer.Length, timeoutHelper.RemainingTime()); if (size == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(decoder.CreatePrematureEOFException()); } } for (;;) { DecodeBytes(); switch (decoder.CurrentState) { case ServerSessionDecoder.State.UpgradeRequest: ProcessUpgradeRequest(ref timeoutHelper); // accept upgrade await Connection.WriteAsync(ServerSessionEncoder.UpgradeResponseBytes, 0, ServerSessionEncoder.UpgradeResponseBytes.Length, true, timeoutHelper.RemainingTime()); IConnection connectionToUpgrade = Connection; if (size > 0) { // TODO: Switch to using PreReadConnection constructor which doesn't take a buffer. This is currently causing an extra buffer allocation. connectionToUpgrade = new PreReadConnection(connectionToUpgrade, connectionBuffer, offset, size); } try { Connection = await InitialServerConnectionReader.UpgradeConnectionAsync(connectionToUpgrade, upgradeAcceptor, this); if (channelBindingProvider != null && channelBindingProvider.IsChannelBindingSupportEnabled) { SetChannelBinding(channelBindingProvider.GetChannelBinding(upgradeAcceptor, ChannelBindingKind.Endpoint)); } connectionBuffer = Connection.AsyncReadBuffer; } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } // Audit Authentication Failure WriteAuditFailure(upgradeAcceptor as StreamSecurityUpgradeAcceptor, exception); throw; } break; case ServerSessionDecoder.State.Start: SetupSecurityIfNecessary(); // we've finished the preamble. Ack and return. await Connection.WriteAsync(ServerSessionEncoder.AckResponseBytes, 0, ServerSessionEncoder.AckResponseBytes.Length, true, timeoutHelper.RemainingTime()); SetupSessionReader(); success = true; return; } if (size == 0) { break; } } } } finally { if (!success) { Connection.Abort(); } } }