/*
 * grifo_tftp_daemon.cpp
 *
 */

#include "bsk_tftpd.h"
#include "mrk.h"
#include "mrk_net.h"
#include "rtlog.h"

#include "mex_hobbit_register.h"
#include "mex.h"

#include "DataRecorder.h"

#include "string.h"
#include "bsk_sprintf.h"

#include "bsp_srio_mini_driver.h"

#include "bsp_storable_config.h"

#include "stdlib.h"

#include "bsp_platform.h"

#define LID "TFTP"
#define PORT 50069

static uint8_t g_tftpd_stack[8*1024];
static mrk_thread_t g_tftpd_thread;

struct srio_filename_t
{
    int valid;

    unsigned int dstId;
    unsigned int dstAddress;
    unsigned int dstOriginalAddress;
    unsigned int dstSize;
    unsigned int dstDoorbell;
    int doorbell_valid;
};

static const char* decode_srio_filename(srio_filename_t* fn, const char* filename, bool mem_protection=true)
{
    static char temp[256];

    fn->valid=0;

    strncpy(temp, filename, (sizeof temp)-1);
#if 0
    char* d=strchr(temp, ':');
    if (!d)
        return "missed driver name";
    d[0]=0;
    ++d;

    if (strcmp("$SRIO", temp))
        return "invalid drive name";
#endif
    char* d=temp;

    fn->dstSize=0;
    fn->doorbell_valid=0;
    fn->dstDoorbell=0;

    char* p=strchr(d, '/');
    if (!p)
        return "missed destination id";
    p[0]=0;
    ++p;
    char* ps=strchr(p, '+');
    if (ps)
    {
        ps[0]=0;
        ++ps;
        fn->dstSize=strtoul(ps, 0, 0);
    }

    unsigned int address=strtoul(p, 0, 0);
    fn->dstAddress=address;
    fn->dstOriginalAddress=address;

    unsigned int db_type=0;
    char* db=strchr(d, '.');
    if (!db)
    {
        db=strchr(d, '!');
        if (db)
            db_type=2;
    }
    else
    	db_type=1;

    if (db)
    {
        db[0]=0;
        ++db;
        if (db<p)
        {
            int n=strtoul(db, 0, 0);
            fn->dstDoorbell=n;
            fn->doorbell_valid=db_type;
        }
    }

    unsigned int node=strtoul(d, 0, 0);
    fn->dstId=node;

    fn->valid=1;

    if (bsp_is_evm() && mem_protection)
    {
        address=address & (0x0000FFFF);
        address=address | (0xB0000000);
        fn->dstAddress=address;
    }

    return 0;
}

#include "bsk_tiny_json.h"

class RecCfgParse
{
public:
	json_t mem[128];
	const json_t* json;
	bool parse(char* str)	//Warning: str is modified by the parser
	{
	    json = json_create( str, mem, sizeof mem / sizeof *mem );

	    if (json==0)
	    {
	        rtlog_printf("REC", rtlog_warning|rtlog_breaking_news, "CFG.json: Error parsing.");
	    	return false;
	    }

	    json_t const* recObj = json_getProperty( json, "recorder" );
	    if ( !recObj || (JSON_OBJ != json_getType( recObj )) ) {
	        rtlog_printf("REC", rtlog_warning|rtlog_breaking_news, "CFG.json: Error - recorder property is not found.");
	        return false;
	    }

	    json_t const* recEntities = json_getProperty( recObj, "entities" );
	    if ( !recEntities || (JSON_OBJ != json_getType( recObj )) ) {
	        rtlog_printf("REC", rtlog_warning|rtlog_breaking_news, "CFG.json: Error - recorder property is not found.");
	        return false;
	    }

	    json_t const* recList = json_getProperty( recEntities, "list" );
	    if ( !recList || (JSON_ARRAY != json_getType( recList )) ) {
	        rtlog_printf("REC", rtlog_warning|rtlog_breaking_news, "CFG.json: Error - list property is not found.");
	        return false;
	    }

	    json_t const* rec_point;

		Recorder::EntityInfo ei=Recorder::getEntities();

	    for( rec_point = json_getChild( recList ); rec_point != 0; rec_point = json_getSibling( rec_point ) ) {
	        if ( JSON_OBJ == json_getType( rec_point ) )
	        {
	            char const* name = json_getPropertyValue( rec_point, "name" );
	    	    json_t const* jEnable = json_getProperty( rec_point, "enabled" );
	            if (jEnable)
	            {
	            	int enabled=json_getInteger(jEnable);
		    		for(unsigned int i=0; i<ei.items; ++i)
		    		{
		    			if (strcmp(ei.instances[i]->name, name)==0)
		    			{
		    				ei.instances[i]->enabled=enabled!=0;
		    				rtlog_printf("REC", rtlog_debug|rtlog_breaking_news, "RP: %s = %u", name, enabled);
		    				break;
		    			}
		    		}

	            }
	        }
	    }
	    return true;

	}
};

