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);
      }
   }
}