// File name of the last transferred PLC md5 hex digest
// with typo in the name, for compatibility with Python runtime
#define LastTransferredPLC "lasttransferedPLC.md5"
// File name of the extra files list
#define ExtraFilesList "extra_files.txt"
PLCObject::PLCObject(void)
m_status.PLCstatus = Empty;
PLCObject::~PLCObject(void)
uint32_t PLCObject::AppendChunkToBlob(
const binary_t *data, const binary_t *blobID, binary_t *newBlobID)
// Append data to blob with given blobID
// Output new blob's md5 into newBlobID
newBlobID->data = (uint8_t *)malloc(MD5::digestsize);
if (newBlobID->data == NULL)
std::vector<uint8_t> k(blobID->data, blobID->data + blobID->dataLength);
auto nh = m_mapBlobIDToBlob.extract(k);
Blob *blob = nh.mapped();
uint32_t res = blob->appendChunk(data->data, data->dataLength);
MD5::digest_t digest = blob->digest();
std::vector<uint8_t> nk((uint8_t*)digest.data, (uint8_t*)digest.data + MD5::digestsize);
m_mapBlobIDToBlob.insert(std::move(nh));
memcpy(newBlobID->data, digest.data, MD5::digestsize);
newBlobID->dataLength = MD5::digestsize;
uint32_t PLCObject::AutoLoad()
uint32_t res = LoadPLC();
#define LOG_READ_BUFFER_SIZE 1 << 10 // 1KB
uint32_t PLCObject::GetLogMessage(
uint8_t level, uint32_t msgID, log_message *message)
char buf[LOG_READ_BUFFER_SIZE];
if(m_status.PLCstatus == Empty){
resultLen = m_PLCSyms.GetLogMessage(
level, msgID, buf, LOG_READ_BUFFER_SIZE - 1,
&tick, &tv_sec, &tv_nsec);
// Get log message with given msgID
message->msg = (char *)malloc(resultLen + 1);
if (message->msg == NULL)
// Copy the log message into eRPC message
memcpy(message->msg, buf, resultLen);
message->msg[resultLen] = '\0';
uint32_t PLCObject::GetPLCID(PSKID *plcID)
plcID->ID = (char *)malloc(m_PSK_ID.size() + 1);
memcpy(plcID->ID, m_PSK_ID.c_str(), m_PSK_ID.size());
plcID->ID[m_PSK_ID.size()] = '\0';
plcID->PSK = (char *)malloc(m_PSK_secret.size() + 1);
memcpy(plcID->PSK, m_PSK_secret.c_str(), m_PSK_secret.size());
plcID->PSK[m_PSK_secret.size()] = '\0';
uint32_t PLCObject::GetPLCstatus(PLCstatus *status)
if(m_status.PLCstatus == Empty){
for(int lvl = 0; lvl < 4; lvl++){
m_status.logcounts[lvl] = 0;
for(int lvl = 0; lvl < 4; lvl++){
m_status.logcounts[lvl] = m_PLCSyms.GetLogCount(lvl);
uint32_t PLCObject::GetTraceVariables(
uint32_t debugToken, TraceVariables *traces)
if(debugToken != m_debugToken)
// Check if there are any traces
size_t sz = m_traces.size();
// Allocate memory for traces
traces->traces.elements = (trace_sample *)malloc(sz * sizeof(trace_sample));
if(traces->traces.elements == NULL)
// Copy traces from vector
memcpy(traces->traces.elements, m_traces.data(), sz * sizeof(trace_sample));
// note that the data is not freed here, it is meant to be freed by eRPC server code
traces->traces.elementsCount = sz;
traces->PLCstatus = m_status.PLCstatus;
uint32_t PLCObject::MatchMD5(const char *MD5, bool *match)
// an empty PLC is never considered to match
if(m_status.PLCstatus == Empty)
// Load the last transferred PLC md5 hex digest
std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum;
} catch (std::exception e) {
// Compare the given MD5 with the last transferred PLC md5
*match = (md5sum == MD5);
#if defined(_WIN32) || defined(_WIN64)
#define SHARED_OBJECT_EXT ".dll"
#elif defined(__APPLE__) || defined(__MACH__)
#define SHARED_OBJECT_EXT ".dylib"
// For Linux/Unix platform
#define SHARED_OBJECT_EXT ".so"
uint32_t PLCObject::BlobAsFile(
const binary_t *BlobID, std::filesystem::path filename)
// Extract the blob from the map
auto nh = m_mapBlobIDToBlob.extract(
std::vector<uint8_t>(BlobID->data, BlobID->data + BlobID->dataLength));
Blob *blob = nh.mapped();
// Realize the blob into a file
uint32_t res = blob->asFile(filename);
uint32_t PLCObject::NewPLC(
const char *md5sum, const binary_t *plcObjectBlobID,
const list_extra_file_1_t *extrafiles, bool *success)
if(m_status.PLCstatus == Started)
if(m_status.PLCstatus == Broken)
// Concatenate md5sum and shared object extension to obtain filename
std::filesystem::path filename =
std::filesystem::path(md5sum) += SHARED_OBJECT_EXT;
// Create the PLC object shared object file
BlobAsFile(plcObjectBlobID, filename);
// create "lasttransferedPLC.md5" file and Save md5sum in it
std::ofstream(std::string(LastTransferredPLC), std::ios::binary) << md5sum;
// create "extra_files.txt" file
std::ofstream extra_files_log(std::string(ExtraFilesList), std::ios::binary);
for (int i = 0; i < extrafiles->elementsCount; i++)
extra_file *extrafile = extrafiles->elements + i;
BlobAsFile(plcObjectBlobID, extrafile->fname);
// Save the extra file name in "extra_files.txt"
extra_files_log << extrafile->fname << std::endl;
uint32_t res = LoadPLC();
m_status.PLCstatus = Stopped;
m_PLCSyms.sym = (decltype(m_PLCSyms.sym))dlsym(m_handle, #sym); \
if (m_PLCSyms.sym == NULL) \
/* TODO: use log instead */ \
std::cout << "Error dlsym " #sym ": " << dlerror() << std::endl; \
uint32_t PLCObject::LoadPLC(void)
// Load the last transferred PLC md5 hex digest
std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum;
} catch (std::exception e) {
// Concatenate md5sum and shared object extension to obtain filename
std::filesystem::path filename(md5sum + SHARED_OBJECT_EXT);
// Load the shared object file
m_handle = dlopen(std::filesystem::absolute(filename).c_str(), RTLD_NOW);
std::cout << "Error: " << dlerror() << std::endl;
// Resolve shared object symbols
FOR_EACH_PLC_SYMBOLS_DO(DLSYM);
// Set content of PLC_ID to md5sum
m_PLCSyms.PLC_ID = (uint8_t *)malloc(md5sum.size() + 1);
if (m_PLCSyms.PLC_ID == NULL)
memcpy(m_PLCSyms.PLC_ID, md5sum.c_str(), md5sum.size());
m_PLCSyms.PLC_ID[md5sum.size()] = '\0';
uint32_t PLCObject::UnLoadPLC(void)
// Unload the shared object file
FOR_EACH_PLC_SYMBOLS_DO(ULSYM);
uint32_t PLCObject::PurgeBlobs(void)
for (auto &blob : m_mapBlobIDToBlob)
m_mapBlobIDToBlob.clear();
uint32_t PLCObject::PurgePLC(void)
// Open the extra files list
std::ifstream extra_files_log(std::string(ExtraFilesList), std::ios::binary);
while (std::getline(extra_files_log, extra_file))
std::filesystem::remove(extra_file);
// Load the last transferred PLC md5 hex digest
std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum;
// Remove the PLC object shared object file
std::filesystem::remove(md5sum + SHARED_OBJECT_EXT);
} catch (std::exception e) {
// Remove the last transferred PLC md5 hex digest
std::filesystem::remove(std::string(LastTransferredPLC));
// Remove the extra files list
std::filesystem::remove(std::string(ExtraFilesList));
} catch (std::exception e) {
uint32_t PLCObject::RepairPLC(void)
if(m_status.PLCstatus == Broken)
LogMessage(LOG_WARNING, "RepairPLC not implemented");
uint32_t PLCObject::ResetLogCount(void)
m_PLCSyms.ResetLogCount();
uint32_t PLCObject::SeedBlob(const binary_t *seed, binary_t *blobID)
// Create a blob with given seed
// Output new blob's md5 into blobID
blob = new Blob(seed->data, seed->dataLength);
MD5::digest_t digest = blob->digest();
std::vector<uint8_t> k((uint8_t*)digest.data, (uint8_t*)digest.data + MD5::digestsize);
m_mapBlobIDToBlob[k] = blob;
blobID->data = (uint8_t *)malloc(MD5::digestsize);
if (blobID->data == NULL)
memcpy(blobID->data, digest.data, MD5::digestsize);
blobID->dataLength = MD5::digestsize;
void PLCObject::PurgeTraceBuffer(void)
for(trace_sample s : m_traces){
free(s.TraceBuffer.data);
uint32_t PLCObject::SetTraceVariablesList(
const list_trace_order_1_t *orders, int32_t *debugtoken)
if(m_status.PLCstatus == Empty)
if(orders->elementsCount == 0)
// actually disables debug
m_PLCSyms.suspendDebug(1);
*debugtoken = -5; // DEBUG_SUSPENDED
// suspend debug before any operation
int res = m_PLCSyms.suspendDebug(0);
// forget about all previous debug variables
m_PLCSyms.ResetDebugVariables();
// call RegisterTraceVariables for each trace order
for (int i = 0; i < orders->elementsCount; i++)
trace_order *order = orders->elements + i;
res = m_PLCSyms.RegisterDebugVariable(order->idx, order->force.data, order->force.dataLength);
// if any error, disable debug
// since debug is already suspended, resume it first
m_PLCSyms.suspendDebug(1);
// old traces are not valid anymore
// Start debug thread if not already started
if(!m_traceThread.joinable())
m_traceThread = std::thread(&PLCObject::TraceThreadProc, this);
*debugtoken = m_debugToken;
uint32_t PLCObject::StartPLC(void)
LogMessage(LOG_INFO, "Starting PLC");
uint32_t res = m_PLCSyms.startPLC(m_argc, m_argv);
m_status.PLCstatus = Broken;
m_status.PLCstatus = Started;
uint32_t PLCObject::StopPLC(bool *success)
LogMessage(LOG_INFO, "Stopping PLC");
uint32_t res = m_PLCSyms.stopPLC();
m_status.PLCstatus = Stopped;
m_status.PLCstatus = Broken;
if(m_traceThread.joinable())
uint32_t PLCObject::LogMessage(uint8_t level, std::string message)
// if PLC isn't loaded, log to stdout
if(m_PLCSyms.LogMessage == NULL)
std::cout << level << message << std::endl;
// Log std::string message with given level
return m_PLCSyms.LogMessage(level, (char *)message.c_str(), message.size());
void PLCObject::TraceThreadProc(void)
while(m_status.PLCstatus == Started)
// Data allocated here is meant to be freed by eRPC server code
int res = m_PLCSyms.GetDebugData(&tick, &size, &buff);
ourData = (uint8_t *)malloc(size);
memcpy(ourData, buff, size);
m_PLCSyms.FreeDebugData();
err = res == 0 ? ENOMEM : res;
m_traces.push_back(trace_sample{tick, binary_t{ourData, size}});
LogMessage(err ? LOG_CRITICAL : LOG_INFO,
err == ENOMEM ? "Out of memory in TraceThreadProc" :
err ? "TraceThreadProc ended because of error" :
"TraceThreadProc ended normally");
uint32_t PLCObject::ExtendedCall(const char * method, const binary_t * argument, binary_t * answer)
answer->data = (uint8_t*)"";