

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file NetworkProtocols.h
	@brief Wire-protocol layer: HTTP request/reply, WebSocket frames, Telnet lines, binary DataMessages, and protocol auto-detection.

	This header sits between the raw transports (NetworkConnections.h) and the
	connection manager (NetworkManager.h). It defines:

	- Message containers: HTTPRequest, HTTPReply, WebsocketData, TelnetLine and
	  JSONM (JSON-with-binary-attachments multipart container). Each knows how
	  to parse itself from raw socket bytes (processHeader()/processContent())
	  and how to serialise itself for sending.
	- Protocol handlers: static-method classes (HTTPProtocol,
	  HTTPClientProtocol, MessageProtocol, TelnetProtocol) that read/write the
	  containers over a NetworkConnection with timeouts.
	- Auto-detection: each protocol implements CheckBufferForCompatibility(),
	  letting a listener sniff the first bytes of an incoming connection and
	  route it to the right handler (see NetworkChannel::autoDetectConnection()).

	PROTOCOL_* constants are bit flags, so a listener can accept several
	protocols on one port (e.g. HTTP + binary messages + Telnet).

	@par Byte order
	WebSocket framing (extended payload lengths, masking keys) is converted
	with htons()/ntohs()/ntoh64() and follows RFC 6455 network (big-endian)
	byte order — see the BHB0_* and BHB1_* masks in WebsocketData. The binary
	DataMessage protocol, by contrast, sends the message's serialised memory
	image as-is: its leading uint32 size field is read on the receiving side
	without any byte-order conversion (see MessageProtocol::ReceiveMessage()),
	so both endpoints are assumed to share the same (in practice little-endian)
	endianness — fine for x86/ARM deployments, but a real portability caveat
	for big-endian hosts.

	@par Wire framing at a glance
	The two binary formats framed by this layer:
	@code
	DataMessage frame (PROTOCOL_MESSAGE), host byte order:
	  [uint32 totalSize][uint32 DATAMESSAGEID][ ...serialised message... ]
	  ReceiveMessage() peeks the first 8 bytes, validates the id, then reads
	  exactly totalSize bytes and hands them to DataMessage (zero-copy adopt).

	WebSocket frame header (RFC 6455), network byte order:
	  byte 0: FIN(0x80) RSV1-3(0x40/0x20/0x10) OPCODE(0x0F)
	  byte 1: MASK(0x80) PAYLOAD-LEN(0x7F)
	    len <= 125 : that value is the payload size (no extension)
	    len == 126 : next 2 bytes  = uint16 payload size (ntohs)
	    len == 127 : next 8 bytes  = uint64 payload size (ntoh64)
	  then, if MASK set: 4-byte masking key; payload bytes are XORed with
	  key[i % 4] (clients MUST mask; servers send unmasked).
	@endcode

	@par Platform notes
	This header itself is platform-neutral: all Winsock-vs-BSD socket
	differences (@c \#ifdef WIN32 / @c _WIN64 vs POSIX, WSAStartup, SOCKET vs
	int descriptors) live one layer down in NetworkConnections.h, and the
	byte-order helpers (htons()/ntohs() from the platform headers,
	utils::ntoh64() for 64-bit values) behave identically on both families.
	Protocol code here therefore compiles unchanged on Windows, Linux and
	macOS.

	@ingroup Networking
*/
#if !defined(_NETWORKPROTOCOLS_H_)
#define _NETWORKPROTOCOLS_H_

#pragma once

#include "DataMessage.h"
#include "NetworkConnections.h"
#include "jsmn.h"
#include "hash/sha1.h"

/*! @name Protocol and encryption flags
	Bit flags identifying wire protocols (combinable for auto-detect listeners)
	and encryption schemes. @{ */
#define PROTOCOL_HTTP_SERVER	1	/*!< Serve HTTP: parse requests, send replies (server role). */
#define PROTOCOL_HTTP_CLIENT	2	/*!< Speak HTTP as a client: send requests, parse replies. */
#define PROTOCOL_MESSAGE		4	/*!< CMSDK binary DataMessage protocol (size-prefixed frames). */
#define PROTOCOL_TELNET			8	/*!< Line-based Telnet-style text protocol. */
#define PROTOCOL_3GCAM			16	/*!< Legacy 3G camera streaming protocol. */
#define ENCRYPTION_SSL			1	/*!< SSL/TLS transport encryption flag. */
#define ENCRYPTION_AES			2	/*!< AES transport encryption flag (reserved). */
/*! @} */

namespace	cmlabs{

/////////////////////////////////////////////////////////////
// Data Types
/////////////////////////////////////////////////////////////

/*! @name HTTP methods
	Numeric HTTP method ids used in HTTPRequest::type; index into ::HTTP_Type
	for the method name string. @{ */
#define HTTP_GET		1	/*!< GET */
#define HTTP_PUT		2	/*!< PUT */
#define HTTP_POST		3	/*!< POST */
#define HTTP_HEAD		4	/*!< HEAD */
#define HTTP_DELETE		5	/*!< DELETE */
#define HTTP_OPTIONS	6	/*!< OPTIONS */
/*! @} */

/*! HTTP method name strings, indexed by the HTTP_* method ids (index 0 unused). */
static char HTTP_Type[][8] =
{
"",
"GET",
"PUT",
"POST",
"HEAD",
"DELETE",
"OPTIONS"
};

// 
//GET /path/file.html HTTP/1.0
//From: someuser@jmarshall.com
//User-Agent: HTTPTool/1.0
//if-modified-since: Sat, 29 Oct 1994 19:43:31 GMT
//[blank line here]
//
//POST /path/script.cgi HTTP/1.0
//From: frog@jmarshall.com
//User-Agent: HTTPTool/1.0
//Content-Type: application/x-www-form-urlencoded
//Content-Length: 32
//
//home=Cosby&favorite+flavor=flies
//

/*! One named entry of an HTTP POST body (form field or uploaded file part).

	Used both when parsing multipart/form-data requests (each part becomes an
	entry in HTTPRequest::postEntries) and when building multipart requests
	(HTTPRequest::createMultipartRequest()). Owns its content buffer.
	@ingroup Networking */
class HTTPPostEntry {
public:
	HTTPPostEntry() { content = NULL; contentSize = 0; }
	virtual ~HTTPPostEntry() { delete [] content; }

