

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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 ControlMessage.cpp
	@brief Implementation of the ControlMessage wrapper and its self test.
	See ControlMessage.h for the API documentation, including the ownership rules for the underlying block.
	@ingroup Messaging
*/
#include "ControlMessage.h"
#include "PsyTime.h"
#include "UnitTestFramework.h"

namespace cmlabs {

ControlMessage::ControlMessage(uint16 command, uint64 from, uint64 to, char* data, uint32 dataSize) {
	this->data = new char[sizeof(ControlMessageHeader) + dataSize];
	ControlMessageHeader* header = (ControlMessageHeader*) this->data;
	header->size = sizeof(ControlMessageHeader) + dataSize;
	header->cid = CONTROLMESSAGEID;
	header->command = command;
	header->time = GetTimeNow();
	header->from = from;
	header->to = to;
	if (dataSize > 0)
		memcpy(this->data+sizeof(ControlMessageHeader), data, dataSize);
}

ControlMessage::ControlMessage(char* data) {
	this->data = data;
}

ControlMessage::~ControlMessage() {
	data = NULL;
}

uint32 ControlMessage::getSize() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->size;
}

uint16 ControlMessage::getCommand() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->command;
}

uint64 ControlMessage::getTime() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->time;
}

uint64 ControlMessage::getFrom() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->from;
}

uint64 ControlMessage::getTo() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->to;
}

uint16 ControlMessage::getStatus() {
	if (!VerObj(data)) return 0;
	return ((struct ControlMessageHeader*)data)->status;
}

bool ControlMessage::setStatus(uint16 status) {
	if (!VerObj(data)) return false;
	((ControlMessageHeader*)data)->status = status;
	return true;
}