static RecCfgParse recCfgParse;

//Minimalistic JASON composer
class MicroJasonComposer
{
public:
	int level;
	int comma;

	bsk_snprint_accumulator_t cx;

	MicroJasonComposer(char* buffer, unsigned int max_size):
		level(0),
		comma(0)
	{
		bsk_snprintf_accumulator_init(&cx, buffer, max_size);
	}

	void checkLevel(int nest=0)
	{
		if (comma)
			bsk_snprintf_accumulate(&cx, ",\n");
		else if (nest)
			level+=nest;
		else
			level=1;
	}

	void begin()
	{
		checkLevel();
		bsk_snprintf_accumulate(&cx, "{");
		comma=0;
	}
	void add(const char* name, const char* value)
	{
		checkLevel();
		bsk_snprintf_accumulate(&cx, "\"%s\":\"%s\"", name, value);
		comma=1;
	}

	void add(const char* name, unsigned int value)
	{
		checkLevel();
		bsk_snprintf_accumulate(&cx, "\"%s\":%u", name, value);
		comma=1;
	}
	void addLiteral(const char* name, unsigned int value)
	{
		checkLevel();
		bsk_snprintf_accumulate(&cx, "\"%s\":%u", name, value);
		comma=1;
	}

	void beginObject(const char* name)
	{
		checkLevel(1);
		bsk_snprintf_accumulate(&cx, "\"%s\":{", name);
		comma=0;
	}
	void endObject()
	{
		bsk_snprintf_accumulate(&cx, "}\n");
		if (level>0)
		{
			--level;
			if (level)
				comma=1;
		}
	}

	void beginArray(const char* name)
	{
		checkLevel(1);
		bsk_snprintf_accumulate(&cx, "\"%s\":[", name);
		comma=0;
	}

	void endArray()
	{
		bsk_snprintf_accumulate(&cx, "]\n");
		if (level>0)
		{
			--level;
			if (level)
				comma=1;
		}
	}

	void end()
	{
		bsk_snprintf_accumulate(&cx, "}\n");
		if (level>0)
		{
			--level;
			if (level)
				comma=1;
		}
	}

};

extern "C"
{

extern int mex_hobbit_get_file(const char** raw, unsigned int* size) __attribute__((weak));
}

static bsk_tftpd_receive_status_t get_hobbit()
{
	bsk_tftpd_receive_status_t sts;
	sts.sts=bsk_tftpd_err_not_found;
	sts.data=0;
	sts.data_size=0;
	sts.ack_msg=0;

	if (mex_hobbit_get_file==0)
	{
		sts.ack_msg="not supported";
		sts.sts=bsk_tftpd_err_bad_operation;
		return sts;
	}
	const char* p=0;
	unsigned int size=0;
	int r=mex_hobbit_get_file(&p, &size);
	if (r)
		return sts;
	sts.sts=bsk_tftpd_ok;
	sts.data=p;
	sts.data_size=size;
	return sts;
}

struct TftpExec;

static TftpExec* tcx;

#include "grifo_version.h"

#ifdef __TMS320C6X__
#define READ_BUFF_SIZE  (128*1024)
#define WRITE_BUFFER_SIZE (8*1024)
#endif
#ifdef __MINGW32__
#define READ_BUFF_SIZE  (128*1024)
#endif

