#pragma once // Recovered AMQP 0-8 surface used by tungsten's telemetry clients. The wire // protocol remains AMQP compatible; only the old Xbox socket/thread plumbing // is replaced by the portable idTCP and Win32 primitives already in idLib. #include "idlib/precompiled.h" #include "idlib/sys/sys_networking.h" #ifdef nullptr #undef nullptr #endif #ifdef _stricmp #undef _stricmp #endif #include #include #include template class idMQList { public: explicit idMQList(int initialGranularity = 16) : list(nullptr), num(0), size(0), granularity(static_cast(initialGranularity)), memTag(5), listStatic(0) {} idMQList(const idMQList& other) : idMQList(other.granularity) { *this = other; } ~idMQList() { delete[] list; } idMQList& operator=(const idMQList& other) { if (this == &other) return *this; SetNum(other.num); for (int index = 0; index < num; ++index) list[index] = other[index]; return *this; } void Clear() { num = 0; } int Num() const { return num; } T* Ptr() { return list; } const T* Ptr() const { return list; } T& operator[](int index) { return list[index]; } const T& operator[](int index) const { return list[index]; } void SetNum(int newNum) { if (newNum > size) Resize(newNum); if (newNum >= 0 && newNum <= size) num = newNum; } int Append(const T& value) { if (num == size && !Resize(num + 1)) return -1; list[num] = value; return num++; } bool RemoveIndex(int index) { if (index < 0 || index >= num) return false; for (int i = index; i + 1 < num; ++i) list[i] = list[i + 1]; --num; return true; } private: bool Resize(int required) { const int step = granularity > 0 ? granularity : 16; const int newSize = ((required + step - 1) / step) * step; T* replacement = new (std::nothrow) T[newSize]; if (replacement == nullptr) return false; for (int index = 0; index < num; ++index) replacement[index] = list[index]; delete[] list; list = replacement; size = newSize; return true; } T* list; int num; int size; short granularity; unsigned char memTag; unsigned char listStatic; }; class idMQKeyValue { public: idMQKeyValue() : key(), value() {} idMQKeyValue(const char* keyText, const char* valueText) : key(keyText), value(valueText) {} const idStr& GetKey() const { return key; } const idStr& GetValue() const { return value; } idStr key; idStr value; }; class idMQTable { public: idMQTable() : args(), hash{} {} void Clear() { args.Clear(); } int GetNumKeyVals() const { return args.Num(); } const idMQKeyValue* GetKeyVal(int index) const { return index >= 0 && index < args.Num() ? &args[index] : nullptr; } void Set(const char* key, const char* value) { const char* safeKey = key == nullptr ? "" : key; for (int index = 0; index < args.Num(); ++index) { if (_stricmp(args[index].key.c_str(), safeKey) == 0) { args[index].value = value == nullptr ? "" : value; return; } } args.Append(idMQKeyValue(safeKey, value == nullptr ? "" : value)); } const char* GetString(const char* key, const char* defaultValue = "") const { const char* safeKey = key == nullptr ? "" : key; for (int index = 0; index < args.Num(); ++index) { if (_stricmp(args[index].key.c_str(), safeKey) == 0) { return args[index].value.c_str(); } } return defaultValue; } private: idMQList args; std::uint32_t hash[8]; }; enum MQErrors_t { MQERROR_NONE = 0, MQERROR_UNKNOWN, MQERROR_INVALID_FRAME, MQERROR_FRAME_OVERFLOW, MQERROR_SESSION_NOT_FOUND, MQERROR_STREAM_ERROR, MQERROR_CONNECTION_START, MQERROR_BAD_SPEC_VERSION, MQERROR_AUTH_FAILED, MQERROR_UNEXPECTED_CLASS, MQERROR_UNEXPECTED_METHOD, MQERROR_SERVER_CLOSED }; enum MQAssemblerState_t { MQ_CMDSTATE_NEED_METHOD = 0, MQ_CMDSTATE_NEED_HEADER, MQ_CMDSTATE_NEED_BODY, MQ_CMDSTATE_COMPLETE }; enum amqpClassId_t { AMQP_CLASS_CONNECTION = 10, AMQP_CLASS_CHANNEL = 20, AMQP_CLASS_ACCESS = 30, AMQP_CLASS_EXCHANGE = 40, AMQP_CLASS_QUEUE = 50, AMQP_CLASS_BASIC = 60 }; enum amqpFrameType_t { AMQP_FRAME_METHOD = 1, AMQP_FRAME_HEADER = 2, AMQP_FRAME_BODY = 3, AMQP_FRAME_HEARTBEAT = 8, AMQP_FRAME_END = 0xCE }; struct amqpEndpoint_t { amqpEndpoint_t(); idStr host; unsigned short port; idStr username; idStr password; idStr vhost; unsigned short channelMax; unsigned short frameMax; unsigned short heartbeat; bool nonBlocking; bool silent; int minorVersion; int majorVersion; }; struct amqpShutdownReason_t { amqpShutdownReason_t() : replyCode(0), cId(0), mId(0), error(MQERROR_NONE), replyText("") {} unsigned short replyCode; unsigned short cId; unsigned short mId; MQErrors_t error; const char* replyText; }; class idMQFrame; class idMQMethod { public: virtual ~idMQMethod() = default; virtual int GetClassId() const = 0; virtual int GetMethodId() const = 0; virtual bool HasContent() const { return false; } virtual bool IsAsync() const { return false; } virtual void Populate(idMQFrame& frame) = 0; virtual void ToFrame(idMQFrame& frame) = 0; }; class idMQFrame { public: idMQFrame(); idMQFrame(unsigned char frameType, unsigned short channelNumber); void Clear(); void ReadData(void* destination, int length); void WriteData(const void* source, int length); unsigned char ReadByte(); unsigned short ReadUInt16(); unsigned int ReadUInt32(); std::uint64_t ReadUInt64(); bool ReadBool(); idStr ReadShortString(); void ReadString(idMQList& value); void ReadTable(idMQTable& value); bool ReadPropertyPresence(); void FinalizeReadPresence(); void WriteByte(unsigned char value); void WriteUInt16(unsigned short value); void WriteUInt32(unsigned int value); void WriteUInt64(std::uint64_t value); void WriteBool(bool value); void WriteShortString(const idStr& value); void WriteString(const idMQList& value); void WriteTable(const idMQTable& value); void WritePropertyPresence(bool present); void FinalizeWritePresence(); void FlushWriteBitBuffer(); void FinalizeFrame(); int ReadFromStream(class idMQTCP& stream); int WriteToStream(class idMQTCP& stream); unsigned char type; unsigned short channel; idMQList data; int readPos; bool locked; bool resetReadBitBuffer; unsigned char readBitBuffer; unsigned int readBitMask; bool flushWriteBitBuffer; unsigned char writeBitBuffer; unsigned int writeBitMask; unsigned short readPresenceBuffer; unsigned short readPresencePosition; unsigned short writePresenceBuffer; unsigned short writePresencePosition; }; class idMQTCP { public: idMQTCP(); ~idMQTCP(); bool Connect(const char* host, unsigned short port, bool nonBlocking, bool silent); void Close(); bool IsOpen() const; int Read(void* data, int size, bool blocking = true, int timeoutMS = 5000); int Write(const void* data, int size, bool blocking = true, int timeoutMS = 5000); int ReadByte(unsigned char& value, bool blocking = true, int timeoutMS = 5000); int ReadUInt16(unsigned short& value, bool blocking = true, int timeoutMS = 5000); int ReadUInt32(unsigned int& value, bool blocking = true, int timeoutMS = 5000); idTCP tcp; }; class idMQContentHeader { public: idMQContentHeader() : bodyLength(0) {} virtual ~idMQContentHeader() = default; virtual int GetProtocolId() const = 0; virtual void Populate(idMQFrame& frame) = 0; virtual void ToFrame(idMQFrame& frame) = 0; std::uint64_t bodyLength; }; class idMQBuffer { public: idMQBuffer(); void Clear(); void WriteData(const void* data, int size); void WriteUInt16(unsigned short value, bool bigEndian = true); void WriteUInt32(unsigned int value, bool bigEndian = true); idMQList body; int readPos; }; class idMQFrameHandler { public: explicit idMQFrameHandler(const amqpEndpoint_t& endpointValue); ~idMQFrameHandler(); bool Connect(); void Close(); int SendFrame(idMQFrame& frame); int ReadFrame(idMQFrame& frame); bool SendHeader(); amqpEndpoint_t endpoint; idSysMutex readLock; idSysMutex writeLock; idMQTCP tcp; }; template class idDeferredResult { public: idDeferredResult() : value(), filled(false), signal(false) {} void SetValue(const T& newValue) { value = newValue; filled = true; signal.Raise(); } bool GetValue(T& result) { if (!signal.Wait(5000) || !filled) return false; result = value; filled = false; return true; } bool GetValue() { if (!signal.Wait(5000) || !filled) return false; filled = false; return true; } void Clear() { filled = false; signal.Clear(); } T value; bool filled; idSysSignal signal; }; class idMQCommand { public: idMQCommand(); ~idMQCommand(); void Reset(); idMQMethod* method; idMQContentHeader* contentHeader; bool complete; idMQList contentBody; std::uint64_t remainingBodyBytes; }; class idMQCommandBuilder { public: idMQCommandBuilder(); MQErrors_t HandleFrame(idMQCommand& command, idMQFrame& frame); MQAssemblerState_t state; std::uint64_t remainingBytes; }; class idMQChannel; class idMQSessionManager; class idMQConnection { public: idMQConnection(); ~idMQConnection(); void Init(amqpEndpoint_t& endpoint); bool Connect(); void Close(); void Close(const char* reason, MQErrors_t error); void Close(amqpShutdownReason_t& reason); void SendFrame(idMQFrame& frame); idMQChannel* GetChannel(); unsigned int frameMaxSize; idMQList knownHosts; idMQFrameHandler* framer; idMQSessionManager* sessionManager; class idMQSession* sessionZero; volatile bool terminateConnection; volatile bool connectionTerminated; volatile bool isClosing; unsigned int threadHandle; amqpShutdownReason_t shutdownReason; private: bool OpenCommunications(); void CreateThread(); void MessageThread(); static unsigned int StaticThread(void* connection); }; class idMQSession { public: idMQSession(int channelNumber, idMQConnection* connection); ~idMQSession(); void Init(bool connectionChannel = false); void Close(); void Close(const char* reason, MQErrors_t error, bool notifyServer = true); void HandleFrame(idMQFrame& frame); void SendCommand(idMQCommand& command); idMQConnection* connection; idMQCommandBuilder cmdBuilder; idMQChannel* protocol; idMQCommand currentCommand; int channelNumber; bool isOpen; }; class idMQSessionManager { public: explicit idMQSessionManager(idMQConnection* connection); ~idMQSessionManager(); void Init(int maximumSessions); void Shutdown(); idMQSession* CreateSession(); idMQSession* Lookup(int channelNumber); bool initialized; idMQConnection* connection; int maxSessions; idMQList sessionList; }; class idMQClientThread { public: idMQClientThread(); virtual ~idMQClientThread(); virtual void StartMessageSystem() { StartThread("AMQP Client"); } virtual void StopMessageSystem() { StopThread(true); } virtual void PreRun() {} virtual void ThreadSlice() { Sys_Yield(); } virtual void OnThreadTerminate() {} void StartThread(const char* threadName); void StopThread(bool waitForStop); unsigned int handle; volatile bool signalQuit; volatile bool terminated; idMQConnection connection; int retryTime; protected: static void Connect(idMQClientThread* thread); static unsigned int Thread(void* thread); }; class idMQClient : public idMQClientThread {}; class idMQGraphiteClient : public idMQClientThread { public: struct graphiteEvent_t { const char* eventName; int count; float duration; float mean; float mean2; }; idMQGraphiteClient(const idStr& exchangeName, const idStr& metricPrefix); ~idMQGraphiteClient() override; void StartMessageSystem() override; void PreRun() override; void ThreadSlice() override; void OnThreadTerminate() override; void LogEvent(const idStr& eventName, float duration); int reportTime; idMQChannel* channel; idMQList outgoingEvents; idStr exchange; idStr prefix; idSysMutex threadMutex; }; #if INTPTR_MAX == INT32_MAX static_assert(sizeof(idMQTCP) == 20, "Recovered idMQTCP ABI changed"); static_assert(sizeof(idMQList) == 16, "Recovered AMQP list ABI changed"); static_assert(sizeof(idMQTable) == 48, "Recovered AMQP table ABI changed"); static_assert(sizeof(idMQBuffer) == 20, "Recovered idMQBuffer ABI changed"); static_assert(sizeof(idMQMethod) == 4, "Recovered idMQMethod ABI changed"); static_assert(sizeof(idMQContentHeader) == 16, "Recovered idMQContentHeader ABI changed"); #endif