bool ControlMessage::UnitTest() {

	// 1. Construction (no payload) and field accessors
	unittest::progress(10, "construction and accessors");
	{
		const uint16 command = 7;
		const uint64 from = 0x1122334455667788ULL;
		const uint64 to   = 0x99AABBCCDDEEFF00ULL;
		uint64 t0 = GetTimeNow();
		ControlMessage msg(command, from, to);

		if (msg.getCommand() != command) { unittest::fail("ControlMessage test: getCommand mismatch\n"); return false; }
		if (msg.getFrom() != from) { unittest::fail("ControlMessage test: getFrom mismatch\n"); return false; }
		if (msg.getTo() != to) { unittest::fail("ControlMessage test: getTo mismatch\n"); return false; }
		if (msg.getSize() != sizeof(ControlMessageHeader)) { unittest::fail("ControlMessage test: getSize (no payload) mismatch\n"); return false; }
		// time is auto-stamped from GetTimeNow(); just sanity-check it is populated and not before we started
		if (msg.getTime() == 0 || msg.getTime() < t0) { unittest::fail("ControlMessage test: getTime not stamped\n"); return false; }
		// status defaults to 0 until set
		if (msg.getStatus() != 0) { unittest::fail("ControlMessage test: default status != 0\n"); return false; }
	}

	// 2. Status set/get round-trip
	unittest::progress(35, "status set/get");
	{
		ControlMessage msg(1, 100, 200);
		if (!msg.setStatus(0xBEEF)) { unittest::fail("ControlMessage test: setStatus returned false\n"); return false; }
		if (msg.getStatus() != 0xBEEF) { unittest::fail("ControlMessage test: getStatus mismatch after setStatus\n"); return false; }
		if (!msg.setStatus(0)) { unittest::fail("ControlMessage test: setStatus(0) returned false\n"); return false; }
		if (msg.getStatus() != 0) { unittest::fail("ControlMessage test: getStatus mismatch after setStatus(0)\n"); return false; }
	}

	// 3. Construction with a binary payload
	unittest::progress(55, "payload construction");
	{
		const char payload[] = { 'C', 'M', 'D', 'A', 'T', 'A' };
		const uint32 payloadSize = sizeof(payload);
		ControlMessage msg(42, 5, 6, (char*)payload, payloadSize);

		if (msg.getCommand() != 42) { unittest::fail("ControlMessage test: payload getCommand mismatch\n"); return false; }
		if (msg.getSize() != sizeof(ControlMessageHeader) + payloadSize) {
			unittest::fail("ControlMessage test: payload getSize mismatch\n"); return false;
		}
		// the payload bytes sit immediately after the header in the data buffer
		const char* stored = msg.data + sizeof(ControlMessageHeader);
		if (memcmp(stored, payload, payloadSize) != 0) {
			unittest::fail("ControlMessage test: payload bytes not stored correctly\n"); return false;
		}
	}

	// 4. Wrap an existing raw buffer (the ControlMessage(char*) constructor) - serialise / deserialise round-trip
	unittest::progress(80, "raw buffer round-trip");
	{
		const char payload[] = { 0x01, 0x02, 0x03, 0x04 };
		const uint32 payloadSize = sizeof(payload);
		ControlMessage orig(99, 0xAAAA, 0xBBBB, (char*)payload, payloadSize);
		orig.setStatus(0x1234);

		// adopt the same underlying buffer through the raw-buffer constructor
		ControlMessage wrapped(orig.data);
		if (wrapped.getCommand() != 99) { unittest::fail("ControlMessage test: wrapped getCommand mismatch\n"); return false; }
		if (wrapped.getFrom() != 0xAAAA) { unittest::fail("ControlMessage test: wrapped getFrom mismatch\n"); return false; }
		if (wrapped.getTo() != 0xBBBB) { unittest::fail("ControlMessage test: wrapped getTo mismatch\n"); return false; }
		if (wrapped.getStatus() != 0x1234) { unittest::fail("ControlMessage test: wrapped getStatus mismatch\n"); return false; }
		if (wrapped.getSize() != sizeof(ControlMessageHeader) + payloadSize) {
			unittest::fail("ControlMessage test: wrapped getSize mismatch\n"); return false;
		}
		if (memcmp(wrapped.data + sizeof(ControlMessageHeader), payload, payloadSize) != 0) {
			unittest::fail("ControlMessage test: wrapped payload mismatch\n"); return false;
		}
		// wrapped's destructor only nulls its pointer (no free); orig owns and frees the buffer.
	}

	// 5. Edge / robustness - accessors on an invalid (NULL) buffer must be safe and return zero
	unittest::progress(92, "edge / robustness");
	{
		ControlMessage bad((char*)NULL);
		if (bad.getSize() != 0) { unittest::fail("ControlMessage test: NULL getSize != 0\n"); return false; }
		if (bad.getCommand() != 0) { unittest::fail("ControlMessage test: NULL getCommand != 0\n"); return false; }
		if (bad.getTime() != 0) { unittest::fail("ControlMessage test: NULL getTime != 0\n"); return false; }
		if (bad.getFrom() != 0) { unittest::fail("ControlMessage test: NULL getFrom != 0\n"); return false; }
		if (bad.getTo() != 0) { unittest::fail("ControlMessage test: NULL getTo != 0\n"); return false; }
		if (bad.getStatus() != 0) { unittest::fail("ControlMessage test: NULL getStatus != 0\n"); return false; }
		if (bad.setStatus(1)) { unittest::fail("ControlMessage test: NULL setStatus returned true\n"); return false; }
	}

	// 6. Construction performance metric
	unittest::progress(97, "construction throughput");
	{
		const int N = 20000;
		const char payload[] = { 'p', 'e', 'r', 'f' };
		uint64 ts = GetTimeNow();
		uint64 acc = 0;
		for (int i = 0; i < N; i++) {
			ControlMessage m((uint16)i, (uint64)i, (uint64)(i + 1), (char*)payload, sizeof(payload));
			acc += m.getCommand();
		}
		double us = (double)(GetTimeNow() - ts);
		if (us > 0)
			unittest::metric("construct_throughput", (double)N / us * 1e6, "msg/s", true);
		unittest::detail("constructed %d control messages, command checksum %llu", N, acc);
	}

	unittest::progress(100, "done");
	return true;
}

void Register_ControlMessage_Tests() {
	UnitTestRunner::instance().registerTest("controlmessage", ControlMessage::UnitTest,
		"Control message construction, status, payload, raw-buffer round-trip", "core");
}


} // namespace cmlabs