	/*! @return true when the entry has a name and non-empty content. */
	bool isValid() {
		return ( name.length() && content && contentSize );
	}
	/*! Copy content bytes into this entry (NUL-terminated internally).
		@param content Source bytes (copied; caller retains ownership).
		@param size Byte count. @return true on success, false if @p content is NULL. */
	bool setContent(const char* content, uint32 size) {
		if (!content) return false;
		this->contentSize = size;
		this->content = new char[size+1];
		memcpy(this->content, content, size);
		this->content[size] = 0;
		return true;
	}

	std::string name;		/*!< Form field name (Content-Disposition name attribute). */
	std::string filename;	/*!< Original filename for file uploads (empty for plain fields). */
	std::string type;		/*!< MIME Content-Type of this part. */
	uint32 contentSize;		/*!< Size of ::content in bytes. */
	char* content;			/*!< Owned content bytes (NUL-terminated for convenience). */
};

/*! A parsed or generated HTTP request (also used for WebSocket upgrade handshakes).

	Dual-role container: on the server side it is filled incrementally from
	socket bytes via processHeader() + processContent(); on the client side
	it is built with one of the createRequest() overloads and serialised from
	the ::data buffer. Header fields land in ::entries, URI/query and form
	parameters in ::params, and multipart POST parts in ::postEntries. The
	whole raw request (header + body) lives in the owned ::data buffer.
	@note Not thread-safe; a request object belongs to one connection thread.
	@ingroup Networking */
class HTTPRequest {
public:

	/*! Build a client-side WebSocket upgrade request (RFC 6455 handshake).
		@param uri Request path. @param host Host header value.
		@param protocolName Optional Sec-WebSocket-Protocol value.
		@param origin Optional Origin header value.
		@return A new request owned by the caller, or NULL on failure. */
	static HTTPRequest* CreateWebsocketRequest(const char* uri, const char* host, const char* protocolName, const char* origin);

//	HTTPRequest(uint64 startRecTime = 0);
	/*! Construct an empty request.
		@param source Packed uint64 endpoint of the sender (0 if local).
		@param startRecTime Receive start timestamp; defaults to now when 0. */
	HTTPRequest(uint64 source = 0, uint64 startRecTime = 0);
	/*! Deep-copy constructor. @param req Request to copy (not modified). */
	HTTPRequest(HTTPRequest* req);
	virtual ~HTTPRequest();

	// Reading it in from a socket
	/*! Parse the HTTP header block from raw bytes.
		@param buffer Raw bytes from the socket (must contain the full header up to CRLFCRLF).
		@param size Number of bytes at @p buffer.
		@param isInvalid Out: set true when the bytes are definitely not valid HTTP.
		@return true when the header was fully parsed; false when more data is needed or invalid. */
	bool processHeader(const char* buffer, uint32 size, bool &isInvalid);
	/*! Append body bytes after the header has been parsed.
		@param buffer Body bytes. @param size Byte count.
		@return true when the body is complete (Content-Length reached). */
	bool processContent(const char* buffer, uint32 size);
	/*! Look up a header field (case-insensitive).
		@param entry Header name, e.g. "Content-Type".
		@return The value, or NULL if absent. Pointer valid while this object lives. */
	const char* getHeaderEntry(const char* entry);
	/*! @return The request line method string (e.g. "GET"), or NULL. */
	const char* getRequest();
	/*! @return The request URI path (without query parameters), or NULL. */
	const char* getURI();
	/*! @return The protocol/version string (e.g. "HTTP/1.1"), or NULL. */
	const char* getProtocol();
	/*! Decode the Basic Authorization header. @return "user:password" plaintext, or empty. */
	std::string decodeBasicAuthorization();
	/*! @return The raw base64 Basic Authorization credential, or NULL if absent. */
	const char* getBasicAuthorization();
	/*! @return The username from the Basic Authorization header, or empty. */
	std::string getBasicAuthorizationUser();
	/*! @return The password from the Basic Authorization header, or empty. */
	std::string getBasicAuthorizationPassword();
	/*! Set the Authorization header from a pre-encoded base64 credential.
		@param authB64 base64("user:password"). @return true on success. */
	bool setBasicAuthorization(const char* authB64);
	/*! Set the Authorization header from plaintext credentials (encoded internally).
		@param user Username. @param password Password. @return true on success. */
	bool setBasicAuthorization(const char* user, const char* password);
	/*! Look up a URI query or form parameter by name.
		@param entry Parameter name. @return The value, or NULL if absent. */
	const char* getParameter(const char* entry);
	/*! Get a multipart POST part's content, size and MIME type.
		@param entry Part name. @param size Out: content size. @param type Out: MIME type pointer.
		@return Content bytes, or NULL if the part does not exist. Owned by this object. */
	const char* getPostData(const char* entry, uint32& size, const char** type);
	/*! Get a multipart POST part's content and size.
		@param entry Part name. @param size Out: content size.
		@return Content bytes, or NULL. Owned by this object. */
	const char* getPostData(const char* entry, uint32& size);
	/*! @param entry Part name. @return The MIME type of a multipart POST part, or NULL. */
	const char* getPostDataType(const char* entry);
	/*! Parse "a=1&b=2" style parameters from a URI query string into ::params.
		@param text Query text after '?'. @return true on success. */
	bool parseURIParameters(const char* text);
	/*! Parse a URL-encoded form body into ::params.
		@param content Body bytes. @param size Byte count. @return true on success. */
	bool parseContentParameters(const char* content, uint32 size);
	/*! Parse one chunk of a multipart body (between boundaries) into ::postEntries.
		@param chunk Chunk bytes. @param size Byte count. @return true on success. */
	bool parseContentChunk(const char* chunk, uint32 size);

	/*! Convert this HTTP request into a binary DataMessage (URI, params and
		content mapped to message fields) for handing into message-based systems.
		@return A new DataMessage owned by the caller, or NULL. */
	DataMessage* convertToMessage();

	/*! @return true when this request is a WebSocket upgrade handshake
		(Connection: Upgrade + Upgrade: websocket headers present). */
	bool isWebsocketUpgrade();

