95 lines
4.2 KiB
Text
95 lines
4.2 KiB
Text
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
export module Crafter.Network:Stream;
|
||
|
|
import std;
|
||
|
|
|
||
|
|
#ifndef CRAFTER_NETWORK_BROWSER
|
||
|
|
namespace Crafter {
|
||
|
|
// A reliable, ordered byte stream with deadlines on both directions.
|
||
|
|
//
|
||
|
|
// This exists so the HTTP/1.1 client and listener can be written once and
|
||
|
|
// run over either a bare socket or a TLS session: `PlainStream` below is
|
||
|
|
// the `http://` transport, `TLSStream` (in :TLS) the `https://` one. The
|
||
|
|
// HTTP/1.1 code holds a `ByteStream&` and never learns which it has.
|
||
|
|
//
|
||
|
|
// Every method takes its own timeout rather than the stream carrying one,
|
||
|
|
// because HTTP/1.1 uses different budgets for different states — a long
|
||
|
|
// idle keep-alive wait, a shorter one once a request has started.
|
||
|
|
export enum class StreamStatus {
|
||
|
|
Data, // `read` bytes are available in the buffer
|
||
|
|
Closed, // the peer closed its send side, cleanly
|
||
|
|
TimedOut, // nothing arrived before the deadline
|
||
|
|
};
|
||
|
|
|
||
|
|
export class ByteStream {
|
||
|
|
public:
|
||
|
|
virtual ~ByteStream() = default;
|
||
|
|
ByteStream() = default;
|
||
|
|
ByteStream(const ByteStream&) = delete;
|
||
|
|
ByteStream& operator=(const ByteStream&) = delete;
|
||
|
|
|
||
|
|
// Read whatever is already available, waiting at most `timeout` for
|
||
|
|
// the first byte. Sets `read` and returns Data, or reports a clean
|
||
|
|
// close / a timeout. Throws on a transport error.
|
||
|
|
virtual StreamStatus ReadSome(char* buffer, std::size_t size,
|
||
|
|
std::chrono::milliseconds timeout,
|
||
|
|
std::size_t& read) = 0;
|
||
|
|
|
||
|
|
// Write the whole buffer. Throws if it could not all be handed over
|
||
|
|
// within `timeout`.
|
||
|
|
virtual void Write(const void* buffer, std::size_t size,
|
||
|
|
std::chrono::milliseconds timeout) = 0;
|
||
|
|
|
||
|
|
// Best-effort orderly close of our send side. Never throws — it runs
|
||
|
|
// on teardown paths where there is nothing useful to do with a
|
||
|
|
// failure.
|
||
|
|
virtual void Shutdown() noexcept = 0;
|
||
|
|
|
||
|
|
// The underlying descriptor, so a listener can shutdown(2) it to wake
|
||
|
|
// a thread parked in poll().
|
||
|
|
virtual int Descriptor() const noexcept = 0;
|
||
|
|
|
||
|
|
// Negotiated ALPN protocol, empty when the transport has no notion of
|
||
|
|
// one (plaintext) or nothing was agreed.
|
||
|
|
virtual std::string_view Protocol() const noexcept { return {}; }
|
||
|
|
|
||
|
|
// Whether the bytes are encrypted on the wire.
|
||
|
|
virtual bool Secure() const noexcept { return false; }
|
||
|
|
};
|
||
|
|
|
||
|
|
// Plaintext TCP. Non-owning: the descriptor stays owned by the ClientTCP
|
||
|
|
// (or whatever else) that opened it.
|
||
|
|
//
|
||
|
|
// The descriptor is switched to non-blocking on construction — both
|
||
|
|
// directions are driven by poll() against a deadline, which a blocking
|
||
|
|
// descriptor cannot express. That is also what lets a write time out
|
||
|
|
// instead of parking forever against a peer that has stopped reading.
|
||
|
|
export class PlainStream final : public ByteStream {
|
||
|
|
public:
|
||
|
|
explicit PlainStream(int descriptor);
|
||
|
|
|
||
|
|
StreamStatus ReadSome(char* buffer, std::size_t size,
|
||
|
|
std::chrono::milliseconds timeout,
|
||
|
|
std::size_t& read) override;
|
||
|
|
void Write(const void* buffer, std::size_t size,
|
||
|
|
std::chrono::milliseconds timeout) override;
|
||
|
|
void Shutdown() noexcept override;
|
||
|
|
int Descriptor() const noexcept override { return descriptor; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
int descriptor;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Put a descriptor into non-blocking mode. Exposed because :TLS needs the
|
||
|
|
// same thing for the descriptor it wraps.
|
||
|
|
export void SetNonBlocking(int descriptor);
|
||
|
|
|
||
|
|
// Wait until `descriptor` is ready for `events` (a poll(2) event mask) or
|
||
|
|
// `deadline` passes; false means the deadline won. Retries across EINTR
|
||
|
|
// and throws on a real poll failure. Shared with :TLS, which has to poll
|
||
|
|
// for whichever direction OpenSSL asks for next.
|
||
|
|
export bool PollDescriptor(int descriptor, short events,
|
||
|
|
std::chrono::steady_clock::time_point deadline);
|
||
|
|
}
|
||
|
|
#endif
|