/*********************************************************
 * Copyright (C) 1999-2017, 2021 VMware, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation version 2.1 and no later version.
 *
 * This program 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 Lesser GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *********************************************************/

/*********************************************************
 * The contents of this file are subject to the terms of the Common
 * Development and Distribution License (the "License") version 1.0
 * and no later version.  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 *         http://www.opensource.org/licenses/cddl1.php
 *
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 *********************************************************/

/*
 * message.c --
 *
 *    Second layer of the internal communication channel between guest
 *    applications and vmware
 *
 *    Build a generic messaging system between guest applications and vmware.
 *
 *    The protocol is not completely symmetrical, because:
 *     . basic requests can only be sent by guest applications (when vmware
 *       wants to post a message to a guest application, the message will be
 *       really fetched only when the guest application will poll for new
 *       available messages)
 *     . several guest applications can talk to vmware, while the contrary is
 *       not true
 *
 *    Operations that are not atomic (in terms of number of backdoor calls)
 *    can be canceled by vmware if a checkpoint/restore occurs in the middle of
 *    such an operation. This layer takes care of retrying those operations.
 */

#include "backdoor_def.h"
#include "guest_msg_def.h"

#ifdef __cplusplus
extern "C" {
#endif

#if defined(__KERNEL__) || defined(_KERNEL) || defined(KERNEL)
#   include "kernelStubs.h"
#else
#   include <stdio.h>
#   include <stdlib.h>
#   include "debug.h"
#endif

#include "backdoor.h"
#include "message.h"

#if defined(MESSAGE_DEBUG)
#  define MESSAGE_LOG(...)   Warning(__VA_ARGS__)
#else
#  define MESSAGE_LOG(...)
#endif


/*
 *-----------------------------------------------------------------------------
 *
 * Message_OpenAllocated --
 *
 *    Open a communication channel using an allocated, but unitialized
 *    Message_Channel structure.  A receive buffer may be optionally
 *    specified with a given size.  If a message larger than this
 *    buffer is received then the communication will end.  If no
 *    receiver buffer is specified, one will be dynamically allocated
 *    to size.  When finished with the channel, Message_CloseAllocated
 *    should be called.
 *
 * Result:
 *    TRUE on success, FALSE on failure.
 *
 * Side-effects:
 *    See above.
 *
 *-----------------------------------------------------------------------------
 */

Bool
Message_OpenAllocated(uint32 proto, Message_Channel *chan,
                      char *receiveBuffer, size_t receiveBufferSize)
{
   uint32 flags;
   Backdoor_proto bp;

   flags = GUESTMSG_FLAG_COOKIE;
retry:
   /* IN: Type */
   bp.in.cx.halfs.high = MESSAGE_TYPE_OPEN;
   /* IN: Magic number of the protocol and flags */
   bp.in.size = proto | flags;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      if (flags) {
         /* Cookies not supported. Fall back to no cookie. --hpreg */
         flags = 0;
         goto retry;
      }

      MESSAGE_LOG("Message: Unable to open a communication channel\n");
      return FALSE;
   }

   /* OUT: Id and cookie */
   chan->id = bp.in.dx.halfs.high;
   chan->cookieHigh = bp.out.si.word;
   chan->cookieLow = bp.out.di.word;

   /* Initialize the channel */
   chan->in = (unsigned char *)receiveBuffer;
   chan->inAlloc = receiveBufferSize;

   ASSERT((receiveBuffer == NULL) == (receiveBufferSize == 0));
   chan->inPreallocated = receiveBuffer != NULL;

   return TRUE;
}


/*
 *-----------------------------------------------------------------------------
 *
 * Message_Open --
 *
 *    Open a communication channel
 *
 * Result:
 *    An allocated Message_Channel on success
 *    NULL on failure
 *
 * Side-effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */

Message_Channel *
Message_Open(uint32 proto) // IN
{
   Message_Channel *chan = malloc(sizeof *chan);

   if (chan != NULL && !Message_OpenAllocated(proto, chan, NULL, 0)) {
      free(chan);
      chan = NULL;
   }
   return chan;
}