	/*! Get the (decoded) request body.
		@param size Out: body size in bytes. @return Body bytes, or NULL. Owned by this object. */
	const char* getContent(uint32& size);
	/*! Get the raw, untransformed body bytes as received.
		@param size Out: body size in bytes. @return Body bytes, or NULL. Owned by this object. */
	const char* getRawContent(uint32& size);

	/*! Build a simple request with optional raw body.
		@param type HTTP_* method id. @param host Host header value. @param uri Request path.
		@param content Optional body (copied). @param contentSize Body size.
		@param keepAlive Request a persistent connection when true.
		@param ifModifiedSince If-Modified-Since timestamp (ms epoch; 0 = omit).
		@return true when the raw request was assembled into ::data. */
	bool createRequest(uint8 type, const char* host, const char* uri, const char* content, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince);
	/*! Build a request with custom header fields and no body.
		@param type HTTP_* method id. @param host Host header. @param uri Request path.
		@param headerEntries Extra header name/value pairs.
		@param keepAlive Persistent connection flag. @param ifModifiedSince ms epoch or 0.
		@return true on success. */
	bool createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, bool keepAlive, uint64 ifModifiedSince);
	/*! Build a request with custom headers and a typed raw body.
		@param type HTTP_* method id. @param host Host header. @param uri Request path.
		@param headerEntries Extra header name/value pairs.
		@param content Body bytes (copied). @param contentType MIME type of the body.
		@param contentSize Body size. @param keepAlive Persistent connection flag.
		@param ifModifiedSince ms epoch or 0. @return true on success. */
	bool createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, const char* content, const char* contentType, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince);
	/*! Build a request whose body is a single form entry.
		@param type HTTP_* method id. @param host Host header. @param uri Request path.
		@param headerEntries Extra header name/value pairs.
		@param bodyEntry The single body part (not owned; content copied).
		@param keepAlive Persistent connection flag. @param ifModifiedSince ms epoch or 0.
		@return true on success. */
	bool createRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, HTTPPostEntry* bodyEntry, bool keepAlive, uint64 ifModifiedSince);
	/*! Build a multipart/form-data request from several named parts.
		@param type HTTP_* method id (typically HTTP_POST). @param host Host header.
		@param uri Request path. @param headerEntries Extra header name/value pairs.
		@param bodyEntries Named body parts (not owned; content copied).
		@param keepAlive Persistent connection flag. @param ifModifiedSince ms epoch or 0.
		@return true on success. */
	bool createMultipartRequest(uint8 type, const char* host, const char* uri, std::map<std::string, std::string>& headerEntries, std::map<std::string, HTTPPostEntry*>& bodyEntries, bool keepAlive, uint64 ifModifiedSince);
	/*! Fill this object with a WebSocket upgrade handshake request.
		@param uri Request path. @param host Host header.
		@param protocolName Optional Sec-WebSocket-Protocol. @param origin Optional Origin.
		@return true on success. */
	bool createWebsocketRequest(const char* uri, const char* host, const char* protocolName, const char* origin);

	/*! @return Total size of the serialised request (header + body) in bytes. */
	uint32 getSize();
	
	uint8 type;				/*!< HTTP_* method id. */
	uint64 time;			/*!< Timestamp when reception/creation started (ms epoch). */
	uint64 endReceiveTime;	/*!< Timestamp when the request was fully received (ms epoch). */
	uint64 source;			/*!< Packed uint64 endpoint of the sender (0 if local). */
	uint32 headerLength;	/*!< Length of the header block in ::data, in bytes. */
	uint32 contentLength;	/*!< Body length (from Content-Length / parsing), in bytes. */
	uint64 ifModifiedSince;	/*!< Parsed If-Modified-Since timestamp (ms epoch; 0 = absent). */
	bool keepAlive;			/*!< Whether the connection should be kept open after replying. */
	char* data;				/*!< Owned raw request bytes (header followed by body). */

	std::map<std::string, std::string> entries;				/*!< Parsed header fields (name to value). */
	std::map<std::string, std::string> params;				/*!< URI query and form parameters (name to value). */
	std::map<std::string, HTTPPostEntry*> postEntries;		/*!< Multipart POST parts by name (owned). */
	std::string postBoundary;								/*!< Multipart boundary string, when applicable. */
};

/*! Metadata for one binary attachment chunk inside a JSONM container.
	@ingroup Networking */
struct JSONMEntry {
	uint32 chunk;		/*!< Chunk index within the container (1-based). */
	uint64 size;		/*!< Attachment size in bytes. */
	uint64 offset;		/*!< Byte offset of the attachment within the container data. */
	std::string name;	/*!< Attachment name. */
	std::string type;	/*!< MIME type of the attachment. */
	/*! @return A one-line human-readable summary of this entry. */
	std::string getInfoString() {
		return utils::StringFormat("[%u] %s {%s} (%s / %llu)\n",
			chunk, name.c_str(), type.c_str(), utils::BytifySize((uint32)size).c_str(), offset);
	}
	/*! @return This entry's metadata as a JSON object string. */
	std::string toJSON() {
		return utils::StringFormat("{\"name\": \"%s\", \"bytesize\": %llu, \"mimetype\": \"%s\"}",
			name.c_str(), size, type.c_str());
	}
};

/*! JSONM: a JSON document with attached binary chunks in one contiguous buffer.

	Wire format used to move JSON plus binary payloads (images, audio, etc.)
	as a single blob: the JSON text comes first and describes the attachments
	(name, size, MIME type); the raw binary chunks follow at recorded
	offsets. Construct from received bytes to parse, or build up with
	setJSON() + addData() to send. Internally serialised into ::jmData.
	@note Guarded by an internal mutex, so concurrent readers/writers on the
	same object are safe, but typical usage is single-threaded.
	@ingroup Networking */
class JSONM {
public:
	JSONM();
	/*! Parse a complete JSONM container from raw bytes.
		@param data Container bytes (copied). @param size Total size in bytes. */
	JSONM(const char* data, uint64 size);
	/*! Create a container from JSON text only (no attachments yet).
		@param json NUL-terminated JSON text (copied). */
	JSONM(const char* json);
	~JSONM();