#ifndef READ_BUFF_SIZE
#define READ_BUFF_SIZE  (4*1048)
#endif

#ifndef WRITE_BUFFER_SIZE
#define WRITE_BUFFER_SIZE (4*1024)
#endif

struct tftp_tmp_storage_t
{
    double align64;
    uint32_t cache_pre_align[32];
    char buffer[READ_BUFF_SIZE];
    char temp_buffer[WRITE_BUFFER_SIZE];
    uint32_t cache_post_align[32];
};

#ifdef _TMS320C6X
#define TMP_MEMORY_SECTION __attribute__((section(".far:_srio_writable_init_memory_")))
#else
#define TMP_MEMORY_SECTION
#endif
static tftp_tmp_storage_t tftp_tmp_storage TMP_MEMORY_SECTION;

struct TftpExec
{
    static bool evm;

    static unsigned int srv_port;
    static volatile bool please_wakeup;
    static volatile unsigned int wakeup_delay_ms;

	mrk_net_udp_socket_t tftpd_socket;

	unsigned int log_level;
	const char* site_name;

    unsigned int rx_request;
    unsigned int tx_request;

    unsigned int rx_refused;
    unsigned int tx_refused;

    bool hello;


    static int srio_mini_drv_memory_write_nowait(unsigned int dst, unsigned int dst_address, const void* source_address, unsigned int size, unsigned int doorbell,bool with_ack=false/* ,enum bsp_srio_mini_drv_priority_t priority*/)
    {
    	if (evm)
    		return 0;
    	return bsp_srio_mini_drv_memory_write_nowait(dst, dst_address, source_address, size, doorbell, with_ack);
    }

    static int srio_mini_drv_send_doorbell(unsigned int dst, unsigned int doorbell)
    {
    	if (evm)
    		return 0;
    	if ((doorbell!=0) && (doorbell!=bsp_srio_mini_drv_no_doorbell))
    		return bsp_srio_mini_drv_send_doorbell(dst, doorbell);
    	return 0;
    }

    static int srio_mini_drv_memory_read_wait(unsigned int dst, unsigned int dst_address, unsigned int size,void* data)
    {
    	if (evm)
    		return 0;
    	return bsp_srio_mini_drv_memory_read_wait(dst, dst_address, size,data);
    }

	void start()
	{
	    evm=bsp_is_evm();

		tcx=this;

		mrk_thread_create(&g_tftpd_thread, "TFTPD", tftpd_exec, 0, 10, sizeof g_tftpd_stack, &g_tftpd_stack, 0);

		//mex_hobbit_register_instance("tftp", *this);
	}

