# MemoryDaemon.java

    /*
    ** Module   : MemoryDaemon.java
    ** Abstract : remote access to low-level memory buffer functions
    **
    ** Copyright (c) 2013-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------Description---------------------------------
    ** 001 GES 20130430 First version. The idea is to provide remote access from the server to the
    **                  client-side low-level memory management functions. For security purposes,
    **                  this class is intentionally package private.
    ** 002 ECF 20150824 Implemented cleanup processing to deallocate memory not previously freed
    **                  explicitly. This will happen in response to a server-side context cleanup.
    ** 003 OM  20160705 Added reference counter.
    ** 004 GES 20180322 Added compiledWordSize().
    ** 005 GBB 20240826 Moving to package osresource. Making access to the class package-private.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any 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
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util.osresource;

    import java.util.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.util.*;

    /**
     * Provides remote access to the {@link MemoryManager} static functions.
     */
    class MemoryDaemon
    implements LowLevelBuffer
    {
       /** Token used to authenticate with the dispatcher when registering APIs. */
       private static Object modToken = null;

       /**
        * All currently allocated addresses mapped to their usage counters. If the counter reaches 0
        * the the memory should be freed.
        */
       private Map<Long, Integer> allocatedAddresses = new HashMap<>();

       /**
        * Create an instance and export its API to the network.
        *
        * @param   single
        *          {@code true} to startup within the server process which must bypass the shared
        *          infrastructure initialization. Use {@code false} for the normal client JVM startup.
        */
       MemoryDaemon(boolean single)
       {
          synchronized (MemoryDaemon.class)
          {
             modToken = RemoteObject.registerServer(LowLevelBuffer.class, this, modToken, single);
          }
       }

       /**
        * Reports on the native byte-ordering of the platform on which this library is
        * running.
        *
        * @return  {@code true} if the platform uses little-endian byte-ordering, {@code false} if
        *          the platform is big-endian.
        */
       @Override
       public boolean isLittleEndian()
       {
          return MemoryManager.isLittleEndian();
       }

       /**
        * Returns the word size (the size of a pointer) for the native code as compiled. This may be
        * different than the word size of the operating system in the case where 32-bit code is run
        * on a 64-bit system.
        *
        * @return   This value will be either 32 or 64 depending on how the native library was
        *           compiled.
        */
       public int compiledWordSize()
       {
          return MemoryManager.compiledWordSize();
       }

       /**
        * Allocate the given number of bytes as a new memory block and return the 64-bit address. All
        * bytes in the memory region will be initialized to 0x00.
        * <p>
        * To duplicate the permissiveness of the 4GL memptr allocation (where certain operations often
        * can read or write past the end of the allocated buffer without an access violation), the
        * buffer allocated by this function will always be 16 bytes larger than the requested size.
        *
        * @param    len
        *           The size in bytes of the memory region to allocate.
        *
        * @return   The address of the allocated memory region or 0 if memory could not be allocated.
        */
       @Override
       public long allocate(long len)
       {
          long addr = MemoryManager.allocate(len);

          if (addr != 0)
          {
             allocatedAddresses.put(addr, 1);
          }

          return addr;
       }

       /**
        * De-allocate the memory block at the given 64-bit address. If this method returns
        * {@code true}, the memory in this region MUST NOT be accessed after this call.
        *
        * @param   addr
        *          The 64-bit pointer to a memory region previously allocated using this module.
        *          DO NOT pass in an invalid pointer.  DO NOT deallocate the same pointer after this
        *          method returned {@code true}.
        *
        * @return  {@code true} if the memory was deallocated and {@code false} if the deallocation
        *          failed because this memory zone was shared with other variable. If so then the
        *          counter was decremented and the memory will be released when it reaches zero.
        */
       @Override
       public boolean deallocate(long addr)
       {
          Integer counter = allocatedAddresses.get(addr);

          if (counter == null)
          {
             // not a valid memory address
             return false;
          }

          if (counter > 1)
          {
             // deallocation cannot be done there are other references to this memory. Decrement the
             // counter and notify the caller that the memory is not free.
             allocatedAddresses.put(addr, counter - 1);
             return false;
          }

          deallocateImpl(addr, false);
          return true;
       }

       /**
        * Increments the number of references to this memory. When a variable is deallocated, just
        * call {@link #deallocate(long)}. It will automatically decrement the counter and truly
        * deallocate the memory when it reaches 0.
        *
        * @param    addr
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        */
       @Override
       public void incrementReference(long addr)
       {
          Integer counter = allocatedAddresses.get(addr);
          if (counter != null)
          {
             allocatedAddresses.put(addr, counter + 1);
          }
       }

       /**
        * Returns the next index position into the array which is the {@code null} byte, starting
        * at the given index position.
        *
        * @param    addr
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        * @param    idx
        *           The index position at which to start the search.
        * @param    size
        *           The length of the buffer. If the search reaches this byte without finding a
        *           null byte, then this value will be returned. If this is given as -1, then the
        *           caller has no size information and we operate in unsafe mode.  In this mode
        *           the code will read until it finds a {@code null} or gets an access violation.
        *           This is how the 4GL does it too.
        *
        * @return   The index of the next byte position which contains the {@code null} byte or the
        *           length of the array if no {@code null} byte is contained.
        */
       @Override
       public long findNextNull(long addr, long idx, long size)
       {
          return MemoryManager.findNextNull(addr, idx, size);
       }

       /**
        * Read the specified range of contents from the memory block at the given 64-bit address.
        *
        * @param    addr
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        * @param    offset
        *           The 0-based index position from the beginning of the buffer, from which to start
        *           reading.
        * @param    len
        *           The number of bytes to read. Must be greater than 0 and must NOT extend past the
        *           end of the buffer.
        *
        * @return   The range of bytes read or {@code null} if there is any problem allocating
        *           the array.
        */
       @Override
       public byte[] read(long addr, long offset, long len)
       {
          return MemoryManager.read(addr, offset, len);
       }

       /**
        * Write the specified contents to the memory block at the given 64-bit address plus offset.
        *
        * @param    addr
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        * @param    offset
        *           The 0-based index position from the beginning of the buffer, at which to start
        *           writing.
        * @param    data
        *           The bytes to write.  The entire contents of the array will be written. The length
        *           of the array must not be bigger than the number of bytes between the offset and
        *           the end of the buffer.
        */
       @Override
       public void write(long addr, long offset, byte[] data)
       {
          MemoryManager.write(addr, offset, data);
       }

       /**
        * Copy the specified range of contents from the source memory block to the destination memory
        * block.
        *
        * @param    source
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        * @param    dest
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.
        * @param    offset
        *           The 0-based index position from the beginning of the buffer, at which to start
        *           writing.
        * @param    len
        *           The number of bytes to write. Must be greater than 0 and must NOT extend past the
        *           end of the destination buffer.
        */
       @Override
       public void copy(long source, long dest, long offset, long len)
       {
          MemoryManager.copy(source, dest, offset, len);
       }

       /**
        * Deallocate all memory allocations which have not been deallocated explicitly previously.
        */
       @Override
       public void cleanup()
       {
          for (Long addr : allocatedAddresses.keySet())
          {
             deallocateImpl(addr, true);
          }
          allocatedAddresses.clear();
       }

       /**
        * De-allocate the memory block at the given 64-bit address.  Memory in this region MUST NOT
        * be accessed after this call.
        *
        * @param    addr
        *           The 64-bit pointer to a memory region previously allocated using this module.
        *           DO NOT pass in an invalid pointer.  DO NOT deallocate the same pointer more than
        *           once.
        * @param    cleanup
        *           {@code true} if called during cleanup processing; {@code false} if called for an
        *           explicit deallocation.
        */
       private void deallocateImpl(long addr, boolean cleanup)
       {
          MemoryManager.deallocate(addr);
          if (!cleanup)
          {
             allocatedAddresses.remove(addr);
          }
       }
    }