	/*! @return Total serialised size (JSON plus all attachments) in bytes. */
	uint64 getSize();
	/*! @return Number of binary attachment chunks. */
	uint32 getCount();

	/*! @return true when the container holds parseable JSON and consistent chunk metadata. */
	bool isValid();
	/*! @return The JSON text portion of the container. */
	std::string getJSON();
	/*! Get an attachment's bytes by name.
		@param name Attachment name. @param size Out: attachment size.
		@return Pointer into the internal buffer (valid while this object lives), or NULL. */
	const char* getData(const char* name, uint64& size);
	/*! Get an attachment's bytes by chunk index.
		@param chunk Chunk index. @param size Out: attachment size.
		@return Pointer into the internal buffer, or NULL. */
	const char* getData(uint32 chunk, uint64& size);
	/*! @param name Attachment name. @return Its MIME type, or empty. */
	std::string getDataType(const char* name);
	/*! @param chunk Chunk index. @return Its MIME type, or empty. */
	std::string getDataType(uint32 chunk);
	/*! @param chunk Chunk index. @return Its attachment name, or empty. */
	std::string getDataName(uint32 chunk);

	/*! Replace the JSON text portion (multipart metadata is regenerated).
		@param json NUL-terminated JSON text. @return true on success. */
	bool setJSON(const char* json);
	/*! Append a binary attachment.
		@param name Attachment name. @param data Bytes (copied). @param size Byte count.
		@param type MIME type. @return The new chunk index, or 0 on failure. */
	uint32 addData(const char* name, const char* data, uint64 size, const char* type);

	/*! @return Multi-line human-readable summary of all chunks. */
	std::string getInfoString();
	/*! Convert this container into a binary DataMessage.
		@return A new DataMessage owned by the caller, or NULL. */
	DataMessage* convertToMessage();

	char* jmData;	/*!< Owned serialised container buffer (JSON + attachments). */
	uint64 jmSize;	/*!< Total size of ::jmData in bytes. */
	uint64 mOffset;	/*!< Byte offset where the binary multipart section begins. */
	std::map<uint32, JSONMEntry> entries;	/*!< Attachment metadata by chunk index. */

private:
	bool setRawJSON(const char* json);
	bool extractMultipartInfo();
	bool updateMultipartJSON();

	utils::Mutex mutex;
};


/*! One WebSocket frame/message (RFC 6455): parsing, generation and control frames.

	Represents a single WebSocket data unit, either parsed from socket bytes
	(processHeader() + processContent(), possibly across multiple fragments —
	see ::isFinal and ::packages) or generated for sending via setData() /
	the CreatePing()/CreatePong()/CreateTerminationConfirmation() factories.
	The BHB0_* and BHB1_* constants are the RFC 6455 basic-header bit masks
	(FIN/RSV/opcode in byte 0, mask flag and 7-bit payload length in byte 1);
	extended payload lengths (16-bit and 64-bit) are read in network byte
	order. Client-to-server frames are masked with ::maskingKey per the RFC.

	Header layout parsed/produced (see also the file-level framing overview):
	@code
	 byte 0            byte 1              optional extension
	+-+-+-+-+-------+ +-+-------------+  +----------------+--------------+
	|F|R|R|R|opcode | |M| payload len |  | ext. len 2/8 B | mask key 4 B |
	|I|S|S|S|(4 bit)| |A|   (7 bit)   |  | if len=126/127 | if MASK set  |
	|N|V|V|V|       | |S|             |  | (big-endian)   |              |
	+-+-+-+-+-------+ +-+-------------+  +----------------+--------------+
	@endcode
	A header is thus 2..14 bytes (::basic_header_length ..
	::max_header_length). Fragmented messages (FIN clear on all but the last
	frame) are reassembled by HTTPProtocol::ReceiveWebsocketData(), counting
	fragments in ::packages.
	@note Not thread-safe; owned by one connection thread.
	@ingroup Networking */
class WebsocketData {
public:
	/*! Frame content type, mapping to the RFC 6455 opcode. */
	enum DataType { NONE = 0, TEXT = 1, BINARY = 2, CLOSE = 8, PING = 9, PONG = 10 } dataType;
	/*! Parsing status of this frame: not started, awaiting more bytes, or fully received. */
	enum WSSTATUS { IDLE = 0, INCOMPLETE = 1, COMPLETE = 2 } status;

	// masks for fields in the basic header (RFC 6455 §5.2)
	static uint8 const BHB0_OPCODE = 0x0F;	/*!< Byte 0: 4-bit opcode (see DataType). */
	static uint8 const BHB0_RSV3 = 0x10;	/*!< Byte 0: reserved bit 3 (extensions; must be 0). */
	static uint8 const BHB0_RSV2 = 0x20;	/*!< Byte 0: reserved bit 2 (extensions; must be 0). */
	static uint8 const BHB0_RSV1 = 0x40;	/*!< Byte 0: reserved bit 1 (extensions; must be 0). */
	static uint8 const BHB0_FIN = 0x80;		/*!< Byte 0: FIN — set on the final fragment of a message. */
	static uint8 const BHB1_PAYLOAD = 0x7F;	/*!< Byte 1: 7-bit basic payload length / extension code. */
	static uint8 const BHB1_MASK = 0x80;	/*!< Byte 1: MASK — payload is XOR-masked (required client-to-server). */
	static uint8 const payload_size_code_16bit = 0x7E; /*!< Basic length 126: a 16-bit big-endian length follows. */
	static uint8 const payload_size_code_64bit = 0x7F; /*!< Basic length 127: a 64-bit big-endian length follows. */

	static uint8 const basic_header_length = 2;
	/// Maximum length of a WebSocket header
	static uint8 const max_header_length = 14;
	/// Maximum length of the variable portion of the WebSocket header
	static uint8 const max_extended_header_length = 12;
	/// Maximum size of a basic WebSocket payload
	static uint8 const payload_size_basic = 125;
	/// Maximum size of an extended WebSocket payload (basic payload = 126)
	static uint16 const payload_size_extended = 0xFFFF; // 2^16, 65535
												  /// Maximum size of a jumbo WebSocket payload (basic payload = 127)
	static uint64 const payload_size_jumbo = 0x7FFFFFFFFFFFFFFFLL;//2^63

