internal static ClassifyState TransitionExpectProtocolError( ClassifyState state, Frame frame, ref ProtocolError error, ref FrameDisposition disposition, Logger logger) { if (state != ClassifyState.ExpectProtocolError || frame == null || frame.Count == 0 || frame.Framelets[0].Type != FrameletType.ProtocolError) { return ClassifyState.InternalStateError; } if (frame.Count > 1) { logger.Site().Error("Protocol error frame had trailing framelets."); return ClassifyState.ErrorInErrorFrame; } var framelet = frame.Framelets[0]; var inputBuffer = new InputBuffer(framelet.Contents); var fastBinaryReader = new FastBinaryReader<InputBuffer>(inputBuffer, version: 1); switch (errorDeserializer.TryDeserialize(fastBinaryReader, out error)) { case Deserialize.Result.Success: break; default: logger.Site().Error("Didn't get a valid {0}.", nameof(ProtocolError)); return ClassifyState.ErrorInErrorFrame; } logger.Site().Debug("Deserialized {0} with code {1}.", nameof(ProtocolError), error.error_code); disposition = FrameDisposition.HandleProtocolError; return ClassifyState.ClassifiedValidFrame; }
internal static ClassifyState TransitionFrameComplete( ClassifyState state, EpoxyHeaders headers, ref ProtocolErrorCode? errorCode, Logger logger) { if (state != ClassifyState.FrameComplete || headers == null) { return ClassifyState.InternalStateError; } switch (headers.message_type) { case EpoxyMessageType.REQUEST: case EpoxyMessageType.RESPONSE: case EpoxyMessageType.EVENT: return ClassifyState.ValidFrame; default: logger.Site().Warning("Received unrecognized message type {0}.", headers.message_type); errorCode = ProtocolErrorCode.NOT_SUPPORTED; return ClassifyState.MalformedFrame; } }
internal static ClassifyState TransitionExpectConfig( ClassifyState state, Frame frame, ref ProtocolErrorCode? errorCode, ref FrameDisposition disposition, Logger logger) { Debug.Assert(state == ClassifyState.ExpectConfig); Debug.Assert(frame != null); if (frame.Count == 0 || frame.Framelets[0].Type != FrameletType.EpoxyConfig) { return ClassifyState.InternalStateError; } if (frame.Count != 1) { logger.Site().Error("Config frame had trailing framelets."); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } var framelet = frame.Framelets[0]; var inputBuffer = new InputBuffer(framelet.Contents); var fastBinaryReader = new FastBinaryReader<InputBuffer>(inputBuffer, version: 1); // We don't currently do anything with the config aside from try to deserialize it. EpoxyConfig config; switch (configDeserializer.TryDeserialize(fastBinaryReader, out config)) { case Deserialize.Result.Success: break; default: logger.Site().Error("Didn't get a valid {0}.", nameof(EpoxyConfig)); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } disposition = FrameDisposition.ProcessConfig; return ClassifyState.ClassifiedValidFrame; }
internal static ClassifyState TransitionExpectMessageData( ClassifyState state, Frame frame, EpoxyHeaders headers, ArraySegment<byte> layerData, ref MessageData messageData, ref ProtocolErrorCode? errorCode, Logger logger) { Debug.Assert(state == ClassifyState.ExpectMessageData); Debug.Assert(frame != null); if (headers == null) { return ClassifyState.InternalStateError; } int messageDataIndex = (layerData.Array == null ? 1 : 2); if (messageDataIndex >= frame.Count) { logger.Site().Error("Frame had headers but no message data."); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } var framelet = frame.Framelets[messageDataIndex]; if (framelet.Type != FrameletType.PayloadData && framelet.Type != FrameletType.ErrorData) { logger.Site().Error("Frame had headers but no message data. Unexpected framelet type {0}", (int)framelet.Type); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } messageData = new MessageData( isError: framelet.Type == FrameletType.ErrorData, data: framelet.Contents); logger.Site().Debug("Extracted {0}-byte message in conversation ID {1}.", messageData.Data.Count, headers.conversation_id); return ClassifyState.ExpectEndOfFrame; }
internal static ClassifyState TransitionExpectEndOfFrame( ClassifyState state, Frame frame, ArraySegment<byte> layerData, ref ProtocolErrorCode? errorCode, Logger logger) { // FIXME: Change all of these to asserts. if (state != ClassifyState.ExpectEndOfFrame || frame == null) { return ClassifyState.InternalStateError; } var validFrameSize = (layerData.Array == null ? 2 : 3); if (frame.Count == validFrameSize) { return ClassifyState.FrameComplete; } else { logger.Site().Error("Frame had trailing framelets."); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } }
internal static ClassifyState TransitionExpectEpoxyHeaders( ClassifyState state, Frame frame, ref EpoxyHeaders headers, ref ProtocolErrorCode? errorCode, Logger logger) { Debug.Assert(state == ClassifyState.ExpectEpoxyHeaders); Debug.Assert(frame != null); if (frame.Count == 0 || frame.Framelets[0].Type != FrameletType.EpoxyHeaders) { return ClassifyState.InternalStateError; } var framelet = frame.Framelets[0]; var inputBuffer = new InputBuffer(framelet.Contents); var fastBinaryReader = new FastBinaryReader<InputBuffer>(inputBuffer, version: 1); switch (headersDeserializer.TryDeserialize(fastBinaryReader, out headers)) { case Deserialize.Result.Success: break; default: logger.Site().Error("Didn't get a valid {0}.", nameof(EpoxyHeaders)); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } logger.Site().Debug("Deserialized {0} with conversation ID {1} and message type {2}.", nameof(EpoxyHeaders), headers.conversation_id, headers.message_type); return ClassifyState.ExpectOptionalLayerData; }
internal static ClassifyState TransitionExpectOptionalLayerData( ClassifyState state, Frame frame, EpoxyHeaders headers, ref ArraySegment<byte> layerData, ref ProtocolErrorCode? errorCode, Logger logger) { Debug.Assert(state == ClassifyState.ExpectOptionalLayerData); Debug.Assert(frame != null); if (headers == null) { return ClassifyState.InternalStateError; } if (frame.Count < 2) { logger.Site().Error("Frame had headers but no message data."); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } var framelet = frame.Framelets[1]; if (framelet.Type == FrameletType.LayerData) { layerData = framelet.Contents; logger.Site().Debug("Extracted {0}-byte layer data in conversation ID {1}.", layerData.Count, headers.conversation_id); } return ClassifyState.ExpectMessageData; }
internal static ClassifyState TransitionExpectFirstFramelet( ClassifyState state, Frame frame, ref ProtocolErrorCode? errorCode, Logger logger) { Debug.Assert(state == ClassifyState.ExpectFirstFramelet); Debug.Assert(frame != null); if (frame.Framelets.Count == 0) { logger.Site().Error("Frame was empty."); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } switch (frame.Framelets[0].Type) { case FrameletType.EpoxyHeaders: return ClassifyState.ExpectEpoxyHeaders; case FrameletType.EpoxyConfig: return ClassifyState.ExpectConfig; case FrameletType.ProtocolError: return ClassifyState.ExpectProtocolError; default: logger.Site().Error("Frame began with invalid FrameletType {0}.", frame.Framelets[0].Type); errorCode = ProtocolErrorCode.MALFORMED_DATA; return ClassifyState.MalformedFrame; } }
internal static ClassifyResult Classify(Frame frame, Logger logger) { if (frame == null) { return new ClassifyResult { Disposition = FrameDisposition.Indeterminate }; } logger.Site().Debug("Processing {0} framelets.", frame.Count); var state = ClassifyState.ExpectFirstFramelet; var disposition = FrameDisposition.Indeterminate; EpoxyHeaders headers = null; var layerData = new ArraySegment<byte>(); var messageData = default(MessageData); ProtocolError error = null; ProtocolErrorCode? errorCode = null; uint transitions = 0; while (true) { // If it looks like we have a bug and are looping forever, bail out of the state machine. if (transitions++ > maximumTransitions) { return new ClassifyResult { Disposition = FrameDisposition.Indeterminate }; } switch (state) { case ClassifyState.ExpectFirstFramelet: state = TransitionExpectFirstFramelet(state, frame, ref errorCode, logger); continue; case ClassifyState.ExpectEpoxyHeaders: state = TransitionExpectEpoxyHeaders(state, frame, ref headers, ref errorCode, logger); continue; case ClassifyState.ExpectOptionalLayerData: state = TransitionExpectOptionalLayerData(state, frame, headers, ref layerData, ref errorCode, logger); continue; case ClassifyState.ExpectMessageData: state = TransitionExpectMessageData(state, frame, headers, layerData, ref messageData, ref errorCode, logger); continue; case ClassifyState.ExpectEndOfFrame: state = TransitionExpectEndOfFrame(state, frame, layerData, ref errorCode, logger); continue; case ClassifyState.FrameComplete: state = TransitionFrameComplete(state, headers, ref errorCode, logger); continue; case ClassifyState.ValidFrame: state = TransitionValidFrame(state, headers, ref disposition); continue; case ClassifyState.ExpectConfig: state = TransitionExpectConfig(state, frame, ref errorCode, ref disposition, logger); continue; case ClassifyState.ExpectProtocolError: state = TransitionExpectProtocolError(state, frame, ref error, ref disposition, logger); continue; case ClassifyState.ClassifiedValidFrame: if (disposition == FrameDisposition.Indeterminate) { state = ClassifyState.InternalStateError; continue; } return new ClassifyResult { Disposition = disposition, Headers = headers, LayerData = layerData, MessageData = messageData, Error = error }; case ClassifyState.MalformedFrame: if (errorCode == null) { state = ClassifyState.InternalStateError; continue; } return new ClassifyResult { Disposition = FrameDisposition.SendProtocolError, ErrorCode = errorCode }; case ClassifyState.ErrorInErrorFrame: return new ClassifyResult { Disposition = FrameDisposition.HangUp, Error = new ProtocolError { error_code = ProtocolErrorCode.ERROR_IN_ERROR } }; case ClassifyState.InternalStateError: return new ClassifyResult { Disposition = FrameDisposition.Indeterminate }; default: logger.Site().Error("Unhandled state {0}. Dropping frame.", state); return new ClassifyResult { Disposition = FrameDisposition.Indeterminate }; } } }
public static async Task<EpoxyNetworkStream> MakeClientStreamAsync( string remoteHostname, Socket socket, EpoxyClientTlsConfig tlsConfig, Logger logger) { Stream clientStream; NetworkStream networkStream = new NetworkStream(socket, ownsSocket: false); if (tlsConfig == null) { clientStream = networkStream; } else { const bool leaveInnerStreamOpen = false; var sslStream = new SslStream( networkStream, leaveInnerStreamOpen, tlsConfig.RemoteCertificateValidationCallback); var clientCertificates = new X509CertificateCollection(); if (tlsConfig.Certificate != null) { clientCertificates.Add(tlsConfig.Certificate); } await sslStream.AuthenticateAsClientAsync( remoteHostname, clientCertificates, AllowedTlsProtocols, tlsConfig.CheckCertificateRevocation); logger.Site().Debug("Authenticated connection to {0}[{1}]", remoteHostname, socket.RemoteEndPoint); clientStream = sslStream; } return new EpoxyNetworkStream(socket, clientStream, logger); }
private static RemoteCertificateValidationCallback MakeServerCertificateValidationCallback( EpoxyServerTlsConfig tlsConfig, Logger logger) { if (tlsConfig.ClientCertificateRequired) { // If client certificates are required, then add an explicit // check that the client provided a certificate. The default // behavior is to allow the connection even if the client // didn't present a certificate. return (sender, certificate, chain, errors) => { if (certificate == null) { logger.Site().Error("Rejecting client. Certificate required, but client did not provide one."); return false; } if (tlsConfig.RemoteCertificateValidationCallback != null) { // There's a user-provided validation callback, so // delegate to that. return tlsConfig.RemoteCertificateValidationCallback( sender, certificate, chain, errors); } else { // Otherwise, require no errors at all to accept the // certificate. return errors == SslPolicyErrors.None; } }; } else { // Client certificates are not required, so just use the // user-provided validation callback. This may be null, but // that's fine. SslStream will just use its default behavior // then. return tlsConfig.RemoteCertificateValidationCallback; } }
public static async Task<EpoxyNetworkStream> MakeServerStreamAsync( Socket socket, EpoxyServerTlsConfig tlsConfig, Logger logger) { Stream serverStream; var networkStream = new NetworkStream(socket, ownsSocket: false); if (tlsConfig == null) { serverStream = networkStream; } else { const bool leaveInnerStreamOpen = false; var sslStream = new SslStream( networkStream, leaveInnerStreamOpen, MakeServerCertificateValidationCallback(tlsConfig, logger)); await sslStream.AuthenticateAsServerAsync( tlsConfig.Certificate, tlsConfig.ClientCertificateRequired, enabledSslProtocols: AllowedTlsProtocols, checkCertificateRevocation: tlsConfig.CheckCertificateRevocation); if (tlsConfig.ClientCertificateRequired && !sslStream.IsMutuallyAuthenticated) { sslStream.Dispose(); throw new AuthenticationException("Mutual authentication was required, but it could not be performed."); } logger.Site().Debug( "Authenticated connection from {0}. Mutually authenticated?: {1}", socket.RemoteEndPoint, sslStream.IsMutuallyAuthenticated); serverStream = sslStream; } return new EpoxyNetworkStream(socket, serverStream, logger); }