/*
 *-----------------------------------------------------------------------------
 *
 * Message_Send --
 *
 *    Send a message over a communication channel
 *
 * Result:
 *    TRUE on success
 *    FALSE on failure (the message is discarded by vmware)
 *
 * Side-effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */

Bool
Message_Send(Message_Channel *chan,    // IN/OUT
             const unsigned char *buf, // IN
             size_t bufSize)           // IN
{
   const unsigned char *myBuf;
   size_t myBufSize;
   Backdoor_proto bp;

retry:
   myBuf = buf;
   myBufSize = bufSize;

   /*
    * Send the size.
    */

   /* IN: Type */
   bp.in.cx.halfs.high = MESSAGE_TYPE_SENDSIZE;
   /* IN: Id and cookie */
   bp.in.dx.halfs.high = chan->id;
   bp.in.si.word = chan->cookieHigh;
   bp.in.di.word = chan->cookieLow;
   /* IN: Size */
   bp.in.size = myBufSize;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      MESSAGE_LOG("Message: Unable to send a message over the communication "
                  "channel %u\n", chan->id);
      return FALSE;
   }

   if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {
      /*
       * High-bandwidth backdoor port supported. Send the message in one
       * backdoor operation. --hpreg
       */

      if (myBufSize) {
         Backdoor_proto_hb bphb;

         bphb.in.bx.halfs.low = BDOORHB_CMD_MESSAGE;
         bphb.in.bx.halfs.high = MESSAGE_STATUS_SUCCESS;
         bphb.in.dx.halfs.high = chan->id;
         bphb.in.bp.word = chan->cookieHigh;
         bphb.in.dstAddr = chan->cookieLow;
         bphb.in.size = myBufSize;
         bphb.in.srcAddr = (uintptr_t) myBuf;
         Backdoor_HbOut(&bphb);
         if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
            if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
               /* A checkpoint occurred. Retry the operation. --hpreg */
               goto retry;
            }

            MESSAGE_LOG("Message: Unable to send a message over the "
                        "communication channel %u\n", chan->id);
            return FALSE;
         }
      }
   } else {
      /*
       * High-bandwidth backdoor port not supported. Send the message, 4 bytes
       * at a time. --hpreg
       */

      for (;;) {
         if (myBufSize == 0) {
            /* We are done */
	    break;
         }

         /* IN: Type */
         bp.in.cx.halfs.high = MESSAGE_TYPE_SENDPAYLOAD;
         /* IN: Id and cookie */
         bp.in.dx.halfs.high = chan->id;
         bp.in.si.word = chan->cookieHigh;
         bp.in.di.word = chan->cookieLow;
         /* IN: Piece of message */
         /*
          * Beware in case we are not allowed to read extra bytes beyond the
          * end of the buffer.
          */
         switch (myBufSize) {
         case 1:
            bp.in.size = myBuf[0];
            myBufSize -= 1;
            break;
         case 2:
            bp.in.size = myBuf[0] | myBuf[1] << 8;
            myBufSize -= 2;
            break;
         case 3:
            bp.in.size = myBuf[0] | myBuf[1] << 8 | myBuf[2] << 16;
            myBufSize -= 3;
            break;
         default:
            bp.in.size = *(const uint32 *)myBuf;
            myBufSize -= 4;
            break;
         }

         bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
         Backdoor(&bp);

         /* OUT: Status */
         if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
            if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
               /* A checkpoint occurred. Retry the operation. --hpreg */
               goto retry;
            }

            MESSAGE_LOG("Message: Unable to send a message over the "
                        "communication channel %u\n", chan->id);
            return FALSE;
         }

         myBuf += 4;
      }
   }

   return TRUE;
}


/*
 *-----------------------------------------------------------------------------
 *
 * Message_Receive --
 *
 *    If vmware has posted a message for this channel, retrieve it
 *
 * Result:
 *    TRUE on success (bufSize is 0 if there is no message)
 *    FALSE on failure
 *
 * Side-effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */

Bool
Message_Receive(Message_Channel *chan, // IN/OUT
                unsigned char **buf,   // OUT
                size_t *bufSize)       // OUT
{
   Backdoor_proto bp;
   size_t myBufSize;
   unsigned char *myBuf;

retry:
   /*
    * Is there a message waiting for our retrieval?
    */

   /* IN: Type */
   bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSIZE;
   /* IN: Id and cookie */
   bp.in.dx.halfs.high = chan->id;
   bp.in.si.word = chan->cookieHigh;
   bp.in.di.word = chan->cookieLow;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      MESSAGE_LOG("Message: Unable to poll for messages over the "
                  "communication channel %u\n", chan->id);
      return FALSE;
   }

   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_DORECV) == 0) {
      /* No message to retrieve */
      *bufSize = 0;
      return TRUE;
   }

   /*
    * Receive the size.
    */

   /* OUT: Type */
   if (bp.in.dx.halfs.high != MESSAGE_TYPE_SENDSIZE) {
      MESSAGE_LOG("Message: Protocol error. Expected a "
                  "MESSAGE_TYPE_SENDSIZE request from vmware\n");
      return FALSE;
   }

   /* OUT: Size */
   myBufSize = bp.out.bx.word;

   /*
    * Allocate an extra byte for a trailing NUL character. The code that will
    * deal with this message may not know about binary strings, and may expect
    * a C string instead. --hpreg
    */
   if (myBufSize + 1 > chan->inAlloc) {
      if (chan->inPreallocated) {
         MESSAGE_LOG("Message: Buffer too small to receive a message over "
                     "the communication channel %u\n", chan->id);
         goto error_quit;
      } else {
         myBuf = (unsigned char *)realloc(chan->in, myBufSize + 1);
         if (myBuf == NULL) {
            MESSAGE_LOG("Message: Not enough memory to receive a message over "
                        "the communication channel %u\n", chan->id);
            goto error_quit;
         }
         chan->in = myBuf;
         chan->inAlloc = myBufSize + 1;
      }
   }
   *bufSize = myBufSize;
   myBuf = *buf = chan->in;

   if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {
      /*
       * High-bandwidth backdoor port supported. Receive the message in one
       * backdoor operation. --hpreg
       */

      if (myBufSize) {
         Backdoor_proto_hb bphb;

         bphb.in.bx.halfs.low = BDOORHB_CMD_MESSAGE;
         bphb.in.bx.halfs.high = MESSAGE_STATUS_SUCCESS;
         bphb.in.dx.halfs.high = chan->id;
         bphb.in.srcAddr = chan->cookieHigh;
         bphb.in.bp.word = chan->cookieLow;
         bphb.in.size = myBufSize;
         bphb.in.dstAddr = (uintptr_t) myBuf;
         Backdoor_HbIn(&bphb);
         if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
            if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
               /* A checkpoint occurred. Retry the operation. --hpreg */
               goto retry;
            }

            MESSAGE_LOG("Message: Unable to receive a message over the "
                        "communication channel %u\n", chan->id);
            goto error_quit;
         }
      }
   } else {
      /*
       * High-bandwidth backdoor port not supported. Receive the message, 4
       * bytes at a time. --hpreg
       */

      for (;;) {
         if (myBufSize == 0) {
            /* We are done */
            break;
         }

         /* IN: Type */
         bp.in.cx.halfs.high = MESSAGE_TYPE_RECVPAYLOAD;
         /* IN: Id and cookie */
         bp.in.dx.halfs.high = chan->id;
         bp.in.si.word = chan->cookieHigh;
         bp.in.di.word = chan->cookieLow;
         /* IN: Status for the previous request (that succeeded) */
         bp.in.size = MESSAGE_STATUS_SUCCESS;

         bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
         Backdoor(&bp);

         /* OUT: Status */
         if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
            if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
               /* A checkpoint occurred. Retry the operation. --hpreg */
               goto retry;
            }

            MESSAGE_LOG("Message: Unable to receive a message over the "
                        "communication channel %u\n", chan->id);
            goto error_quit;
         }

         /* OUT: Type */
         if (bp.in.dx.halfs.high != MESSAGE_TYPE_SENDPAYLOAD) {
            MESSAGE_LOG("Message: Protocol error. Expected a "
                        "MESSAGE_TYPE_SENDPAYLOAD from vmware\n");
            goto error_quit;
         }

         /* OUT: Piece of message */
         /*
          * Beware in case we are not allowed to write extra bytes beyond the
          * end of the buffer. --hpreg
          */
         switch (myBufSize) {
         case 1:
            myBuf[0] = bp.out.bx.word & 0xff;
            myBufSize -= 1;
            break;
         case 2:
            myBuf[0] = bp.out.bx.word & 0xff;
            myBuf[1] = (bp.out.bx.word >> 8) & 0xff;
            myBufSize -= 2;
            break;
         case 3:
            myBuf[0] = bp.out.bx.word & 0xff;
            myBuf[1] = (bp.out.bx.word >> 8) & 0xff;
            myBuf[2] = (bp.out.bx.word >> 16) & 0xff;
            myBufSize -= 3;
            break;
         default:
            *(uint32 *)myBuf = bp.out.bx.word;
            myBufSize -= 4;
            break;
         }

         myBuf += 4;
      }
   }

   /* Write a trailing NUL just after the message. --hpreg */
   chan->in[*bufSize] = '\0';

   /* IN: Type */
   bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSTATUS;
   /* IN: Id and cookie */
   bp.in.dx.halfs.high = chan->id;
   bp.in.si.word = chan->cookieHigh;
   bp.in.di.word = chan->cookieLow;
   /* IN: Status for the previous request (that succeeded) */
   bp.in.size = MESSAGE_STATUS_SUCCESS;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
	 /* A checkpoint occurred. Retry the operation. --hpreg */
	 goto retry;
      }

      MESSAGE_LOG("Message: Unable to receive a message over the "
                  "communication channel %u\n", chan->id);
      goto error_quit;
   }

   return TRUE;