	//	HTTPRequest(uint64 startRecTime = 0);
	/*! Construct an empty frame.
		@param source Packed uint64 endpoint of the sender (0 if local).
		@param startRecTime Receive start timestamp; defaults to now when 0. */
	WebsocketData(uint64 source = 0, uint64 startRecTime = 0);
	/*! Deep-copy constructor. @param req Frame to copy (not modified). */
	WebsocketData(WebsocketData* req);
	virtual ~WebsocketData();

	/*! @return A new CLOSE frame acknowledging a termination request (caller owns it). */
	static WebsocketData* CreateTerminationConfirmation();
	/*! @return A new PONG control frame (caller owns it). */
	static WebsocketData* CreatePong();
	/*! @return A new PING control frame (caller owns it). */
	static WebsocketData* CreatePing();

	/*! @return true if this is a CLOSE frame initiating termination. */
	bool isTerminationRequest();
	/*! @return true if this is a CLOSE frame confirming termination. */
	bool isTerminationConfirmation();
	/*! @return true once the full payload has been received (status == COMPLETE). */
	bool isComplete();
	/*! @return true if this is a PING control frame. */
	bool isPing();
	/*! @return true if this is a PONG control frame. */
	bool isPong();

	/*! Set the payload and build the serialised frame for sending.
		@param dataType TEXT, BINARY or a control type.
		@param maskData Apply client-side masking (required for client-to-server frames).
		@param data Payload bytes (copied; may be NULL for empty control frames).
		@param size Payload size in bytes.
		@return true when the frame was assembled.
		@warning The current sender writes at most a 16-bit extended length: a
		single frame larger than 65535 bytes would be framed incorrectly (the
		64-bit "jumbo" branch is unreachable — see the implementation note in
		NetworkProtocols.cpp). Keep individual frames ≤ 64 KB; the receive path
		handles all RFC sizes. */
	bool setData(DataType dataType, bool maskData, const char* data = NULL, uint64 size = 0);
	/*! Get the serialised on-the-wire frame bytes (header + masked payload).
		@param size Out: total frame size. @return Frame bytes owned by this object, or NULL. */
	const char* getRawData(uint64& size);

	// Reading it in from a socket
	/*! Parse the frame header from raw bytes (handles 16/64-bit extended lengths).
		@param buffer Raw bytes. @param size Byte count.
		@param isInvalid Out: set true when the bytes cannot be a WebSocket frame.
		@return true when the header was parsed; false if more bytes are needed or invalid. */
	bool processHeader(const char* buffer, uint64 size, bool &isInvalid);
	/*! Append (and unmask) payload bytes after the header has been parsed.
		@param buffer Payload bytes. @param size Byte count.
		@return true when the payload is complete. */
	bool processContent(const char* buffer, uint64 size);
	//const char* getHeaderEntry(const char* entry);
	//const char* getRequest();
	//const char* getURI();
	//const char* getProtocol();
	//std::string decodeBasicAuthorization();
	//const char* getBasicAuthorization();
	//std::string getBasicAuthorizationUser();
	//std::string getBasicAuthorizationPassword();
	//bool setBasicAuthorization(const char* authB64);
	//bool setBasicAuthorization(const char* user, const char* password);
	//const char* getParameter(const char* entry);
	//const char* getPostData(const char* entry, uint32& size, const char** type);
	//const char* getPostData(const char* entry, uint32& size);
	//const char* getPostDataType(const char* entry);
	//bool parseURIParameters(const char* text);
	//bool parseContentParameters(const char* content, uint32 size);
	//bool parseContentChunk(const char* chunk, uint32 size);

	//DataMessage* convertToMessage();

	/*! Get the decoded (unmasked) payload.
		@param size Out: payload size. @return Payload bytes owned by this object, or NULL. */
	const char* getContent(uint64& size);

	//bool createRequest(uint8 type, const char* host, const char* uri, const char* content, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince);
	/*! @return Number of payload bytes received so far (this frame). */
	uint64 getContentSize();
	/*! @return The declared payload size from the frame header. */
	uint64 getPayloadSize();

	bool isFinal;			/*!< FIN bit: true when this is the last fragment of a message. */
	uint64 time;			/*!< Timestamp when reception/creation started (ms epoch). */
	uint64 endReceiveTime;	/*!< Timestamp when the frame was fully received (ms epoch). */
	uint64 source;			/*!< Packed uint64 endpoint of the sender (0 if local). */
	//uint64 ifModifiedSince;
	//bool keepAlive;
	uint32 headerLength;	/*!< Parsed frame header length in bytes (2..14). */
	uint64 contentSize;		/*!< Payload bytes accumulated so far. */
	uint64 payloadSize;		/*!< Declared payload length from the header. */
	uint8 opcode;			/*!< RFC 6455 opcode (see DataType). */
	uint32 maskingKey;		/*!< 32-bit masking key (0 when unmasked, i.e. server-to-client). */
	uint32 packages;		/*!< Number of fragments accumulated into this message. */

	char* data;				/*!< Owned decoded payload bytes. */
	char* rawData;			/*!< Owned serialised frame bytes (for sending). */

	//std::map<std::string, std::string> entries;
	//std::map<std::string, std::string> params;
	//std::map<std::string, HTTPPostEntry*> postEntries;
	//std::string postBoundary;
};


/*! @name Telnet line-ending styles */
/*! @{ */
#define TELNET_WINDOWS	1	/*!< CRLF (\\r\\n) line endings. */
#define TELNET_UNIX		2	/*!< LF (\\n) line endings. */
/*! @} */

/*! One line of Telnet-style text traffic.

	The unit of exchange for the ::PROTOCOL_TELNET protocol — a plain text
	line plus its line-ending convention, timestamp and source endpoint.
	Used for interactive console/diagnostic connections.
	@ingroup Networking */
class TelnetLine {
public:
	/*! Construct an empty line. @param source Packed uint64 endpoint of the sender. */
	TelnetLine(uint64 source);
	virtual ~TelnetLine();

