1
0
Fork 0
golang-github-dop251-goja-n.../buffer/buffer.go
Daniel Baumann 21b930d007
Adding upstream version 0.0~git20250409.f7acab6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 11:36:18 +02:00

1198 lines
36 KiB
Go

package buffer
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"math"
"math/big"
"reflect"
"strconv"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/errors"
"github.com/dop251/goja_nodejs/goutil"
"github.com/dop251/goja_nodejs/require"
"github.com/dop251/base64dec"
"golang.org/x/text/encoding/unicode"
)
const ModuleName = "buffer"
type Buffer struct {
r *goja.Runtime
bufferCtorObj *goja.Object
uint8ArrayCtorObj *goja.Object
uint8ArrayCtor goja.Constructor
}
var (
symApi = goja.NewSymbol("api")
)
var (
reflectTypeArrayBuffer = reflect.TypeOf(goja.ArrayBuffer{})
reflectTypeString = reflect.TypeOf("")
reflectTypeInt = reflect.TypeOf(int64(0))
reflectTypeFloat = reflect.TypeOf(0.0)
reflectTypeBytes = reflect.TypeOf(([]byte)(nil))
)
func Enable(runtime *goja.Runtime) {
runtime.Set("Buffer", require.Require(runtime, ModuleName).ToObject(runtime).Get("Buffer"))
}
func Bytes(r *goja.Runtime, v goja.Value) []byte {
var b []byte
err := r.ExportTo(v, &b)
if err != nil {
return []byte(v.String())
}
return b
}
func mod(r *goja.Runtime) *goja.Object {
res := r.Get("Buffer")
if res == nil {
res = require.Require(r, ModuleName).ToObject(r).Get("Buffer")
}
m, ok := res.(*goja.Object)
if !ok {
panic(r.NewTypeError("Could not extract Buffer"))
}
return m
}
func api(mod *goja.Object) *Buffer {
if s := mod.GetSymbol(symApi); s != nil {
b, _ := s.Export().(*Buffer)
return b
}
return nil
}
func GetApi(r *goja.Runtime) *Buffer {
return api(mod(r))
}
func DecodeBytes(r *goja.Runtime, arg, enc goja.Value) []byte {
switch arg.ExportType() {
case reflectTypeArrayBuffer:
return arg.Export().(goja.ArrayBuffer).Bytes()
case reflectTypeString:
var codec StringCodec
if !goja.IsUndefined(enc) {
codec = stringCodecs[enc.String()]
}
if codec == nil {
codec = utf8Codec
}
return codec.DecodeAppend(arg.String(), nil)
default:
if o, ok := arg.(*goja.Object); ok {
if o.ExportType() == reflectTypeBytes {
return o.Export().([]byte)
}
}
}
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"data\" argument must be of type string or an instance of Buffer, TypedArray, or DataView."))
}
func WrapBytes(r *goja.Runtime, data []byte) *goja.Object {
m := mod(r)
if api := api(m); api != nil {
return api.WrapBytes(data)
}
if from, ok := goja.AssertFunction(m.Get("from")); ok {
ab := r.NewArrayBuffer(data)
v, err := from(m, r.ToValue(ab))
if err != nil {
panic(err)
}
return v.ToObject(r)
}
panic(r.NewTypeError("Buffer.from is not a function"))
}
// EncodeBytes returns the given byte slice encoded as string with the given encoding. If encoding
// is not specified or not supported, returns a Buffer that wraps the data.
func EncodeBytes(r *goja.Runtime, data []byte, enc goja.Value) goja.Value {
var codec StringCodec
if !goja.IsUndefined(enc) {
codec = StringCodecByName(enc.String())
}
if codec != nil {
return r.ToValue(codec.Encode(data))
}
return WrapBytes(r, data)
}
func (b *Buffer) WrapBytes(data []byte) *goja.Object {
return b.fromBytes(data)
}
func (b *Buffer) ctor(call goja.ConstructorCall) (res *goja.Object) {
arg := call.Argument(0)
switch arg.ExportType() {
case reflectTypeInt, reflectTypeFloat:
panic(b.r.NewTypeError("Calling the Buffer constructor with numeric argument is not implemented yet"))
// TODO implement
}
return b._from(call.Arguments...)
}
type StringCodec interface {
DecodeAppend(string, []byte) []byte
Encode([]byte) string
Decode(s string) []byte
}
type hexCodec struct{}
func (hexCodec) DecodeAppend(s string, b []byte) []byte {
l := hex.DecodedLen(len(s))
dst, res := expandSlice(b, l)
n, err := hex.Decode(dst, []byte(s))
if err != nil {
res = res[:len(b)+n]
}
return res
}
func (hexCodec) Decode(s string) []byte {
n, _ := hex.DecodeString(s)
return n
}
func (hexCodec) Encode(b []byte) string {
return hex.EncodeToString(b)
}
type _utf8Codec struct{}
func (c _utf8Codec) DecodeAppend(s string, b []byte) []byte {
r := c.Decode(s)
dst, res := expandSlice(b, len(r))
copy(dst, r)
return res
}
func (_utf8Codec) Decode(s string) []byte {
r, _ := unicode.UTF8.NewEncoder().String(s)
return []byte(r)
}
func (_utf8Codec) Encode(b []byte) string {
r, _ := unicode.UTF8.NewDecoder().Bytes(b)
return string(r)
}
type base64Codec struct{}
type base64UrlCodec struct {
base64Codec
}
func (base64Codec) DecodeAppend(s string, b []byte) []byte {
res, _ := Base64DecodeAppend(b, s)
return res
}
func (base64Codec) Decode(s string) []byte {
res, _ := base64.StdEncoding.DecodeString(s)
return res
}
func (base64Codec) Encode(b []byte) string {
return base64.StdEncoding.EncodeToString(b)
}
func (base64UrlCodec) Encode(b []byte) string {
return base64.RawURLEncoding.EncodeToString(b)
}
var utf8Codec StringCodec = _utf8Codec{}
var stringCodecs = map[string]StringCodec{
"hex": hexCodec{},
"utf8": utf8Codec,
"utf-8": utf8Codec,
"base64": base64Codec{},
"base64Url": base64UrlCodec{},
}
func expandSlice(b []byte, l int) (dst, res []byte) {
if cap(b)-len(b) < l {
b1 := make([]byte, len(b)+l)
copy(b1, b)
dst = b1[len(b):]
res = b1
} else {
dst = b[len(b) : len(b)+l]
res = b[:len(b)+l]
}
return
}
func Base64DecodeAppend(dst []byte, src string) ([]byte, error) {
l := base64.RawStdEncoding.DecodedLen(len(src))
d, res := expandSlice(dst, l)
n, err := base64dec.DecodeBase64(d, src)
res = res[:len(dst)+n]
return res, err
}
func (b *Buffer) fromString(str, enc string) *goja.Object {
codec := stringCodecs[enc]
if codec == nil {
codec = utf8Codec
}
return b.fromBytes(codec.DecodeAppend(str, nil))
}
func (b *Buffer) fromBytes(data []byte) *goja.Object {
o, err := b.uint8ArrayCtor(b.bufferCtorObj, b.r.ToValue(b.r.NewArrayBuffer(data)))
if err != nil {
panic(err)
}
return o
}
func (b *Buffer) _from(args ...goja.Value) *goja.Object {
if len(args) == 0 {
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined"))
}
arg := args[0]
switch arg.ExportType() {
case reflectTypeArrayBuffer:
v, err := b.uint8ArrayCtor(b.bufferCtorObj, args...)
if err != nil {
panic(err)
}
return v
case reflectTypeString:
var enc string
if len(args) > 1 {
enc = args[1].String()
}
return b.fromString(arg.String(), enc)
default:
if o, ok := arg.(*goja.Object); ok {
if o.ExportType() == reflectTypeBytes {
bb, _ := o.Export().([]byte)
a := make([]byte, len(bb))
copy(a, bb)
return b.fromBytes(a)
} else {
if f, ok := goja.AssertFunction(o.Get("valueOf")); ok {
valueOf, err := f(o)
if err != nil {
panic(err)
}
if valueOf != o {
args[0] = valueOf
return b._from(args...)
}
}
if s := o.GetSymbol(goja.SymToPrimitive); s != nil {
if f, ok := goja.AssertFunction(s); ok {
str, err := f(o, b.r.ToValue("string"))
if err != nil {
panic(err)
}
args[0] = str
return b._from(args...)
}
}
}
// array-like
if v := o.Get("length"); v != nil {
length := int(v.ToInteger())
a := make([]byte, length)
for i := 0; i < length; i++ {
item := o.Get(strconv.Itoa(i))
if item != nil {
a[i] = byte(item.ToInteger())
}
}
return b.fromBytes(a)
}
}
}
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received %s", arg))
}
func (b *Buffer) from(call goja.FunctionCall) goja.Value {
return b._from(call.Arguments...)
}
func StringCodecByName(name string) StringCodec {
return stringCodecs[name]
}
func (b *Buffer) getStringCodec(enc goja.Value) (codec StringCodec) {
if !goja.IsUndefined(enc) {
codec = stringCodecs[enc.String()]
if codec == nil {
panic(errors.NewTypeError(b.r, "ERR_UNKNOWN_ENCODING", "Unknown encoding: %s", enc))
}
} else {
codec = utf8Codec
}
return
}
func (b *Buffer) fill(buf []byte, fill string, enc goja.Value) []byte {
codec := b.getStringCodec(enc)
b1 := codec.DecodeAppend(fill, buf[:0])
if len(b1) > len(buf) {
return b1[:len(buf)]
}
for i := len(b1); i < len(buf); {
i += copy(buf[i:], buf[:i])
}
return buf
}
func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
arg0 := call.Argument(0)
size := -1
if goja.IsNumber(arg0) {
size = int(arg0.ToInteger())
}
if size < 0 {
panic(errors.NewArgumentNotNumberTypeError(b.r, "size"))
}
fill := call.Argument(1)
buf := make([]byte, size)
if !goja.IsUndefined(fill) {
if goja.IsString(fill) {
var enc goja.Value
if a := call.Argument(2); goja.IsString(a) {
enc = a
} else {
enc = goja.Undefined()
}
buf = b.fill(buf, fill.String(), enc)
} else {
fill = fill.ToNumber()
if !goja.IsNaN(fill) && !goja.IsInfinity(fill) {
fillByte := byte(fill.ToInteger())
if fillByte != 0 {
for i := range buf {
buf[i] = fillByte
}
}
}
}
}
return b.fromBytes(buf)
}
func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
codec := b.getStringCodec(call.Argument(0))
start := goutil.CoercedIntegerArgument(call, 1, 0, 0)
// Node's Buffer class makes this zero if it is negative
if start < 0 {
start = 0
} else if start >= int64(len(bb)) {
// returns an empty string if start is beyond the length of the buffer
return b.r.ToValue("")
}
// NOTE that Node will default to the length of the buffer, but uses 0 for type mismatch defaults
end := goutil.CoercedIntegerArgument(call, 2, int64(len(bb)), 0)
if end < 0 || start >= end {
// returns an empty string if end < 0 or start >= end
return b.r.ToValue("")
} else if end > int64(len(bb)) {
// and Node ensures you don't go past the Buffer
end = int64(len(bb))
}
return b.r.ToValue(codec.Encode(bb[start:end]))
}
func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
other := call.Argument(0)
if b.r.InstanceOf(other, b.uint8ArrayCtorObj) {
otherBytes := Bytes(b.r, other)
return b.r.ToValue(bytes.Equal(bb, otherBytes))
}
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array."))
}
// readBigInt64BE reads a big-endian 64-bit signed integer from the buffer
func (b *Buffer) readBigInt64BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := int64(binary.BigEndian.Uint64(bb[offset : offset+8]))
return b.r.ToValue(big.NewInt(value))
}
// readBigInt64LE reads a little-endian 64-bit signed integer from the buffer
func (b *Buffer) readBigInt64LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := int64(binary.LittleEndian.Uint64(bb[offset : offset+8]))
return b.r.ToValue(big.NewInt(value))
}
// readBigUInt64BE reads a big-endian 64-bit unsigned integer from the buffer
func (b *Buffer) readBigUInt64BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := binary.BigEndian.Uint64(bb[offset : offset+8])
return b.r.ToValue(new(big.Int).SetUint64(value))
}
// readBigUInt64LE reads a little-endian 64-bit unsigned integer from the buffer
func (b *Buffer) readBigUInt64LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := binary.LittleEndian.Uint64(bb[offset : offset+8])
return b.r.ToValue(new(big.Int).SetUint64(value))
}
// readDoubleBE reads a big-endian 64-bit floating-point number from the buffer
func (b *Buffer) readDoubleBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := binary.BigEndian.Uint64(bb[offset : offset+8])
return b.r.ToValue(math.Float64frombits(value))
}
// readDoubleLE reads a little-endian 64-bit floating-point number from the buffer
func (b *Buffer) readDoubleLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 8)
value := binary.LittleEndian.Uint64(bb[offset : offset+8])
return b.r.ToValue(math.Float64frombits(value))
}
// readFloatBE reads a big-endian 32-bit floating-point number from the buffer
func (b *Buffer) readFloatBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := binary.BigEndian.Uint32(bb[offset : offset+4])
return b.r.ToValue(math.Float32frombits(value))
}
// readFloatLE reads a little-endian 32-bit floating-point number from the buffer
func (b *Buffer) readFloatLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := binary.LittleEndian.Uint32(bb[offset : offset+4])
return b.r.ToValue(math.Float32frombits(value))
}
// readInt8 reads an 8-bit signed integer from the buffer
func (b *Buffer) readInt8(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 1)
value := int8(bb[offset])
return b.r.ToValue(value)
}
// readInt16BE reads a big-endian 16-bit signed integer from the buffer
func (b *Buffer) readInt16BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 2)
value := int16(binary.BigEndian.Uint16(bb[offset : offset+2]))
return b.r.ToValue(value)
}
// readInt16LE reads a little-endian 16-bit signed integer from the buffer
func (b *Buffer) readInt16LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 2)
value := int16(binary.LittleEndian.Uint16(bb[offset : offset+2]))
return b.r.ToValue(value)
}
// readInt32BE reads a big-endian 32-bit signed integer from the buffer
func (b *Buffer) readInt32BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := int32(binary.BigEndian.Uint32(bb[offset : offset+4]))
return b.r.ToValue(value)
}
// readInt32LE reads a little-endian 32-bit signed integer from the buffer
func (b *Buffer) readInt32LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := int32(binary.LittleEndian.Uint32(bb[offset : offset+4]))
return b.r.ToValue(value)
}
// readIntBE reads a big-endian signed integer of variable byte length
func (b *Buffer) readIntBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
var value int64
for i := int64(0); i < byteLength; i++ {
value = (value << 8) | int64(bb[offset+i])
}
value = signExtend(value, byteLength)
return b.r.ToValue(value)
}
// readIntLE reads a little-endian signed integer of variable byte length
func (b *Buffer) readIntLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
var value int64
for i := byteLength - 1; i >= 0; i-- {
value = (value << 8) | int64(bb[offset+i])
}
value = signExtend(value, byteLength)
return b.r.ToValue(value)
}
// readUInt8 reads an 8-bit unsigned integer from the buffer
func (b *Buffer) readUInt8(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 1)
value := bb[offset]
return b.r.ToValue(value)
}
// readUInt16BE reads a big-endian 16-bit unsigned integer from the buffer
func (b *Buffer) readUInt16BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 2)
value := binary.BigEndian.Uint16(bb[offset : offset+2])
return b.r.ToValue(value)
}
// readUInt16LE reads a little-endian 16-bit unsigned integer from the buffer
func (b *Buffer) readUInt16LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 2)
value := binary.LittleEndian.Uint16(bb[offset : offset+2])
return b.r.ToValue(value)
}
// readUInt32BE reads a big-endian 32-bit unsigned integer from the buffer
func (b *Buffer) readUInt32BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := binary.BigEndian.Uint32(bb[offset : offset+4])
return b.r.ToValue(value)
}
// readUInt32LE reads a little-endian 32-bit unsigned integer from the buffer
func (b *Buffer) readUInt32LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset := b.getOffsetArgument(call, 0, bb, 4)
value := binary.LittleEndian.Uint32(bb[offset : offset+4])
return b.r.ToValue(value)
}
// readUIntBE reads a big-endian unsigned integer of variable byte length
func (b *Buffer) readUIntBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
var value uint64
for i := int64(0); i < byteLength; i++ {
value = (value << 8) | uint64(bb[offset+i])
}
return b.r.ToValue(value)
}
// readUIntLE reads a little-endian unsigned integer of variable byte length
func (b *Buffer) readUIntLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
var value uint64
for i := byteLength - 1; i >= 0; i-- {
value = (value << 8) | uint64(bb[offset+i])
}
return b.r.ToValue(value)
}
// write will write a string to the Buffer at offset according to the character encoding. The length parameter is
// the number of bytes to write. If buffer did not contain enough space to fit the entire string, only part of string
// will be written.
func (b *Buffer) write(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
str := goutil.RequiredStringArgument(b.r, call, "string", 0)
// note that we are passing in zero for numBytes, since the length parameter, which depends on offset,
// will dictate the number of bytes
offset := b.getOffsetArgument(call, 1, bb, 0)
// the length defaults to the size of the buffer - offset
maxLength := int64(len(bb)) - offset
length := goutil.OptionalIntegerArgument(b.r, call, "length", 2, maxLength)
codec := b.getStringCodec(call.Argument(3))
raw := codec.Decode(str)
if int64(len(raw)) < length {
// make sure we only write up to raw bytes
length = int64(len(raw))
}
n := copy(bb[offset:], raw[:length])
return b.r.ToValue(n)
}
// writeBigInt64BE writes a big-endian 64-bit signed integer to the buffer
func (b *Buffer) writeBigInt64BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
intValue := value.Int64()
binary.BigEndian.PutUint64(bb[offset:offset+8], uint64(intValue))
return b.r.ToValue(offset + 8)
}
// writeBigInt64LE writes a little-endian 64-bit signed integer to the buffer
func (b *Buffer) writeBigInt64LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
intValue := value.Int64()
binary.LittleEndian.PutUint64(bb[offset:offset+8], uint64(intValue))
return b.r.ToValue(offset + 8)
}
// writeBigUInt64BE writes a big-endian 64-bit unsigned integer to the buffer
func (b *Buffer) writeBigUInt64BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
uintValue := value.Uint64()
binary.BigEndian.PutUint64(bb[offset:offset+8], uintValue)
return b.r.ToValue(offset + 8)
}
// writeBigUInt64LE writes a little-endian 64-bit unsigned integer to the buffer
func (b *Buffer) writeBigUInt64LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
uintValue := value.Uint64()
binary.LittleEndian.PutUint64(bb[offset:offset+8], uintValue)
return b.r.ToValue(offset + 8)
}
// writeDoubleBE writes a big-endian 64-bit double to the buffer
func (b *Buffer) writeDoubleBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
bits := math.Float64bits(value)
binary.BigEndian.PutUint64(bb[offset:offset+8], bits)
return b.r.ToValue(offset + 8)
}
// writeDoubleLE writes a little-endian 64-bit double to the buffer
func (b *Buffer) writeDoubleLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 8)
bits := math.Float64bits(value)
binary.LittleEndian.PutUint64(bb[offset:offset+8], bits)
return b.r.ToValue(offset + 8)
}
// writeFloatBE writes a big-endian 32-bit float to the buffer
func (b *Buffer) writeFloatBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinFloat32Range(value)
bits := math.Float32bits(float32(value))
binary.BigEndian.PutUint32(bb[offset:offset+4], bits)
return b.r.ToValue(offset + 4)
}
// writeFloatLE writes a little-endian 32-bit floating-point number to the buffer
func (b *Buffer) writeFloatLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinFloat32Range(value)
bits := math.Float32bits(float32(value))
binary.LittleEndian.PutUint32(bb[offset:offset+4], bits)
return b.r.ToValue(offset + 4)
}
// writeInt8 writes an 8-bit signed integer to the buffer
func (b *Buffer) writeInt8(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 1)
if value < math.MinInt8 || value > math.MaxInt8 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
bb[offset] = byte(int8(value))
return b.r.ToValue(offset + 1)
}
// writeInt16BE writes a big-endian 16-bit signed integer to the buffer
func (b *Buffer) writeInt16BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 2)
b.ensureWithinInt16Range(value)
binary.BigEndian.PutUint16(bb[offset:offset+2], uint16(value))
return b.r.ToValue(offset + 2)
}
// writeInt16LE writes a little-endian 16-bit signed integer to the buffer
func (b *Buffer) writeInt16LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 2)
b.ensureWithinInt16Range(value)
binary.LittleEndian.PutUint16(bb[offset:offset+2], uint16(value))
return b.r.ToValue(offset + 2)
}
// writeInt32BE writes a big-endian 32-bit signed integer to the buffer
func (b *Buffer) writeInt32BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinInt32Range(value)
binary.BigEndian.PutUint32(bb[offset:offset+4], uint32(value))
return b.r.ToValue(offset + 4)
}
// writeInt32LE writes a little-endian 32-bit signed integer to the buffer
func (b *Buffer) writeInt32LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinInt32Range(value)
binary.LittleEndian.PutUint32(bb[offset:offset+4], uint32(value))
return b.r.ToValue(offset + 4)
}
// writeIntBE writes a big-endian signed integer of variable byte length
func (b *Buffer) writeIntBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
b.ensureWithinIntRange(byteLength, value)
// Write bytes in big-endian order (most significant byte first)
for i := int64(0); i < byteLength; i++ {
shift := uint(8 * (byteLength - 1 - i))
bb[offset+i] = byte(value >> shift)
}
return b.r.ToValue(offset + byteLength)
}
// writeIntLE writes a little-endian signed integer of variable byte length
func (b *Buffer) writeIntLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
b.ensureWithinIntRange(byteLength, value)
// Write bytes in little-endian order
for i := int64(0); i < byteLength; i++ {
shift := uint(8 * i)
bb[offset+i] = byte(value >> shift)
}
return b.r.ToValue(offset + byteLength)
}
// writeUInt8 writes an 8-bit unsigned integer to the buffer
func (b *Buffer) writeUInt8(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 1)
if value < 0 || value > 255 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
bb[offset] = uint8(value)
return b.r.ToValue(offset + 1)
}
// writeUInt16BE writes a big-endian 16-bit unsigned integer to the buffer
func (b *Buffer) writeUInt16BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 2)
b.ensureWithinUInt16Range(value)
binary.BigEndian.PutUint16(bb[offset:offset+2], uint16(value))
return b.r.ToValue(offset + 2)
}
// writeUInt16LE writes a little-endian 16-bit unsigned integer to the buffer
func (b *Buffer) writeUInt16LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 2)
b.ensureWithinUInt16Range(value)
binary.LittleEndian.PutUint16(bb[offset:offset+2], uint16(value))
return b.r.ToValue(offset + 2)
}
// writeUInt32BE writes a big-endian 32-bit unsigned integer to the buffer
func (b *Buffer) writeUInt32BE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinUInt32Range(value)
binary.BigEndian.PutUint32(bb[offset:offset+4], uint32(value))
return b.r.ToValue(offset + 4)
}
// writeUInt32LE writes a little-endian 32-bit unsigned integer to the buffer
func (b *Buffer) writeUInt32LE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset := b.getOffsetArgument(call, 1, bb, 4)
b.ensureWithinUInt32Range(value)
binary.LittleEndian.PutUint32(bb[offset:offset+4], uint32(value))
return b.r.ToValue(offset + 4)
}
// writeUIntBE writes a big-endian unsigned integer of variable byte length
func (b *Buffer) writeUIntBE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
b.ensureWithinUIntRange(byteLength, value)
// Write the bytes in big-endian order (most significant byte first)
for i := int64(0); i < byteLength; i++ {
shift := (byteLength - 1 - i) * 8
bb[offset+i] = byte(value >> shift)
}
return b.r.ToValue(offset + byteLength)
}
// writeUIntLE writes a little-endian unsigned integer of variable byte length
func (b *Buffer) writeUIntLE(call goja.FunctionCall) goja.Value {
bb := Bytes(b.r, call.This)
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
b.ensureWithinUIntRange(byteLength, value)
// Write the bytes in little-endian order
for i := int64(0); i < byteLength; i++ {
shift := uint(8 * i)
bb[offset+i] = byte(value >> shift)
}
return b.r.ToValue(offset + byteLength)
}
func (b *Buffer) getOffsetArgument(call goja.FunctionCall, argIndex int, bb []byte, numBytes int64) int64 {
offset := goutil.OptionalIntegerArgument(b.r, call, "offset", argIndex, 0)
if offset < 0 || offset+numBytes > int64(len(bb)) {
panic(errors.NewArgumentOutOfRangeError(b.r, "offset", offset))
}
return offset
}
func (b *Buffer) getVariableLengthReadArguments(call goja.FunctionCall, bb []byte) (int64, int64) {
return b.getVariableLengthArguments(call, bb, 0, 1)
}
func (b *Buffer) getVariableLengthWriteArguments(call goja.FunctionCall, bb []byte) (int64, int64) {
return b.getVariableLengthArguments(call, bb, 1, 2)
}
func (b *Buffer) getVariableLengthArguments(call goja.FunctionCall, bb []byte, offsetArgIndex, byteLengthArgIndex int) (int64, int64) {
offset := goutil.RequiredIntegerArgument(b.r, call, "offset", offsetArgIndex)
byteLength := goutil.RequiredIntegerArgument(b.r, call, "byteLength", byteLengthArgIndex)
if byteLength < 1 || byteLength > 6 {
panic(errors.NewArgumentOutOfRangeError(b.r, "byteLength", byteLength))
}
if offset < 0 || offset+byteLength > int64(len(bb)) {
panic(errors.NewArgumentOutOfRangeError(b.r, "offset", offset))
}
return offset, byteLength
}
func (b *Buffer) ensureWithinFloat32Range(value float64) {
if value < -math.MaxFloat32 || value > math.MaxFloat32 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
func (b *Buffer) ensureWithinInt16Range(value int64) {
if value < math.MinInt16 || value > math.MaxInt16 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
func (b *Buffer) ensureWithinInt32Range(value int64) {
if value < math.MinInt32 || value > math.MaxInt32 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
// ensureWithinIntRange checks to make sure that value is within the integer range
// defined by the byteLength. Note that byteLength can be at most 6 bytes, so a
// 48 bit integer is the largest possible value.
func (b *Buffer) ensureWithinIntRange(byteLength, value int64) {
// Calculate the valid range for the given byte length
bits := byteLength * 8
minValue := -(int64(1) << (bits - 1))
maxValue := (int64(1) << (bits - 1)) - 1
if value < minValue || value > maxValue {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
func (b *Buffer) ensureWithinUInt16Range(value int64) {
if value < 0 || value > math.MaxUint16 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
func (b *Buffer) ensureWithinUInt32Range(value int64) {
if value < 0 || value > math.MaxUint32 {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
// ensureWithinUIntRange checks to make sure that value is within the unsigned integer
// range defined by the byteLength. Note that byteLength can be at most 6 bytes, so a
// 48 bit unsigned integer is the largest possible value.
func (b *Buffer) ensureWithinUIntRange(byteLength, value int64) {
// Validate that the value is within the valid range for the given byteLength
maxValue := (int64(1) << (8 * byteLength)) - 1
if value < 0 || value > maxValue {
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
}
}
func signExtend(value int64, numBytes int64) int64 {
// we don't have to turn this to a uint64 first because numBytes < 8 so
// the sign bit will never pushed out of the int64 range
return (value << (64 - 8*numBytes)) >> (64 - 8*numBytes)
}
func Require(runtime *goja.Runtime, module *goja.Object) {
b := &Buffer{r: runtime}
uint8Array := runtime.Get("Uint8Array")
if c, ok := goja.AssertConstructor(uint8Array); ok {
b.uint8ArrayCtor = c
} else {
panic(runtime.NewTypeError("Uint8Array is not a constructor"))
}
uint8ArrayObj := uint8Array.ToObject(runtime)
ctor := runtime.ToValue(b.ctor).ToObject(runtime)
ctor.SetPrototype(uint8ArrayObj)
ctor.DefineDataPropertySymbol(symApi, runtime.ToValue(b), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
b.bufferCtorObj = ctor
b.uint8ArrayCtorObj = uint8ArrayObj
proto := runtime.NewObject()
proto.SetPrototype(uint8ArrayObj.Get("prototype").ToObject(runtime))
proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
proto.Set("equals", b.proto_equals)
proto.Set("toString", b.proto_toString)
proto.Set("readBigInt64BE", b.readBigInt64BE)
proto.Set("readBigInt64LE", b.readBigInt64LE)
proto.Set("readBigUInt64BE", b.readBigUInt64BE)
// aliases for readBigUInt64BE
proto.Set("readBigUint64BE", b.readBigUInt64BE)
proto.Set("readBigUInt64LE", b.readBigUInt64LE)
// aliases for readBigUInt64LE
proto.Set("readBigUint64LE", b.readBigUInt64LE)
proto.Set("readDoubleBE", b.readDoubleBE)
proto.Set("readDoubleLE", b.readDoubleLE)
proto.Set("readFloatBE", b.readFloatBE)
proto.Set("readFloatLE", b.readFloatLE)
proto.Set("readInt8", b.readInt8)
proto.Set("readInt16BE", b.readInt16BE)
proto.Set("readInt16LE", b.readInt16LE)
proto.Set("readInt32BE", b.readInt32BE)
proto.Set("readInt32LE", b.readInt32LE)
proto.Set("readIntBE", b.readIntBE)
proto.Set("readIntLE", b.readIntLE)
proto.Set("readUInt8", b.readUInt8)
// aliases for readUInt8
proto.Set("readUint8", b.readUInt8)
proto.Set("readUInt16BE", b.readUInt16BE)
// aliases for readUInt16BE
proto.Set("readUint16BE", b.readUInt16BE)
proto.Set("readUInt16LE", b.readUInt16LE)
// aliases for readUInt16LE
proto.Set("readUint16LE", b.readUInt16LE)
proto.Set("readUInt32BE", b.readUInt32BE)
// aliases for readUInt32BE
proto.Set("readUint32BE", b.readUInt32BE)
proto.Set("readUInt32LE", b.readUInt32LE)
// aliases for readUInt32LE
proto.Set("readUint32LE", b.readUInt32LE)
proto.Set("readUIntBE", b.readUIntBE)
// aliases for readUIntBE
proto.Set("readUintBE", b.readUIntBE)
proto.Set("readUIntLE", b.readUIntLE)
// aliases for readUIntLE
proto.Set("readUintLE", b.readUIntLE)
proto.Set("write", b.write)
proto.Set("writeBigInt64BE", b.writeBigInt64BE)
proto.Set("writeBigInt64LE", b.writeBigInt64LE)
proto.Set("writeBigUInt64BE", b.writeBigUInt64BE)
// aliases for writeBigUInt64BE
proto.Set("writeBigUint64BE", b.writeBigUInt64BE)
proto.Set("writeBigUInt64LE", b.writeBigUInt64LE)
// aliases for writeBigUInt64LE
proto.Set("writeBigUint64LE", b.writeBigUInt64LE)
proto.Set("writeDoubleBE", b.writeDoubleBE)
proto.Set("writeDoubleLE", b.writeDoubleLE)
proto.Set("writeFloatBE", b.writeFloatBE)
proto.Set("writeFloatLE", b.writeFloatLE)
proto.Set("writeInt8", b.writeInt8)
proto.Set("writeInt16BE", b.writeInt16BE)
proto.Set("writeInt16LE", b.writeInt16LE)
proto.Set("writeInt32BE", b.writeInt32BE)
proto.Set("writeInt32LE", b.writeInt32LE)
proto.Set("writeIntBE", b.writeIntBE)
proto.Set("writeIntLE", b.writeIntLE)
proto.Set("writeUInt8", b.writeUInt8)
// aliases for writeUInt8
proto.Set("writeUint8", b.writeUInt8)
proto.Set("writeUInt16BE", b.writeUInt16BE)
// aliases for writeUInt16BE
proto.Set("writeUint16BE", b.writeUInt16BE)
proto.Set("writeUInt16LE", b.writeUInt16LE)
// aliases for writeUInt16LE
proto.Set("writeUint16LE", b.writeUInt16LE)
proto.Set("writeUInt32BE", b.writeUInt32BE)
// aliases for writeUInt32BE
proto.Set("writeUint32BE", b.writeUInt32BE)
proto.Set("writeUInt32LE", b.writeUInt32LE)
// aliases for writeUInt32LE
proto.Set("writeUint32LE", b.writeUInt32LE)
proto.Set("writeUIntBE", b.writeUIntBE)
// aliases for writeUIntBE
proto.Set("writeUintBE", b.writeUIntBE)
proto.Set("writeUIntLE", b.writeUIntLE)
// aliases for writeUIntLE
proto.Set("writeUintLE", b.writeUIntLE)
ctor.Set("prototype", proto)
ctor.Set("poolSize", 8192)
ctor.Set("from", b.from)
ctor.Set("alloc", b.alloc)
exports := module.Get("exports").(*goja.Object)
exports.Set("Buffer", ctor)
}
func init() {
require.RegisterCoreModule(ModuleName, Require)
}