/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ArrayBuffer functionality. */

#ifndef js_ArrayBuffer_h
#define js_ArrayBuffer_h

#include <stddef.h>  // size_t
#include <stdint.h>  // uint32_t

#include "jstypes.h"  // JS_PUBLIC_API

#include "js/GCAPI.h"       // JS::AutoRequireNoGC
#include "js/RootingAPI.h"  // JS::Handle

struct JS_PUBLIC_API JSContext;
class JS_PUBLIC_API JSObject;

namespace JS {

// CREATION

/**
 * Create a new ArrayBuffer with the given byte length.
 */
extern JS_PUBLIC_API JSObject* NewArrayBuffer(JSContext* cx, uint32_t nbytes);

/**
 * Create a new ArrayBuffer with the given |contents|, which may be null only
 * if |nbytes == 0|.  |contents| must be allocated compatible with deallocation
 * by |JS_free|.
 *
 * If and only if an ArrayBuffer is successfully created and returned,
 * ownership of |contents| is transferred to the new ArrayBuffer.
 */
extern JS_PUBLIC_API JSObject* NewArrayBufferWithContents(JSContext* cx,
                                                          size_t nbytes,
                                                          void* contents);

using BufferContentsFreeFunc = void (*)(void* contents, void* userData);

/**
 * Create a new ArrayBuffer with the given contents. The contents must not be
 * modified by any other code, internal or external.
 *
 * When the ArrayBuffer is ready to be disposed of, `freeFunc(contents,
 * freeUserData)` will be called to release the ArrayBuffer's reference on the
 * contents.
 *
 * `freeFunc()` must not call any JSAPI functions that could cause a garbage
 * collection.
 *
 * The caller must keep the buffer alive until `freeFunc()` is called, or, if
 * `freeFunc` is null, until the JSRuntime is destroyed.
 *
 * The caller must not access the buffer on other threads. The JS engine will
 * not allow the buffer to be transferred to other threads. If you try to
 * transfer an external ArrayBuffer to another thread, the data is copied to a
 * new malloc buffer. `freeFunc()` must be threadsafe, and may be called from
 * any thread.
 *
 * This allows ArrayBuffers to be used with embedder objects that use reference
 * counting, for example. In that case the caller is responsible
 * for incrementing the reference count before passing the contents to this
 * function. This also allows using non-reference-counted contents that must be
 * freed with some function other than free().
 */
extern JS_PUBLIC_API JSObject* NewExternalArrayBuffer(
    JSContext* cx, size_t nbytes, void* contents,
    BufferContentsFreeFunc freeFunc, void* freeUserData = nullptr);

/**
 * Create a new ArrayBuffer with the given non-null |contents|.
 *
 * Ownership of |contents| remains with the caller: it isn't transferred to the
 * returned ArrayBuffer.  Callers of this function *must* ensure that they
 * perform these two steps, in this order, to properly relinquish ownership of
 * |contents|:
 *
 *   1. Call |JS::DetachArrayBuffer| on the buffer returned by this function.
 *      (|JS::DetachArrayBuffer| is generally fallible, but a call under these
 *      circumstances is guaranteed to succeed.)
 *   2. |contents| may be deallocated or discarded consistent with the manner
 *      in which it was allocated.
 *
 * Do not simply allow the returned buffer to be garbage-collected before
 * deallocating |contents|, because in general there is no way to know *when*
 * an object is fully garbage-collected to the point where this would be safe.
 */
extern JS_PUBLIC_API JSObject* NewArrayBufferWithUserOwnedContents(
    JSContext* cx, size_t nbytes, void* contents);

/**
 * Create a new mapped ArrayBuffer with the given memory mapped contents. It
 * must be legal to free the contents pointer by unmapping it. On success,
 * ownership is transferred to the new mapped ArrayBuffer.
 */
extern JS_PUBLIC_API JSObject* NewMappedArrayBufferWithContents(JSContext* cx,
                                                                size_t nbytes,
                                                                void* contents);

/**
 * Create memory mapped ArrayBuffer contents.
 * Caller must take care of closing fd after calling this function.
 */
extern JS_PUBLIC_API void* CreateMappedArrayBufferContents(int fd,
                                                           size_t offset,
                                                           size_t length);

/**
 * Release the allocated resource of mapped ArrayBuffer contents before the
 * object is created.
 * If a new object has been created by JS::NewMappedArrayBufferWithContents()
 * with this content, then JS::DetachArrayBuffer() should be used instead to
 * release the resource used by the object.
 */
extern JS_PUBLIC_API void ReleaseMappedArrayBufferContents(void* contents,
                                                           size_t length);

// TYPE TESTING

/*
 * Check whether obj supports the JS::GetArrayBuffer* APIs.  Note that this may
 * return false if a security wrapper is encountered that denies the unwrapping.
 * If this test succeeds, then it is safe to call the various predicate and
 * accessor JSAPI calls defined below.
 */
extern JS_PUBLIC_API bool IsArrayBufferObject(JSObject* obj);

// PREDICATES

/**
 * Check whether the obj is a detached ArrayBufferObject. Note that this may
 * return false if a security wrapper is encountered that denies the
 * unwrapping.
 */
extern JS_PUBLIC_API bool IsDetachedArrayBufferObject(JSObject* obj);

/**
 * Check whether the obj is ArrayBufferObject and memory mapped. Note that this
 * may return false if a security wrapper is encountered that denies the
 * unwrapping.
 */
extern JS_PUBLIC_API bool IsMappedArrayBufferObject(JSObject* obj);

/**
 * Return true if the ArrayBuffer |obj| contains any data, i.e. it is not a
 * detached ArrayBuffer.  (ArrayBuffer.prototype is not an ArrayBuffer.)
 *
 * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known
 * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
 * ArrayBuffer, and the unwrapping will succeed.
 */
extern JS_PUBLIC_API bool ArrayBufferHasData(JSObject* obj);

// ACCESSORS

extern JS_PUBLIC_API JSObject* UnwrapArrayBuffer(JSObject* obj);

/**
 * Attempt to unwrap |obj| as an ArrayBuffer.
 *
 * If |obj| *is* an ArrayBuffer, return it unwrapped and set |*length| and
 * |*data| to weakly refer to the ArrayBuffer's contents.
 *
 * If |obj| isn't an ArrayBuffer, return nullptr and do not modify |*length| or
 * |*data|.
 */
extern JS_PUBLIC_API JSObject* GetObjectAsArrayBuffer(JSObject* obj,
                                                      uint32_t* length,
                                                      uint8_t** data);

/**
 * Return the available byte length of an ArrayBuffer.
 *
 * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known
 * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
 * ArrayBuffer, and the unwrapping will succeed.
 */
extern JS_PUBLIC_API uint32_t GetArrayBufferByteLength(JSObject* obj);

// This one isn't inlined because there are a bunch of different ArrayBuffer
// classes that would have to be individually handled here.
//
// There is an isShared out argument for API consistency (eases use from DOM).
// It will always be set to false.
extern JS_PUBLIC_API void GetArrayBufferLengthAndData(JSObject* obj,
                                                      uint32_t* length,
                                                      bool* isSharedMemory,
                                                      uint8_t** data);

/**
 * Return a pointer to the start of the data referenced by a typed array. The
 * data is still owned by the typed array, and should not be modified on
 * another thread. Furthermore, the pointer can become invalid on GC (if the
 * data is small and fits inside the array's GC header), so callers must take
 * care not to hold on across anything that could GC.
 *
 * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known
 * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
 * ArrayBuffer, and the unwrapping will succeed.
 *
 * |*isSharedMemory| is always set to false.  The argument is present to
 * simplify its use from code that also interacts with SharedArrayBuffer.
 */
extern JS_PUBLIC_API uint8_t* GetArrayBufferData(JSObject* obj,
                                                 bool* isSharedMemory,
                                                 const AutoRequireNoGC&);

// MUTATORS

/**
 * Detach an ArrayBuffer, causing all associated views to no longer refer to
 * the ArrayBuffer's original attached memory.
 *
 * This function throws only if it is provided a non-ArrayBuffer object or if
 * the provided ArrayBuffer is a WASM-backed ArrayBuffer or an ArrayBuffer used
 * in asm.js code.
 */
extern JS_PUBLIC_API bool DetachArrayBuffer(JSContext* cx,
                                            Handle<JSObject*> obj);

/**
 * Steal the contents of the given ArrayBuffer. The ArrayBuffer has its length
 * set to 0 and its contents array cleared. The caller takes ownership of the
 * return value and must free it or transfer ownership via
 * JS::NewArrayBufferWithContents when done using it.
 */
extern JS_PUBLIC_API void* StealArrayBufferContents(JSContext* cx,
                                                    Handle<JSObject*> obj);

}  // namespace JS

#endif /* js_ArrayBuffer_h */