	/*! Set the line text from a NUL-terminated string.
		@param buffer Text (copied). @param addCR Append the line ending when true.
		@return true on success. */
	bool setLine(const char* buffer, bool addCR);
	/*! Set the line text from a length-delimited buffer.
		@param buffer Text bytes (copied). @param len Byte count.
		@param addCR Append the line ending when true. @return true on success. */
	bool setLine(const char* buffer, uint32 len, bool addCR = false);
	/*! Take ownership of an existing buffer as the line text (no copy).
		@param buffer Heap buffer this object will delete[]. @param len Byte count.
		@return true on success. */
	bool giveData(char* buffer, uint32 len);
	/*! @return The line size in bytes (excluding terminator). */
	uint32 getSize();
	/*! Select the line-ending convention.
		@param type ::TELNET_WINDOWS or ::TELNET_UNIX. @return true on success. */
	bool setCR(uint8 type);

	char* data;		/*!< Owned line text. */
	char cr[3];		/*!< Active line-ending characters (\\r\\n or \\n, NUL-terminated). */
	uint64 time;	/*!< Timestamp of creation/receipt (ms epoch). */
	uint64 source;	/*!< Packed uint64 endpoint of the sender. */
	uint32 size;	/*!< Line length in bytes. */
	uint32 user;	/*!< Optional user/session tag for multi-user telnet servers. */
};

// HTTP/1.0 200 OK
// HTTP/1.0 500 Internal Server Error  <H1>Error while creating internal page</H1>
// HTTP/1.0 200 OK
// Cache-Control: No-Cache
// HTTP/1.0 404 Object Not Found   <H1>File not found</H1>
// HTTP/1.0 403 Access Forbidden   <H1>Access Denied</H1>

/*! @name HTTP reply status codes
	CMSDK-internal status ids used with HTTPReply::createErrorPage() etc.;
	index into ::HTTP_Status for the full status line text. @{ */
#define HTTP_OK						0	/*!< 200 OK */
#define HTTP_MOVED_PERMANENTLY		1	/*!< 301 Moved Permanently */
#define HTTP_USE_LOCAL_COPY			2	/*!< 304 Not Modified (use cached copy) */
#define HTTP_MALFORMED_URL			3	/*!< 400 Bad Request */
#define HTTP_UNAUTHORIZED			4	/*!< 401 Unauthorized (triggers Basic auth) */
#define HTTP_ACCESS_DENIED			5	/*!< 403 Forbidden */
#define HTTP_FILE_NOT_FOUND			6	/*!< 404 Not Found */
#define HTTP_INTERNAL_ERROR			7	/*!< 500 Internal Server Error (page generation) */
#define HTTP_NOT_IMPLEMENTED		8	/*!< 501 Not Implemented */
#define HTTP_SERVICE_NOT_AVAILABLE	9	/*!< 503 Service Unavailable */
#define HTTP_GATEWAY_TIMEOUT		10	/*!< 504 Gateway Timeout */
#define HTTP_SERVER_UNAVAILABLE		11	/*!< 500 (backend server unavailable) */
#define HTTP_SERVER_NOREPLY			12	/*!< 500 (no reply from backend server) */
#define HTTP_SERVER_MALFORMED_REPLY	13	/*!< 500 (malformed backend reply) */
#define HTTP_SWITCH_PROTOCOL		14	/*!< 101 Switching Protocols (WebSocket upgrade) */
/*! @} */

/*! Full HTTP status lines (with inline error bodies for error cases),
	indexed by the HTTP_* status ids above. */
static char HTTP_Status[][128] =
{
"HTTP/1.1 200 OK",
"HTTP/1.1 301 Moved Permanently",
"HTTP/1.1 304 Use Local Copy",
"HTTP/1.1 400 Bad Request <H1>Malformed URL</H1>",
"HTTP/1.1 401 Unauthorized",
"HTTP/1.1 403 Access Denied",
"HTTP/1.1 404 Object Not Found",
"HTTP/1.1 500 Internal Server Error  <H1>Error while creating page</H1>",
"HTTP/1.1 501 Not Implemented  <H1>Not implemented</H1>",
"HTTP/1.1 503 Service Unavailable  <H1>Service Unavailable</H1>",
"HTTP/1.1 504 Gateway Timeout  <H1>Gateway Timeout</H1>",
"HTTP/1.1 500 Internal Server Error  <H1>Server unavailable</H1>",
"HTTP/1.1 500 Internal Server Error  <H1>No reply from server</H1>",
"HTTP/1.1 500 Internal Server Error  <H1>Malformed server reply</H1>",
"HTTP/1.1 101 Switching Protocols"
};

/*! A parsed or generated HTTP response.

	Counterpart to HTTPRequest: on the client side it is filled from socket
	bytes (processHeader()/processContent()); on the server side it is built
	with createPage(), createFromFile(), createErrorPage() and friends, then
	serialised from the owned ::data buffer. Also carries the "101 Switching
	Protocols" reply that completes a WebSocket handshake.
	@note Not thread-safe; owned by one connection thread.
	@ingroup Networking */
class HTTPReply {
public:
	/*! Build a canned error reply.
		@param type One of the HTTP_* status ids. @return New reply owned by the caller. */
	static HTTPReply* CreateErrorReply(uint8 type);
	/*! Build a 401 reply requesting Basic authentication.
		@param realm Realm name shown by the browser. @return New reply owned by the caller. */
	static HTTPReply* CreateAuthorizationReply(const char* realm);
	/*! Build the "101 Switching Protocols" reply for a WebSocket handshake.
		@param key Client's Sec-WebSocket-Key (the SHA1-based accept token is derived from it).
		@param version Client's Sec-WebSocket-Version. @return New reply owned by the caller. */
	static HTTPReply* CreateWebsocketHTTPReply(const char* key, const char* version);

	/*! Construct an empty reply. @param source Packed uint64 endpoint of the sender. */
	HTTPReply(uint64 source = 0);
	/*! Deep-copy constructor. @param reply Reply to copy (not modified). */
	HTTPReply(HTTPReply* reply);
	virtual ~HTTPReply();
	
	/*! @return true when this reply completes a WebSocket upgrade (status 101). */
	bool isWebsocketUpgrade();

