public async ValueTask ConnectAsync(Socket socket, EndPoint endpoint, CancellationToken cancel) { // HTTP connect request string addr = endpoint.ToString() !; var sb = new System.Text.StringBuilder(); sb.Append("CONNECT "); sb.Append(addr); sb.Append(" HTTP/1.1\r\nHost: "); sb.Append(addr); sb.Append("\r\n\r\n"); // Send the connect request. await socket.SendAsync(System.Text.Encoding.ASCII.GetBytes(sb.ToString()), SocketFlags.None, cancel).ConfigureAwait(false); // Read the HTTP response, reserve enough space for reading at least HTTP1.1 byte[] buffer = new byte[256]; int received = 0; while (true) { received += await socket.ReceiveAsync(buffer.AsMemory(received), SocketFlags.None, cancel).ConfigureAwait(false); // Check if we received the full HTTP response, if not, continue reading otherwise we're done. int end = HttpParser.IsCompleteMessage(buffer.AsSpan(0, received)); if (end < 0 && received == buffer.Length) { // We need to allocate a new buffer byte[] newBuffer = new byte[buffer.Length * 2]; Buffer.BlockCopy(buffer, 0, newBuffer, 0, received); buffer = newBuffer; } else { break; } } var parser = new HttpParser(); parser.Parse(buffer); if (parser.Status() != 200) { // TODO the retry always use the same proxy address throw new ConnectFailedException(RetryPolicy.AfterDelay(TimeSpan.Zero)); } }
public async ValueTask InitializeAsync(CancellationToken cancel) { await _underlying.InitializeAsync(cancel).ConfigureAwait(false); try { // The server waits for the client's upgrade request, the client sends the upgrade request. if (!_incoming) { // Compose the upgrade request. var sb = new StringBuilder(); sb.Append("GET " + _resource + " HTTP/1.1\r\n"); sb.Append("Host: " + _host + "\r\n"); sb.Append("Upgrade: websocket\r\n"); sb.Append("Connection: Upgrade\r\n"); sb.Append("Sec-WebSocket-Protocol: " + IceProtocol + "\r\n"); sb.Append("Sec-WebSocket-Version: 13\r\n"); sb.Append("Sec-WebSocket-Key: "); // The value for Sec-WebSocket-Key is a 16-byte random number, encoded with Base64. byte[] key = new byte[16]; _rand.NextBytes(key); _key = Convert.ToBase64String(key); sb.Append(_key + "\r\n\r\n"); // EOM byte[] data = _utf8.GetBytes(sb.ToString()); _sendBuffer.Add(data); await _underlying.SendAsync(_sendBuffer, cancel).ConfigureAwait(false); } _sendBuffer.Clear(); // Try to read the client's upgrade request or the server's response. var httpBuffer = new ArraySegment <byte>(); while (true) { ReadOnlyMemory <byte> buffer = await _underlying.ReceiveAsync(0, cancel).ConfigureAwait(false); if (httpBuffer.Count + buffer.Length > _communicator.IncomingFrameSizeMax) { throw new InvalidDataException( "WebSocket frame size is greater than the configured IncomingFrameSizeMax value"); } ArraySegment <byte> tmpBuffer = new byte[httpBuffer.Count + buffer.Length]; if (httpBuffer.Count > 0) { httpBuffer.CopyTo(tmpBuffer); } buffer.CopyTo(tmpBuffer.Slice(httpBuffer.Count)); httpBuffer = tmpBuffer; // Check if we have enough data for a complete frame. int endPos = HttpParser.IsCompleteMessage(httpBuffer); if (endPos != -1) { // Add back the un-consumed data to the buffer. _underlying.Rewind(httpBuffer.Count - endPos); httpBuffer = httpBuffer.Slice(0, endPos); break; // Done } } try { if (_parser.Parse(httpBuffer)) { if (_incoming) { (bool addProtocol, string key) = ReadUpgradeRequest(); // Compose the response. var sb = new StringBuilder(); sb.Append("HTTP/1.1 101 Switching Protocols\r\n"); sb.Append("Upgrade: websocket\r\n"); sb.Append("Connection: Upgrade\r\n"); if (addProtocol) { sb.Append($"Sec-WebSocket-Protocol: {IceProtocol}\r\n"); } // The response includes: // // "A |Sec-WebSocket-Accept| header field. The value of this header field is constructed // by concatenating /key/, defined above in step 4 in Section 4.2.2, with the string // "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this concatenated value // to obtain a 20-byte value and base64-encoding (see Section 4 of [RFC4648]) this 20-byte // hash. sb.Append("Sec-WebSocket-Accept: "); string input = key + WsUUID; #pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms using var sha1 = SHA1.Create(); byte[] hash = sha1.ComputeHash(_utf8.GetBytes(input)); #pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms sb.Append(Convert.ToBase64String(hash) + "\r\n" + "\r\n"); // EOM Debug.Assert(_sendBuffer.Count == 0); byte[] data = _utf8.GetBytes(sb.ToString()); _sendBuffer.Add(data); await _underlying.SendAsync(_sendBuffer, cancel).ConfigureAwait(false); _sendBuffer.Clear(); } else { ReadUpgradeResponse(); } } else { throw new InvalidDataException("incomplete WebSocket request frame"); } } catch (WebSocketException ex) { throw new InvalidDataException(ex.Message, ex); } } catch (Exception ex) { if (_communicator.TraceLevels.Transport >= 2) { _communicator.Logger.Trace(TraceLevels.TransportCategory, $"{_transportName} connection HTTP upgrade request failed\n{this}\n{ex}"); } throw; } if (_communicator.TraceLevels.Transport >= 1) { if (_incoming) { _communicator.Logger.Trace(TraceLevels.TransportCategory, $"accepted {_transportName} connection HTTP upgrade request\n{this}"); } else { _communicator.Logger.Trace(TraceLevels.TransportCategory, $"{_transportName} connection HTTP upgrade request accepted\n{this}"); } } }