// Update is called once per frame void Update() { foreach (var client in clients.Values) { client.Connection.OnTimeout(); } if (receiveResult == null) { receiveResult = udpClient.BeginReceive((res) => { var state = (ClientState)res.AsyncState; state.ReceiveBytes = udpClient.EndReceive(res, ref state.remoteIpEndPoint); }, new ClientState()); } if (receiveResult.IsCompleted) { Poll((ClientState)receiveResult.AsyncState); receiveResult = null; } foreach (var key in clients.Keys) { var client = clients[key]; var written = client.Connection.Send(sendBuf); if (written == (int)QuicheError.QUICHE_ERR_DONE) { Debug.Log("done writing"); continue; } if (written < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), written); Debug.LogError($"ailed to create packet: {written} {err}"); client.Connection.Close(false, 0x1, Encoding.ASCII.GetBytes("fail")); continue; } var buf = sendBuf.Take(written).ToArray(); udpClient.Send(buf, written, client.RemoteIpEndPoint); Debug.Log($"sent {written} bytes"); } foreach (var key in clients.Keys .Where(k => clients[k].Connection.IsClosed).ToArray()) { Debug.LogWarning("Connection Dispose"); var client = clients[key]; clients.Remove(key); client.Connection.Dispose(); } }
private void Poll(ClientState state) { var recvBytes = state.ReceiveBytes; var remoteIpEndPoint = state.remoteIpEndPoint; QuicheHeaderInfo header; try{ header = QuicheHeaderInfo.Construct(recvBytes); } catch (Exception e) { Debug.LogError($"{e}"); return; } if (header.Type == 6 /* Type::VersionNegotiation */) { Debug.LogError("Version negotiation invalid on the server"); return; } var key = HexDump(header.Dcid); Client client; if (!clients.TryGetValue(key, out client)) { if (header.Type != 1 /* Type::Initial */) { Debug.LogError("Packet is not Initial"); return; } if (!VersionIsSupported(header.Version)) { Debug.LogWarning("Doing version negotiation"); var length = (int)NegotiateVersion( header.Scid, header.Dcid, negotiateBuf); var sendBuf = negotiateBuf.Take(length).ToArray(); udpClient.Send(sendBuf, length, remoteIpEndPoint); return; } var scid = new byte[QuicheClient.LOCAL_CONN_ID_LEN]; new System.Random().NextBytes(scid); byte[] odcid = new byte[65535]; if (!noRetry) { if (header.Token.Length == 0) { Debug.LogWarning("Doing stateless retry"); Debug.Log($"Retry: scid={HexDump(header.Scid)} new_scid={HexDump(scid)}"); var new_token = MintToken(header, remoteIpEndPoint); var _out = new byte[65535]; var written = (int)Retry(header.Scid, header.Dcid, scid, new_token, _out); udpClient.Send( _out.Take(written).ToArray(), written, remoteIpEndPoint); return; } odcid = ValidateToken(header.Token, remoteIpEndPoint); if (odcid == null) { Debug.LogError("Invalid address validation token"); return; } } if (scid.Length != header.Dcid.Length) { Debug.LogError("Invalid destination connection ID"); return; } scid = header.Dcid; var conn = quiche.Accept(scid, odcid); var _client = new Client(conn, remoteIpEndPoint); clients.Add(HexDump(scid), _client); client = _client; } var read = client.Connection.Receive(recvBytes); if (read == (int)QuicheError.QUICHE_ERR_DONE) { Debug.Log("done reading"); return; } if (read < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), read); Debug.LogError($"recv failed {err}"); throw new Exception(); } Debug.Log($"recv {read} bytes"); if (client.Connection.IsInEarlyData || client.Connection.IsEstablished) { foreach (ulong streamId in client.Connection.Writable()) { if (!client.PartialResponses.ContainsKey(streamId)) { continue; } var partialResponse = client.PartialResponses[streamId]; var written = client.Connection.StreamSend(streamId, partialResponse.Body, true); if (written < 0) { continue; } partialResponse.Written += written; if (partialResponse.Written == partialResponse.Body.Length) { client.PartialResponses.Remove(streamId); } } foreach (ulong streamId in client.Connection.Readable()) { Debug.Log($"stream {streamId} is readable"); bool fin = false; var readStream = client.Connection.StreamReceive( streamId, streamRecv, ref fin); if (readStream < 0) { continue; } if (fin) { var body = Encoding.ASCII.GetBytes("Hello World!\n"); var written = client.Connection.StreamSend( streamId, body, true); if (written < 0) { continue; } if (written < body.Length) { var partialResponse = new PartialResponse(body, written); client.PartialResponses.Add(streamId, partialResponse); } } } } }
// Update is called once per frame void Update() { if (quicheConn == null || quicheConn.IsClosed) { return; } quicheConn.OnTimeout(); if (receiveResult == null) { receiveResult = udpClient.BeginReceive((res) => { var recvBytes = udpClient.EndReceive(res, ref remoteIpEndPoint); var read = quicheConn.Receive(recvBytes); if (read == (int)QuicheError.QUICHE_ERR_DONE) { Debug.Log("done reading"); return; } if (read < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), read); Debug.LogError($"recv failed {err}"); throw new Exception(); } }, null); } if (receiveResult.IsCompleted) { receiveResult = null; } if (quicheConn.IsEstablished && h3Conn == null) { h3Conn = new H3Connection(quicheConn, h3Config); } if (h3Conn != null) { var reqsDone = 0; for (var i = reqsSent; i < reqsCount; i++) { Debug.Log($"sending HTTP request [{string.Join(",", req.Select(x=>H3Header.DebugString(x)))}]"); var streamId = h3Conn.SendRequest(req, body == null); if (streamId == (int)H3Error.QUICHE_H3_TRANSPORT_ERROR) { Debug.Log("not enough stream credits, retry later..."); break; } if (streamId < 0) { H3Error err = (H3Error)Enum .ToObject(typeof(H3Error), streamId); Debug.LogError($"recv failed {err}"); throw new Exception(); } if (body != null) { var e = h3Conn.SendBody((ulong)streamId, body, true); if (e < 0) { H3Error err = (H3Error)Enum .ToObject(typeof(H3Error), e); Debug.LogError($"recv failed {err}"); throw new Exception(); } } reqsDone++; } reqsSent += reqsDone; } if (h3Conn != null) { H3Event ev = null; // Process HTTP/3 events. while (h3Conn != null) { var streamId = h3Conn.Poll(ref ev); if (streamId == (int)H3Error.QUICHE_H3_DONE) { break; } else if (streamId < 0) { H3Error err = (H3Error)Enum .ToObject(typeof(H3Error), streamId); Debug.LogError($"recv failed {err}"); return; } switch (ev.EventType) { case (uint)H3EventType.QUICHE_H3_EVENT_HEADERS: { var rc = ev.ForEachHeader((name, nameLen, value, valueLen, argp) => { Debug.Log($"got HTTP header: {name}={value}"); }); if (rc != 0) { Debug.LogError("failed to process headers"); } break; } case (uint)H3EventType.QUICHE_H3_EVENT_DATA: { var _out = new byte[65535]; var len = h3Conn.ReceiveBody((ulong)streamId, _out); if (len <= 0) { break; } Debug.Log($"{Encoding.ASCII.GetString(_out.Take((int)len).ToArray())}"); break; } case (uint)H3EventType.QUICHE_H3_EVENT_FINISHED: { reqsComplete++; if (reqsComplete == reqsCount) { Debug.Log($"{reqsComplete}/{reqsCount} response(s) received, cloing..."); var e = quicheConn.Close(true, 0, Encoding.ASCII.GetBytes("kthxbye")); h3Conn.Dispose(); h3Conn = null; if (e == (int)QuicheError.QUICHE_ERR_DONE) { break; } else if (e < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), e); Debug.LogError($"recv failed {err}"); throw new Exception(); } } break; } } ev.Dispose(); ev = null; } } var write = quicheConn.Send(buf); if (write == (int)QuicheError.QUICHE_ERR_DONE) { return; } if (write < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), write); Debug.LogError($"send failed {err}"); throw new Exception(); } udpClient.Send(buf, write); }
private void Poll() { if (conn == null) { return; } if (conn.IsClosed) { return; } conn.OnTimeout(); if (receiveResult == null) { receiveResult = client.BeginReceive((res) => { var recvBytes = client.EndReceive(res, ref RemoteIpEndPoint); var read = conn.Receive(recvBytes); if (read == (int)QuicheError.QUICHE_ERR_DONE) { Debug.Log("done reading"); return; } if (read < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), read); Debug.LogError($"recv failed {err}"); throw new Exception(); } }, null); } if (receiveResult.IsCompleted) { receiveResult = null; } if (conn.IsEstablished) { if (!req_sent) { Debug.Log($"sending HTTP request for {uri.PathAndQuery}"); var req = Encoding.ASCII.GetBytes($"GET {uri.PathAndQuery}\r\n"); var streamWrite = conn.StreamSend(HTTP_REQ_STREAM_ID, req, true); if (streamWrite < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), streamWrite); Debug.LogError($"send failed {err}"); throw new Exception(); } req_sent = true; } foreach (ulong streamId in conn.Readable()) { bool fin = false; var readStream = conn.StreamReceive( streamId, streamRecv, ref fin); if (readStream < 0) { continue; } var res = Encoding.ASCII.GetString( streamRecv.Take(readStream).ToArray()); Debug.Log($"{res}"); if (fin) { var reason = Encoding.ASCII.GetBytes("kthxbye"); int closeError = conn.Close(true, 0, reason); if (closeError < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), closeError); Debug.LogError($"send failed {err}"); throw new Exception(); } } } } var write = conn.Send(buf); if (write == (int)QuicheError.QUICHE_ERR_DONE) { return; } if (write < 0) { QuicheError err = (QuicheError)Enum .ToObject(typeof(QuicheError), write); Debug.LogError($"send failed {err}"); throw new Exception(); } client.Send(buf, write); }