	// Reading it in from a socket
	/*! Parse the response header block from raw bytes.
		@param buffer Raw bytes. @param size Byte count.
		@param isInvalid Out: set true when the bytes are not valid HTTP.
		@return true when the header was fully parsed. */
	bool processHeader(const char* buffer, uint32 size, bool &isInvalid);
	/*! Append body bytes after the header has been parsed.
		@param buffer Body bytes. @param size Byte count.
		@return true when the body is complete. */
	bool processContent(const char* buffer, uint32 size);
	/*! Look up a header field (case-insensitive).
		@param entry Header name. @return The value, or NULL. Valid while this object lives. */
	const char* getHeaderEntry(const char* entry);
	/*! @return The protocol/version string from the status line (e.g. "HTTP/1.1"). */
	const char* getProtocol();

	/*! Get the (decoded) response body.
		@param size Out: body size. @return Body bytes owned by this object, or NULL. */
	const char* getContent(uint32& size);
	/*! Get the raw, untransformed body bytes.
		@param size Out: body size. @return Body bytes owned by this object, or NULL. */
	const char* getRawContent(uint32& size);

	// Generating the data
	/*! Build a complete response with headers and content.
		@param status HTTP_* status id. @param time Current time (ms epoch) for the Date header.
		@param serverName Server header value. @param lastMod Last-Modified timestamp (0 = omit).
		@param keepAlive Keep the connection open after sending.
		@param cache Allow client caching (otherwise Cache-Control: No-Cache).
		@param contentType MIME type of @p content.
		@param content Body bytes (copied). @param contentSize Body size (strlen when 0 and text).
		@param additionalHeaderEntries Optional extra raw header lines.
		@return true when the reply was assembled. */
	bool createPage(uint8 status, uint64 time, const char* serverName,
		uint64 lastMod, bool keepAlive, bool cache, const char* contentType,
		const char* content, uint32 contentSize = 0, const char* additionalHeaderEntries = NULL);

	/*! Build a response by reading a file from disk (MIME type from extension).
		Replies 304 Use-Local-Copy when the file is unchanged since @p ifLastMod.
		@param time Current time (ms epoch). @param serverName Server header value.
		@param ifLastMod Client's If-Modified-Since timestamp (0 = none).
		@param keepAlive Keep the connection open. @param cache Allow client caching.
		@param filename Path of the file to serve.
		@return true when the reply was assembled (404 error page when missing). */
	bool createFromFile(uint64 time, const char* serverName,
		uint64 ifLastMod, bool keepAlive, bool cache,
		const char* filename);

	/*! Build a CORS-preflight (OPTIONS) response.
		@param status HTTP_* status id. @param time Current time. @param serverName Server header.
		@param keepAlive Keep the connection open. @param origin Access-Control-Allow-Origin value.
		@param operations Access-Control-Allow-Methods value. @return true on success. */
	bool createOptionsResponse(uint8 status, uint64 time, const char* serverName, bool keepAlive, const char* origin, const char* operations);
	/*! Build a canned error page for the given status.
		@param status HTTP_* status id. @param time Current time. @param serverName Server header.
		@param keepAlive Keep the connection open. @return true on success. */
	bool createErrorPage(uint8 status, uint64 time, const char* serverName, bool keepAlive);
	/*! Build a 401 Basic-auth challenge. @param realm Realm name. @return true on success. */
	bool createAuthorizationReply(const char* realm);
	/*! Build the 101 WebSocket handshake completion in-place.
		@param key Client's Sec-WebSocket-Key. @param version Sec-WebSocket-Version.
		@return true on success. */
	bool createWebsocketHTTPReply(const char* key, const char* version);

	/*! @return Total size of the serialised reply (header + body) in bytes. */
	uint32 getSize();

	uint8  type;			/*!< HTTP_* status id of this reply. */
	uint64 time;			/*!< Timestamp of creation/receipt (ms epoch). */
	uint64 source;			/*!< Packed uint64 endpoint of the sender (0 if local). */
	uint32 headerLength;	/*!< Header block length within ::data, in bytes. */
	uint32 contentLength;	/*!< Body length in bytes. */
	bool keepAlive;			/*!< Whether the connection stays open after this reply. */
	char* data;				/*!< Owned raw reply bytes (header followed by body). */

	std::map<std::string, std::string> entries;	/*!< Parsed header fields (name to value). */
	std::map<std::string, std::string> params;	/*!< Auxiliary parsed parameters. */
};


/////////////////////////////////////////////////////////////
// Auto Detect Protocols
/////////////////////////////////////////////////////////////

/*! Base class for the protocol auto-detection framework.

	Each protocol class implements static CheckBufferForCompatibility(),
	which sniffs the first bytes arriving on a fresh connection to decide
	whether they match its wire format, and InitialiseConversation() to
	perform any initial exchange. NetworkChannel::autoDetectConnection() uses
	these to multiplex several protocols on one listening port.
	@ingroup Networking */
class NetworkProtocol {
public:
	uint8 protocol;	/*!< PROTOCOL_* id of this handler. */
	NetworkProtocol() {protocol = 0;}
	/*! Sniff whether @p buffer looks like this protocol.
		@param buffer First bytes received. @param length Byte count.
		@return true if compatible. Base implementation always returns false. */
	static bool CheckBufferForCompatibility(const char* buffer, uint32 length) {return false;};
	/*! Perform any protocol-specific initial exchange on a new connection.
		@param con The fresh connection. @return true on success. */
	static bool InitialiseConversation(NetworkConnection* con) {return false;};
};

/*! Server-side HTTP/WebSocket protocol handler (static send/receive helpers).

	Reads HTTPRequests and WebSocket frames from a connection and writes
	HTTPReplies / frames back, with millisecond timeouts. Used by
	NetworkChannel::HTTPServerRun() — the built-in HTTP server thread.
	@ingroup Networking */