	static void __monitor_invokable_exec_start(void) __attribute__((used));
private:
	static int tftpd_log_delegate(bsk_tftpd_context_id_t, const char* const msg, ...)
	{
		va_list args;
		va_start(args, msg);
		rtlog_vprintf(LID, rtlog_debug|rtlog_breaking_news, msg, args);
		va_end(args);
		return 0;
	}

/*
	struct tmp_storage_t
	{
	    double align64;
	    uint32_t cache_pre_align[32];
	    char buffer[2048];
	    char temp_buffer[2048];
        uint32_t cache_post_align[32];
	};
*/
	static bsk_tftpd_receive_status_t tftpd_receive_delegate(bsk_tftpd_context_id_t /*id*/, bsk_tftpd_receive_delegate_operation_t op, const void* const data, unsigned int /*block*/, unsigned int /*offset*/, unsigned int size_bytes)
	{
		static bool mon_scripting=false;
		static bool mon_asynch=false;
		static int rcv_config=0;
		static unsigned int rcv_size=0;
        static srio_filename_t fn;

		//tmp_storage_t tmp_storage;
        tftp_tmp_storage_t& tmp_storage=tftp_tmp_storage;

		char* tmp_msg=tmp_storage.buffer;
#define sizeof_tmp_msg  (sizeof(tmp_storage.temp_buffer))

		//static char tmp_msg[2048];


		bsk_tftpd_receive_status_t sts;
		sts.ack_msg=0;
		sts.sts=bsk_tftpd_err_not_found;

		if ((op==bsk_tftpd_error) || (data==0))
			return sts;

        const char* p=(char*)data;

        bool srio_bridge=strncmp(p, "$SRIO:0", 7)==0;
        bool mem_bridge=strncmp(p, "$MEM:0", 6)==0;

        if (srio_bridge || mem_bridge)
        {
            const char* r=decode_srio_filename(&fn, &p[6], srio_bridge); //(const char*)data);
            if (r)
            {
                rtlog_printf(LID, rtlog_warning|rtlog_breaking_news, "Invalid SRIO filename");
                sts.sts=bsk_tftpd_err_not_found;
                sts.ack_msg=r;
                return sts;
            }
            unsigned int max_size=op==bsk_tftpd_open_file_read ? sizeof(tmp_storage.buffer) : sizeof_tmp_msg;

            if ((fn.dstSize<=0) || (fn.dstSize>max_size)) //sizeof_tmp_msg))
            {
                sts.sts=bsk_tftpd_err_no_space;
                sts.ack_msg="too big/small";
                rtlog_printf(LID, rtlog_error|rtlog_breaking_news, "SRIO RD Open: %X!%u: %X * %u", fn.dstId, fn.dstDoorbell, fn.dstOriginalAddress, fn.dstSize);
                return sts;
            }
        }

	    if (op==bsk_tftpd_open_file_read)
	    {
	    	//const char* p=(char*)data;
	    	bool rec_data=strcmp(p, "REC:data")==0;
            bool rec_stream=strcmp(p, "REC:stream")==0;

            rcv_config=0;

            if (strcmp(p, "version.txt")==0)
            {
            	unsigned int psize=0;
            	const char* p=GrifoVersion::getFormattedVersionInfo(psize);
            	if (p && psize)
            	{
            		sts.data=p;
            		sts.data_size=psize;
            		sts.ack_msg=0;
            		sts.sts=bsk_tftpd_ok;
                    rtlog_printf(LID, rtlog_info|rtlog_breaking_news, "Sending version info (%u)", psize);

            	}
            	else
            	{
                    sts.sts=bsk_tftpd_err_not_found;
                    sts.ack_msg="Sorry, no version info";
                    rtlog_printf(LID, rtlog_warning|rtlog_breaking_news, "Sorry, no version info");
            	}
            }
	    	if ((strcmp(p, "$hello$")==0) && (tcx->hello))
	    	{
    			sts.sts=bsk_tftpd_err_file_exists;
    			sts.ack_msg=tcx->site_name; //REC|MON|SRIO";
	    	}
	    	else if (srio_bridge)
	    	{
                {
                	if (fn.doorbell_valid)
                	{
                		srio_mini_drv_send_doorbell(fn.dstId, fn.dstDoorbell);
                	}

                    rtlog_printf("TFTP", rtlog_report|rtlog_breaking_news, "SRIO-RD[0x%X] 0x%X x %u -> %u", fn.dstId, fn.dstAddress, fn.dstSize, fn.doorbell_valid ? fn.dstDoorbell : 0);

                    int sriores=srio_mini_drv_memory_read_wait(fn.dstId, fn.dstAddress, fn.dstSize, tmp_msg);
                    if (sriores)
                    {
                        sts.sts=bsk_tftpd_err_access;
                        sts.ack_msg="SRIO read error";
                        rtlog_printf(LID, rtlog_error|rtlog_breaking_news, "SRIO RD Open: %X!%u: %X * %u", fn.dstId, fn.dstDoorbell, fn.dstOriginalAddress, fn.dstSize);
                    }
                    else
                    {
                        sts.data=tmp_msg;
                        sts.data_size=fn.dstSize;
                        sts.sts=bsk_tftpd_ok;
                        rtlog_printf(LID, rtlog_info|rtlog_breaking_news, "SRIO RD Open: %X!%u: %X * %u", fn.dstId, fn.dstDoorbell, fn.dstOriginalAddress, fn.dstSize);
                    }
                }
	    	}
	    	else if (mem_bridge)
	    	{
	    		sts.sts=bsk_tftpd_ok;
	    		sts.data_size=fn.dstSize;
	    		sts.data=(const void*)fn.dstAddress;
	    	}
	    	else if (rec_data || rec_stream) //strcmp(p, "REC:data")==0)
	    	{
	    	    Recorder::setStreamMode(rec_stream);
	    		Recorder::FileInfo fi=Recorder::getData();
	    		if (fi.size)
	    		{
	    			sts.data=fi.data;
	    			sts.data_size=fi.size;
	    			sts.sts=bsk_tftpd_ok;
	    		}
	    		else if (fi.status==0)
	    		{
	    			sts.sts=bsk_tftpd_err_no_space;
	    		}
	    	}
	    	else if (strcmp(p, "REC:cfg.json")==0)
	    	{
	    		Recorder::EntityInfo ei=Recorder::getEntities();
	    		MicroJasonComposer j(tmp_msg, sizeof_tmp_msg);
	    		j.begin();
	    		j.add("site", tcx->site_name ? tcx->site_name : "????");
	    		j.beginObject("recorder");
	    		j.addLiteral("capacity", 0);
	    		j.addLiteral("recording", 0);
	    		j.beginObject("entities");
	    		j.add("num", ei.items);
	    		j.beginArray("list");
	    		for(unsigned int i=0; i<ei.items; ++i)
	    		{
	    			j.begin();
	    			j.add("name", ei.instances[i]->name);
	    			j.add("enabled", ei.instances[i]->enabled);
	    			j.endObject();
	    		}
	    		j.endArray();
	    		j.endObject();
	    		j.endObject();
	    		j.end();

#if 0
	    		bsk_snprint_accumulator_t cx;
	    		bsk_snprintf_accumulator_init(&cx, tmp_msg, sizeof tmp_msg);

	    		bsk_snprintf_accumulate(&cx,
	    				"{\"recorder\":\n"
	    				"  {\"entities\":\n"
	    				"    {\"num\": %u\n"
	    				"     \"list\": [\n"
	    				, ei.items);
	    		for(unsigned int i=0; i<ei.items; ++i)
	    		{
	    			bsk_snprintf_accumulate(&cx, "{\"%s\": %u}\n", ei.instances[i]->name, ei.instances[i]->enabled);
	    		}
	    		bsk_snprintf_accumulate(&cx, "]}}}\n");
#endif
	    		sts.data=tmp_msg;
	    		sts.data_size=j.cx.str_len;
	    		sts.sts=bsk_tftpd_ok;
	    	}
	    	else if (strcmp(p, "MON:hobbit.txt")==0)
	    	{
	    		sts=get_hobbit();
	    	}
	    	if (sts.sts!=bsk_tftpd_err_not_found)
	    		++tcx->rx_request;
	    	else
	    		++tcx->rx_refused;
	    }
	    else if (op==bsk_tftpd_open_file_write)
	    {
	    	rcv_config=0;
	    	rcv_size=0;
	    	mon_scripting=false;
	    	mon_asynch=false;

	    	const char* p=(char*)data;
            memset(tmp_msg, 0, sizeof_tmp_msg);

	    	if (strcmp(p, "REC:start")==0)
	    	{
	    		unsigned int s=Recorder::start();
	    		bsk_snprintf(tmp_msg, sizeof_tmp_msg, "size=%u", s);
    			sts.sts=bsk_tftpd_err_file_exists;
    			sts.ack_msg=tmp_msg;
	    	}
            if (strcmp(p, "REC:sata_start")==0)
            {
                unsigned int s=Recorder::sataStart();
                bsk_snprintf(tmp_msg, sizeof_tmp_msg, "size=%u", s);
                sts.sts=bsk_tftpd_err_file_exists;
                sts.ack_msg=tmp_msg;
            }
	    	else if (strcmp(p, "REC:stop")==0)
	    	{
	    		unsigned int s=Recorder::stop();
	    		bsk_snprintf(tmp_msg, sizeof_tmp_msg, "size=%u", s);
    			sts.sts=bsk_tftpd_err_file_exists;
    			sts.ack_msg=tmp_msg;
	    	}
            else if (strcmp(p, "REC:start.json")==0)
            {
                rcv_config=3;
                sts.sts=bsk_tftpd_ok;
                //sts.data=tmp_msg;
                //sts.data_size=sizeof tmp_msg;
            }
	    	else if (strcmp(p, "REC:cfg.json")==0)
	    	{
	    		rcv_config=1;
	    		sts.sts=bsk_tftpd_ok;
	    		//sts.data=tmp_msg;
	    		//sts.data_size=sizeof tmp_msg;
	    	}
            else if (strcmp(p, "CFG:start.txt")==0)
            {
                rcv_config=2;
                sts.sts=bsk_tftpd_ok;
                //sts.data=tmp_msg;
                //sts.data_size=sizeof tmp_msg;
            }
	    	else if ((strcmp(p, "MON:script.txt")==0) || (strcmp(p, "MON/script.txt")==0))
	    	{
	    		mon_scripting=true;
	    		mon_asynch=false;
	    		sts.sts=bsk_tftpd_ok;
	    		sts.data=tmp_msg;
	    		sts.data_size=sizeof_tmp_msg;
	    	}
	    	else if (strcmp(p, "MON:exec.txt")==0)
	    	{
	    		mon_scripting=true;
	    		mon_asynch=true;
	    		sts.sts=bsk_tftpd_ok;
	    		//rcv_config=1000;
	    		sts.data=tmp_msg;
	    		sts.data_size=sizeof_tmp_msg;
	    	}
	    	else if (srio_bridge)
	    	{
	    	    rcv_config=100;
                sts.sts=bsk_tftpd_ok;
	    	}
	    	else if (mem_bridge)
	    	{
	    	    rcv_config=200;
                sts.sts=bsk_tftpd_ok;
	    	}
	    }
	    else if (op==bsk_tftpd_data)
	    {
	    	if (mon_scripting)
	    	{
	    		sts.sts=bsk_tftpd_ok;
	    		if (!mon_asynch)
	    			mex_script_inject((const char*)data, size_bytes);
	    		else
	    		{
		    		unsigned int s=rcv_size+size_bytes;
		    		if (s>=(sizeof_tmp_msg-1020))
		    		{
		    			sts.sts=bsk_tftpd_err_no_space;
		    		}
		    		memcpy(&tmp_msg[rcv_size], data, size_bytes);
		    		rcv_size=s;

	    		}
	    	}
	    	else if (rcv_config)
	    	{
	    		sts.sts=bsk_tftpd_ok;
	    		unsigned int s=rcv_size+size_bytes;
	    		if (s>=(sizeof_tmp_msg-1020))
	    		{
	    			sts.sts=bsk_tftpd_err_no_space;
	    		}
	    		memcpy(&tmp_msg[rcv_size], data, size_bytes);
	    		rcv_size=s;
	    	}

	    }
	    else if (op==bsk_tftpd_end)
	    {
	    	if (mon_scripting)
	    	{
	    		mon_scripting=false;
	    		sts.sts=bsk_tftpd_ok;
	    		if (mon_asynch)
	    		{
		    		//memcpy(&tmp_msg[rcv_size], data, size_bytes);
		    		//rcv_size+=size_bytes;
		    		//tmp_msg[rcv_size]=0;
                    rtlog_const_message("TFTPD", rtlog_info|rtlog_breaking_news, "Exec asynch monitor script...");
    	    		memcpy(&tmp_msg[rcv_size], data, size_bytes);
    	    		rcv_size+=size_bytes;
                    const char* r=mex_script_asynch_exec(tmp_msg, rcv_size); //mex_script_inject((const char*)data, size_bytes);
                    if (r)
                    {
                    	if (r[0]=='?')
                    	{
							sts.sts=bsk_tftpd_err_access;
                    	}
                    	else
                    	{
                    		sts.sts=bsk_tftpd_err_generic;
                    	}
							sts.ack_msg=r;
							rtlog_printf("TFTPD", rtlog_info|rtlog_breaking_news, "Exec reply: %s", r);
                    }
	    		}
	    		else if (size_bytes)
	    		{
                    rtlog_const_message("TFTPD", rtlog_info|rtlog_breaking_news, "Committing monitor script...");
	    			mex_script_inject((const char*)data, size_bytes);
	    		}
	    		mon_asynch=false;
	    	}
	    	else if (rcv_config)
	    	{
	    		sts.sts=bsk_tftpd_ok;
	    		memcpy(&tmp_msg[rcv_size], data, size_bytes);
	    		rcv_size+=size_bytes;
	    		if ((rcv_config==1) || (rcv_config==3))
	    		{
                    rtlog_const_message("TFTPD", rtlog_info|rtlog_breaking_news, "REC configuration");
	    		    bool ok=recCfgParse.parse(tmp_msg);
	                if (ok && (rcv_config==3))
	                {
	                    unsigned int s=Recorder::start();
	                    bsk_snprintf(tmp_msg, sizeof_tmp_msg, "size=%u", s);
	                    sts.ack_msg=tmp_msg;
	                }
	    		}
	    		else if (rcv_config==2)
	    		{
	    		    int r=bsp_raw_storable_config_save(tmp_msg, sizeof_tmp_msg);
	    		    if (r)
	    		    {
	                    rtlog_printf("TFTPD", rtlog_warning|rtlog_breaking_news, "Saving configuration failed (%d)", r);
	                    sts.sts=bsk_tftpd_err_access;
	                    sts.ack_msg="NVM save error";
	    		    }
	    		    else
	                    rtlog_const_message("TFTPD", rtlog_success|rtlog_breaking_news, "Saving configuration: OK");
	    		}
	    		else if (rcv_config==100)
	    		{
                	memcpy(tmp_storage.temp_buffer, tmp_msg, fn.dstSize);
                	unsigned int doorbell=fn.doorbell_valid ? fn.dstDoorbell : (unsigned int)bsp_srio_mini_drv_no_doorbell;
                	rtlog_printf("TFTP", rtlog_report|rtlog_breaking_news, "SRIO-WR[0x%X] X0x%X x %u -> %u", fn.dstId, fn.dstAddress, fn.dstSize, doorbell);
                    int sriores=srio_mini_drv_memory_write_nowait(fn.dstId, fn.dstAddress, tmp_storage.temp_buffer/*tmp_msg*/, fn.dstSize, doorbell, false);
                    if (sriores)
                    {
                        sts.sts=bsk_tftpd_err_access;
                        sts.ack_msg="SRIO read error";
                        rtlog_printf(LID, rtlog_error|rtlog_breaking_news, "SRIO WR Open: %X!%u: %X * %u", fn.dstId, fn.dstDoorbell, fn.dstOriginalAddress, fn.dstSize);
                    }
                    else
                    {
                        sts.data=tmp_msg;
                        sts.data_size=fn.dstSize;
                        sts.sts=bsk_tftpd_ok;
                        rtlog_printf(LID, rtlog_info|rtlog_breaking_news, "SRIO WR Open: %X!%u: %X * %u", fn.dstId, fn.dstDoorbell, fn.dstOriginalAddress, fn.dstSize);
                    }
	    		}
	    		else if (rcv_config==200)
	    		{
                    rtlog_printf(LID, rtlog_info|rtlog_breaking_news, "memcpy(0x%X,,%u)", fn.dstAddress, rcv_size);
	    			memcpy((void*)fn.dstAddress, tmp_msg, rcv_size);
	    		}
	    	}
            rcv_config=0;
	    }

		return sts;
	}

