/* Copyright (C) 2002-2003 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2015-2024 m-privacy GmbH, Berlin
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

//
// rdr::OutStream marshalls data into a buffer stored in RDR (RFB Data
// Representation).
//

#ifndef __RDR_OUTSTREAM_H__
#define __RDR_OUTSTREAM_H__

#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef WIN32
#include <wtypes.h>
#include <dbghelp.h>
#else
#include <execinfo.h>
#endif

#ifndef WIN32
#define Sleep(x) usleep((x)*1000)
#endif

#include <rdr/types.h>
#include <rdr/mutex.h>
#include <rdr/Exception.h>
#include <rdr/InStream.h>
#include <rfb/LogWriter.h>

enum QPrioEnum {QPRIOLOW=0, QPRIOMEDIUM=1, QPRIOHIGH=2};
#define QPRIONUM (QPRIOHIGH + 1)
#define QPRIOMAX QPRIOHIGH
#define QPRIOMIN QPRIOLOW

typedef enum QPrioEnum QPrio;

#define MAXBUFFERSIZE 65535
#define MAXBUFFERNUM 65535

/* U32 serial + U32 CRC + U16 buffer size */
#define CHECKHEADERSIZE (sizeof(U32) + sizeof(U32) + sizeof(U16))

#ifndef MASTERKEY
#define MASTERKEY 0x999999
#endif

static rfb::LogWriter ovlog("OutStream");


namespace rdr {

  struct queueBuffer
  {
    U32  size;
    U32  used;
    U8 * data;
    struct queueBuffer * next;
    time_t lastUsed;
    U16  number;
  };

  class OutStream {

  protected:

    OutStream();

  public:

    virtual ~OutStream();

    virtual void savebacktrace(const char * function);

    inline bool check_key(int key, const char * name = NULL) {
      if(key != storedKey && (!allowMasterKey || key != MASTERKEY)) {
        if (storedKey) {
          classLog->error("key %u != storedKey %u for function %s on object %p", key, storedKey, name ? name : "OutStream", this);
          if (classLog->getLevel() >= classLog->LEVEL_DEBUG)
            savebacktrace(name ? name : __func__);
          return false;
        } else {
          storedKey = key;
          classLog->debug("set storedKey for function %s on object %p to %u", name ? name : "OutStream", this, storedKey);
        }
      }
      return true;
    }

    inline bool check_prio(QPrio prio, const char * name = NULL) {
      if(prio > QPRIOMAX) {
        classLog->error("function %s in object %p used QPrio %u > maximum %u", name ? name : "OutStream", this, prio, QPRIOMAX);
        return false;
      }
      return true;
    }

    /*
     * check() ensures there is buffer space for at least length bytes
     * We always have or make space, but allow to override with virtual
     */
    virtual void check(size_t length, int key=0, QPrio prio=QPRIOMEDIUM) {}

    /*
     * overrun() ensures there is buffer space for at least needed bytes
     * We always have or make space, but allow to override with virtual
     */
    virtual void overrun(size_t needed, int key=0, QPrio prio=QPRIOMEDIUM) {}

    // writeU/SN() methods write unsigned and signed N-bit integers.
    // inefficient, but working

    virtual void writeU8(U8 u, int key=0, QPrio prio=QPRIOMEDIUM);
    virtual void writeU16(U16 u, int key=0, QPrio prio=QPRIOMEDIUM);
    virtual void writeU32(U32 u, int key=0, QPrio prio=QPRIOMEDIUM);

    virtual void writeS8( S8 s) {
      writeU8((U8)s);
    }
    virtual void writeS16(S16 s) {
      writeU16((U16)s);
    }
    virtual void writeS32(S32 s) {
      writeU32((U32)s);
    }

    inline void copyU8(U8 * data, U8 u) {
      *data = u;
    }

    inline void copyU16(U8 * data, U16 u) {
      *data = (U8) (u >> 8);
      *(data + 1) = (U8) u;
    }

    inline void copyU32(U8 * data, U32 u) {
      *data = (U8) (u >> 24);
      *(data + 1) = (U8) (u >> 16);
      *(data + 2) = (U8) (u >> 8);
      *(data + 3) = (U8) u;
    }

    virtual void pad(size_t bytes, int key=0, QPrio prio=QPRIOMEDIUM);

    virtual void skip(size_t bytes, int key=0, QPrio prio=QPRIOMEDIUM) {
      classLog->error("skip() should not be used here, calling pad() instead!");
      pad(bytes, key, prio);
    }

    /* writeBytes() writes an exact number of bytes. */
    virtual void writeBytes(const void* data, size_t numBytes, int key=0, QPrio prio=QPRIOMEDIUM);