class HTTPProtocol : public NetworkProtocol
{
public:
	HTTPProtocol() {protocol = PROTOCOL_HTTP_SERVER;}
	/*! @return true when @p buffer starts like an HTTP request (method line). */
	static bool CheckBufferForCompatibility(const char* buffer, uint32 length);
	/*! No initial exchange needed for HTTP. @param con Connection. @return true. */
	static bool InitialiseConversation(NetworkConnection* con);
	/*! Serialise and send a reply. @param con Connection. @param reply Reply (caller keeps ownership). @return true if fully sent. */
	static bool SendHTTPReply(NetworkConnection* con, HTTPReply* reply);
	/*! Read a full reply from the connection.
		@param con Connection. @param timeout Max wait in ms.
		@return New reply owned by the caller, or NULL on timeout/parse failure. Blocking. */
	static HTTPReply* ReceiveHTTPReply(NetworkConnection* con, uint32 timeout);
	/*! Serialise and send a request. @param con Connection. @param req Request (caller keeps ownership). @return true if fully sent. */
	static bool SendHTTPRequest(NetworkConnection* con, HTTPRequest* req);
	/*! Read a full request from the connection.
		@param con Connection. @param timeout Max wait in ms.
		@return New request owned by the caller, or NULL. Blocking. */
	static HTTPRequest* ReceiveHTTPRequest(NetworkConnection* con, uint32 timeout);
	/*! Read one complete WebSocket message (reassembling fragments).
		@param con Connection. @param timeout Max wait in ms.
		@return New frame owned by the caller, or NULL. Blocking. */
	static WebsocketData* ReceiveWebsocketData(NetworkConnection* con, uint32 timeout);
	/*! Send a WebSocket frame. @param con Connection. @param wsData Frame (caller keeps ownership). @return true if fully sent. */
	static bool SendWebsocketData(NetworkConnection* con, WebsocketData* wsData);
};

/*! Client-side HTTP protocol handler (mirror of HTTPProtocol for outgoing requests).
	@ingroup Networking */
class HTTPClientProtocol : public NetworkProtocol
{
public:
	HTTPClientProtocol() {protocol = PROTOCOL_HTTP_CLIENT;}
	/*! @return true when @p buffer starts like an HTTP response (status line). */
	static bool CheckBufferForCompatibility(const char* buffer, uint32 length);
	/*! No initial exchange needed. @param con Connection. @return true. */
	static bool InitialiseConversation(NetworkConnection* con);
	/*! @copydoc HTTPProtocol::SendHTTPReply() */
	static bool SendHTTPReply(NetworkConnection* con, HTTPReply* reply);
	/*! @copydoc HTTPProtocol::ReceiveHTTPReply() */
	static HTTPReply* ReceiveHTTPReply(NetworkConnection* con, uint32 timeout);
	/*! @copydoc HTTPProtocol::SendHTTPRequest() */
	static bool SendHTTPRequest(NetworkConnection* con, HTTPRequest* req);
	/*! @copydoc HTTPProtocol::ReceiveHTTPRequest() */
	static HTTPRequest* ReceiveHTTPRequest(NetworkConnection* con, uint32 timeout);
};

/*! Binary DataMessage protocol handler: size-prefixed serialised messages.

	The native CMSDK inter-node protocol. A "frame" is simply the serialised
	DataMessage memory image: its first uint32 is the total size, followed by
	the DATAMESSAGEID object id — ReceiveMessage() peeks those 8 bytes, then
	reads exactly @c size bytes and hands the buffer to a DataMessage that
	adopts it (zero-copy). Used by NetworkChannel::MessageConnectionRun() and
	the Request* classes.
	@note The size field is not byte-order converted on the wire; both peers
	must share the same endianness (see the file-level byte-order notes).
	A blocking send/receive round-trip over an established connection:
	@code
	DataMessage* out = new DataMessage();
	out->setString("URI", "ping");
	MessageProtocol::SendMessage(con, out);              // con keeps nothing; we still own out
	delete out;
	DataMessage* in = MessageProtocol::ReceiveMessage(con, 3000); // we own in
	if (in) { delete in; }   // use it, then delete — caller owns the reply
	@endcode
	@ingroup Networking */
class MessageProtocol : public NetworkProtocol
{
public:
	MessageProtocol() {protocol = PROTOCOL_MESSAGE;}
	/*! @return true when @p buffer starts with the DataMessage frame signature. */
	static bool CheckBufferForCompatibility(const char* buffer, uint32 length);
	/*! Perform the initial message-protocol greeting. @param con Connection. @return true on success. */
	static bool InitialiseConversation(NetworkConnection* con);
	/*! Serialise and send one message.
		@param con Connection. @param msg Message (caller keeps ownership).
		@param receiver Destination endpoint for datagram transports (0 for TCP).
		@return true if fully sent. */
	static bool SendMessage(NetworkConnection* con, DataMessage* msg, uint64 receiver = 0);
	/*! Read one full message frame.
		@param con Connection. @param timeout Max wait in ms.
		@return New DataMessage owned by the caller, or NULL. Blocking. */
	static DataMessage* ReceiveMessage(NetworkConnection* con, uint32 timeout);
};

/*! Telnet line protocol handler for interactive text connections.
	@ingroup Networking */
class TelnetProtocol : public NetworkProtocol
{
public:
	TelnetProtocol() {protocol = PROTOCOL_TELNET;}
	/*! @return true when @p buffer looks like plain line-oriented text. */
	static bool CheckBufferForCompatibility(const char* buffer, uint32 length);
	/*! Send any initial prompt/negotiation. @param con Connection. @return true on success. */
	static bool InitialiseConversation(NetworkConnection* con);
	/*! Send one line (with its line ending). @param con Connection. @param line Line (caller keeps ownership). @return true if sent. */
	static bool SendTelnetLine(NetworkConnection* con, TelnetLine* line);
	/*! Read one line terminated by CR/LF.
		@param con Connection. @param timeout Max wait in ms.
		@return New TelnetLine owned by the caller, or NULL. Blocking. */
	static TelnetLine* ReceiveTelnetLine(NetworkConnection* con, uint32 timeout);
	/*! Read a fixed number of bytes as one line (for raw/binary console input).
		@param con Connection. @param timeout Max wait in ms. @param size Exact byte count.
		@return New TelnetLine owned by the caller, or NULL. Blocking. */
	static TelnetLine* ReceiveTelnetLine(NetworkConnection* con, uint32 timeout, uint32 size);
};

// Pure in-memory unit test for the network protocol framing/parsing helpers
// (HTTPRequest build/parse round-trip and WebsocketData encode/decode
// round-trip). Uses no real sockets. Returns true on success.
bool NetworkProtocols_UnitTest();

}

#endif // _NETWORKPROTOCOLS_H_