	static int tftpd_send_delegate(bsk_tftpd_context_id_t /*id*/, unsigned int client_address, unsigned int port, const void* const data, unsigned int size)
	{
	    //const bsk_tftpd_context_info_t* cx=bsk_tftpd_context_info(id);

	    mrk_net_udp_send(&tcx->tftpd_socket, data, size, client_address, port, 0);

	    return 0;
	}

	static void tftpd_exec(void)
	{
		if (srv_port==0)
		{
		    srv_port=PORT;
        }

		rtlog_printf(LID, rtlog_success|rtlog_breaking_news, "TFTP daemon started port %u...", srv_port);

		mrk_net_udp_socket(&tcx->tftpd_socket, "TFTPD");
		mrk_net_udp_bind(&tcx->tftpd_socket, mrk_net_myip_address(0), srv_port); //PORT);

		mrk_net_udp_set_capacity(&tcx->tftpd_socket, 4);

		bsk_tftpd_set_log_delegate(0, tftpd_log_delegate);
		bsk_tftpd_set_receive_delegate(0, tftpd_receive_delegate);
		bsk_tftpd_set_send_delegate(0, tftpd_send_delegate);

#ifdef MRK_NET_APIPA_SUPPORT
		mrk_net_apipa_socket_enable(&tcx->tftpd_socket, 1);
#endif

		mrk_thread_sleep_ms(500);

		if (please_wakeup)
		{
			unsigned int delay=wakeup_delay_ms ? wakeup_delay_ms : 1500;
			mrk_thread_sleep_ms(delay);
		}

		for(;;)
		{
			static char datagram[2048];
			unsigned int received_len=0;
			mrk_net_ip_port_t client_port=0;
			mrk_net_ip_raw_address_t client_ip=0;

			if (please_wakeup)
			{
#ifdef __MINGW32__
				mrk_net_ip_raw_address_t ip=mrk_net_myip_address(0);
#else
				mrk_net_ip_raw_address_t ip=mrk_net_myip_address_broadcast(0);
#endif
				bsk_tftpd_send_nack(0, ip, 50068, bsk_tftpd_err_generic, "Wakeup");
				please_wakeup=false;
			}

			int r=mrk_net_udp_receive(&tcx->tftpd_socket, datagram, sizeof datagram, &client_ip, &client_port, &received_len, mrk_wait_forever);
			if (r==0)
				bsk_tftpd_process_packet(0, client_ip, client_port, datagram, received_len);

		}
	}

public:
};