    // copyBytes() efficiently transfers data between streams (but not here)
    virtual void copyBytes(InStream* is, int length, int key=0, QPrio prio=QPRIOMEDIUM) {
      classLog->error("copyBytes() not available here!");
    }

    // writeOpaqueN() writes a quantity without byte-swapping.
    virtual void writeOpaque8(U8 u, int key=0, QPrio prio=QPRIOMEDIUM);
    virtual void writeOpaque16(U16 u, int key=0, QPrio prio=QPRIOMEDIUM);
    virtual void writeOpaque32(U32 u, int key=0, QPrio prio=QPRIOMEDIUM);

    // length() returns the length of the stream.
    virtual size_t length(int key=0, QPrio prio=QPRIOMEDIUM) { return 0; }
    virtual int bufferUsage(int key=0, QPrio prio=QPRIOMEDIUM) { return queueEmpty(prio) ? 0 : 1; }

    // flush() requests that the stream be flushed.
    virtual void flush(int key=0, QPrio prio=QPRIOMEDIUM, bool wait=false) {}

    // avail() returns the number of bytes that currently be written to the
    // stream without any risk of blocking.
    virtual size_t avail(int key=0, QPrio prio=QPRIOMEDIUM) {
      return maxBufferSize;
    }

    // cork() requests that the stream coalesces flushes in an efficient way
    virtual void cork(bool enable, int key=0, QPrio prio=QPRIOMEDIUM) { corked = enable; flush(MASTERKEY, prio); }
    virtual U8* getptr(size_t length, QPrio prio=QPRIOMEDIUM) {
      classLog->error("getptr() not available here!");
      return NULL;
    }
    virtual U8* getend(QPrio prio=QPRIOMEDIUM) {
      classLog->error("getend() not available here!");
      return NULL;
    }
    virtual void setptr(size_t length, int key=0, QPrio prio=QPRIOMEDIUM) {
      classLog->error("setptr() not available here!");
    }

    virtual bool sendSignal(U16 sigId, const void* data = NULL, U16 dataLength = 0, int key=0, QPrio prio=QPRIOMEDIUM) {return false;}

    // Buffer Queueing
    // Buffers are per queue level.
    // A buffer got here must be returned to the level's pool with
    // returnQueueBuffer() later.
    // get unused buffer of given size
    virtual struct queueBuffer * getQueueBuffer(U32 size, QPrio prio=QPRIOMEDIUM);

    // return a chain of buffers that are no longer needed to buffer pool
    // last buffer in chain must have buffer->next == NULL
    virtual void returnQueueBuffer(struct queueBuffer * buffer, QPrio prio=QPRIOMEDIUM, time_t now=0);

    // Submit a chain of buffers to called layer for sending.
    // Last buffer in chain must have next == NULL (preset by getQueueBuffer())
    // Returns true if success.
    // Queues are per layer.
    virtual bool submitQueueBuffer(struct queueBuffer * firstBuffer, struct queueBuffer * lastBuffer, int key=0, QPrio prio=QPRIOMEDIUM);

    // get next single buffer out of queue, returns NULL if empty
    virtual struct queueBuffer * popBuffer (QPrio prio);

    // put pop'd single buffer back to head of queue in case lower level failed
    virtual void pushBuffer (struct queueBuffer * buffer, QPrio prio);

    inline U16 getMaxBufferSize () { return maxBufferSize; }

    virtual void printBufferUsage();

    // queue empty?
    bool queueEmpty (QPrio prio);

    void resetClassLog() { classLog = &ovlog; }

    void setAllowMasterKey(bool value) { allowMasterKey = value; }

    void fillCheckHeader(struct queueBuffer * buffer, U8 header[CHECKHEADERSIZE]);

  protected:
    bool corked;
    int storedKey;
    rfb::LogWriter * classLog;
    U16 maxBuffers[QPRIONUM];
    U16 keepBuffers[QPRIONUM];
    U16 flushThreshBuffers[QPRIONUM];
    U16 maxBufferSize;
    U32 serial;

  private:
    /* buffers per queue */
    MUTEX_TYPE bufferPoolMutex[QPRIONUM];
    struct queueBuffer * unusedBufferHead[QPRIONUM];
    struct queueBuffer * unusedBufferTail[QPRIONUM];
    U16 numBuffers[QPRIONUM];
    U16 spareBuffers[QPRIONUM];
    U16 maxUsedBuffers[QPRIONUM];
    MUTEX_TYPE queueMutex[QPRIONUM];
    struct queueBuffer * bufferQueueHead[QPRIONUM];
    struct queueBuffer * bufferQueueTail[QPRIONUM];
    U32 overKeepCount[QPRIONUM];
    unsigned submitCount[QPRIONUM];
    bool allowMasterKey;
    MUTEX_TYPE serialMutex;
  };
}

#endif