error_quit:
   /* IN: Type */
   if (myBufSize == 0) {
      bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSTATUS;
   } else {
      bp.in.cx.halfs.high = MESSAGE_TYPE_RECVPAYLOAD;
   }
   /* IN: Id and cookie */
   bp.in.dx.halfs.high = chan->id;
   bp.in.si.word = chan->cookieHigh;
   bp.in.di.word = chan->cookieLow;
   /* IN: Status for the previous request (that failed) */
   bp.in.size = 0;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      MESSAGE_LOG("Message: Unable to signal an error of reception over the "
                  "communication channel %u\n", chan->id);
      return FALSE;
   }

   return FALSE;
}


/*
 *-----------------------------------------------------------------------------
 *
 * Message_CloseAllocated --
 *
 *    Close a communication channel that had been allocated by the
 *    caller.  (For use with Message_OpenAllocated.)
 *
 * Result:
 *    TRUE on success, the channel is destroyed
 *    FALSE on failure
 *
 * Side-effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */

Bool
Message_CloseAllocated(Message_Channel *chan) // IN/OUT
{
   Backdoor_proto bp;
   Bool ret = TRUE;

   /* IN: Type */
   bp.in.cx.halfs.high = MESSAGE_TYPE_CLOSE;
   /* IN: Id and cookie */
   bp.in.dx.halfs.high = chan->id;
   bp.in.si.word = chan->cookieHigh;
   bp.in.di.word = chan->cookieLow;

   bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
   Backdoor(&bp);

   /* OUT: Status */
   if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
      MESSAGE_LOG("Message: Unable to close the communication channel %u\n",
                  chan->id);
      ret = FALSE;
   }

   if (!chan->inPreallocated) {
      free(chan->in);
   }
   chan->in = NULL;

   return ret;
}
   

/*
 *-----------------------------------------------------------------------------
 *
 * Message_Close --
 *
 *    Close a communication channel.
 *
 * Result:
 *    TRUE on success, the channel is destroyed
 *    FALSE on failure
 *
 * Side-effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */

Bool
Message_Close(Message_Channel *chan) // IN/OUT
{
   Bool ret = Message_CloseAllocated(chan);

   free(chan);
   return ret;
}

#ifdef __cplusplus
}
#endif