bool TftpExec::evm;
unsigned int TftpExec::srv_port;
volatile bool TftpExec::please_wakeup;
volatile unsigned int TftpExec::wakeup_delay_ms;

static TftpExec autoMonitorable_tftpExec;

static TftpExec& tftpExec=autoMonitorable_tftpExec;


void TftpExec::__monitor_invokable_exec_start(void)
{

    const char* p=(const char*)bsp_raw_storable_config_load();
    if (p && (p[0]==';') && (p[1]=='+') && (p[2]=='+'))
    {
        rtlog_printf("TFTPD", rtlog_warning|rtlog_breaking_news, "try exec start script (%p)", p);
        mex_script_inject(p, 2000);
    }
    else
        rtlog_printf("TFTPD", rtlog_warning|rtlog_breaking_news, "invalid start script (%p)", p);
}

static int grifo_tftpd_start()
{
	tftpExec.start();
	return 0;
}

#include "grifo_tftp_services.h"

GrifoTftpService& GrifoTftpService::instance()
{
	static GrifoTftpService theInstance;

	return theInstance;
}

void GrifoTftpService::setPort(int port)
{
	autoMonitorable_tftpExec.srv_port=port;
}

void GrifoTftpService::start(const char* site_name)
{
	if (site_name)
	{
		tcx->hello=true;
		tcx->site_name=site_name;
	}
	grifo_tftpd_start();
}

void GrifoTftpService::sendWakeupOnStart(unsigned int delay_ms)
{
	autoMonitorable_tftpExec.please_wakeup=true;
	autoMonitorable_tftpExec.wakeup_delay_ms=delay_ms;
}

GrifoTftpService::GrifoTftpService()
{
    tcx=&tftpExec;
	tcx->hello=false;
	tcx->site_name="????";
	//grifo_tftpd_start();
}

//void GrifoTftpService::setSiteName(const char* site_name)
//{
//	tcx->site_name=site_name;
//}



