Skip to content

Commit ba90ff8

Browse files
stampy88dave sinclair
and
dave sinclair
authored
Added variable length Buffer.read* methods (#93)
* Added variable length `Buffer.read*` methods - added `readIntBE`, `readIntLE`, `readUIntBE`, and `readUIntLE`. - also refactored out some common methods to create errors * added errors.NewRangeError. Also fixed mispelled `ErrCodedOutOfRange` -> `ErrCodeOutOfRange` (extra `d`) * added the `readUint*` (small `i`) aliases --------- Co-authored-by: dave sinclair <david_sinclair@cable.comcast.com>
1 parent 2ae4cd2 commit ba90ff8

File tree

3 files changed

+479
-13
lines changed

3 files changed

+479
-13
lines changed

buffer/buffer.go

+128-2
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,36 @@ func (b *Buffer) readInt32LE(call goja.FunctionCall) goja.Value {
524524
return b.r.ToValue(value)
525525
}
526526

527+
// readIntBE reads a big-endian signed integer of variable byte length
528+
func (b *Buffer) readIntBE(call goja.FunctionCall) goja.Value {
529+
bb := Bytes(b.r, call.This)
530+
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
531+
532+
var value int64
533+
for i := int64(0); i < byteLength; i++ {
534+
value = (value << 8) | int64(bb[offset+i])
535+
}
536+
537+
value = signExtend(value, byteLength)
538+
539+
return b.r.ToValue(value)
540+
}
541+
542+
// readIntLE reads a little-endian signed integer of variable byte length
543+
func (b *Buffer) readIntLE(call goja.FunctionCall) goja.Value {
544+
bb := Bytes(b.r, call.This)
545+
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
546+
547+
var value int64
548+
for i := byteLength - 1; i >= 0; i-- {
549+
value = (value << 8) | int64(bb[offset+i])
550+
}
551+
552+
value = signExtend(value, byteLength)
553+
554+
return b.r.ToValue(value)
555+
}
556+
527557
// readUInt8 reads an 8-bit unsigned integer from the buffer
528558
func (b *Buffer) readUInt8(call goja.FunctionCall) goja.Value {
529559
bb := Bytes(b.r, call.This)
@@ -569,6 +599,32 @@ func (b *Buffer) readUInt32LE(call goja.FunctionCall) goja.Value {
569599
return b.r.ToValue(value)
570600
}
571601

602+
// readUIntBE reads a big-endian unsigned integer of variable byte length
603+
func (b *Buffer) readUIntBE(call goja.FunctionCall) goja.Value {
604+
bb := Bytes(b.r, call.This)
605+
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
606+
607+
var value uint64
608+
for i := int64(0); i < byteLength; i++ {
609+
value = (value << 8) | uint64(bb[offset+i])
610+
}
611+
612+
return b.r.ToValue(value)
613+
}
614+
615+
// readUIntLE reads a little-endian unsigned integer of variable byte length
616+
func (b *Buffer) readUIntLE(call goja.FunctionCall) goja.Value {
617+
bb := Bytes(b.r, call.This)
618+
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
619+
620+
var value uint64
621+
for i := byteLength - 1; i >= 0; i-- {
622+
value = (value << 8) | uint64(bb[offset+i])
623+
}
624+
625+
return b.r.ToValue(value)
626+
}
627+
572628
func (b *Buffer) getOffsetArgument(call goja.FunctionCall, argIndex int, bb []byte, numBytes int64) int64 {
573629
arg := call.Argument(argIndex)
574630
var offset int64
@@ -578,16 +634,56 @@ func (b *Buffer) getOffsetArgument(call goja.FunctionCall, argIndex int, bb []by
578634
// optional arg that defaults to zero
579635
offset = 0
580636
} else {
581-
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"offset\" argument must be of type number."))
637+
panic(b.newArgumentNotNumberTypeError("offset"))
582638
}
583639

584640
if offset < 0 || offset+numBytes > int64(len(bb)) {
585-
panic(errors.NewError(b.r, nil, errors.ErrCodedOutOfRange, "The value of \"offset\" %d is out of range", offset))
641+
panic(b.newArgumentOutOfRangeError("offset", offset))
586642
}
587643

588644
return offset
589645
}
590646

647+
func (b *Buffer) getVariableLengthReadArguments(call goja.FunctionCall, bb []byte) (int64, int64) {
648+
offset := b.getRequiredIntegerArgument(call, "offset", 0)
649+
byteLength := b.getRequiredIntegerArgument(call, "byteLength", 1)
650+
651+
if byteLength < 1 || byteLength > 6 {
652+
panic(b.newArgumentOutOfRangeError("byteLength", byteLength))
653+
}
654+
if offset < 0 || offset+byteLength > int64(len(bb)) {
655+
panic(b.newArgumentOutOfRangeError("offset", offset))
656+
}
657+
658+
return offset, byteLength
659+
}
660+
661+
func signExtend(value int64, numBytes int64) int64 {
662+
// we don't have to turn this to a uint64 first because numBytes < 8 so
663+
// the sign bit will never pushed out of the int64 range
664+
return (value << (64 - 8*numBytes)) >> (64 - 8*numBytes)
665+
}
666+
667+
func (b *Buffer) getRequiredIntegerArgument(call goja.FunctionCall, name string, argIndex int) int64 {
668+
arg := call.Argument(argIndex)
669+
if isNumber(arg) {
670+
return arg.ToInteger()
671+
}
672+
if goja.IsUndefined(arg) {
673+
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"%s\" argument is required.", name))
674+
}
675+
676+
panic(b.newArgumentNotNumberTypeError(name))
677+
}
678+
679+
func (b *Buffer) newArgumentNotNumberTypeError(name string) *goja.Object {
680+
return errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"%s\" argument must be of type number.", name)
681+
}
682+
683+
func (b *Buffer) newArgumentOutOfRangeError(name string, v int64) *goja.Object {
684+
return errors.NewRangeError(b.r, errors.ErrCodeOutOfRange, "The value of \"%s\" %d is out of range", name, v)
685+
}
686+
591687
func Require(runtime *goja.Runtime, module *goja.Object) {
592688
b := &Buffer{r: runtime}
593689
uint8Array := runtime.Get("Uint8Array")
@@ -612,7 +708,13 @@ func Require(runtime *goja.Runtime, module *goja.Object) {
612708
proto.Set("readBigInt64BE", b.readBigInt64BE)
613709
proto.Set("readBigInt64LE", b.readBigInt64LE)
614710
proto.Set("readBigUInt64BE", b.readBigUInt64BE)
711+
// aliases for readBigUInt64BE
712+
proto.Set("readBigUint64BE", b.readBigUInt64BE)
713+
615714
proto.Set("readBigUInt64LE", b.readBigUInt64LE)
715+
// aliases for readBigUInt64LE
716+
proto.Set("readBigUint64LE", b.readBigUInt64LE)
717+
616718
proto.Set("readDoubleBE", b.readDoubleBE)
617719
proto.Set("readDoubleLE", b.readDoubleLE)
618720
proto.Set("readFloatBE", b.readFloatBE)
@@ -622,11 +724,35 @@ func Require(runtime *goja.Runtime, module *goja.Object) {
622724
proto.Set("readInt16LE", b.readInt16LE)
623725
proto.Set("readInt32BE", b.readInt32BE)
624726
proto.Set("readInt32LE", b.readInt32LE)
727+
proto.Set("readIntBE", b.readIntBE)
728+
proto.Set("readIntLE", b.readIntLE)
625729
proto.Set("readUInt8", b.readUInt8)
730+
// aliases for readUInt8
731+
proto.Set("readUint8", b.readUInt8)
732+
626733
proto.Set("readUInt16BE", b.readUInt16BE)
734+
// aliases for readUInt16BE
735+
proto.Set("readUint16BE", b.readUInt16BE)
736+
627737
proto.Set("readUInt16LE", b.readUInt16LE)
738+
// aliases for readUInt16LE
739+
proto.Set("readUint16LE", b.readUInt16LE)
740+
628741
proto.Set("readUInt32BE", b.readUInt32BE)
742+
// aliases for readUInt32BE
743+
proto.Set("readUint32BE", b.readUInt32BE)
744+
629745
proto.Set("readUInt32LE", b.readUInt32LE)
746+
// aliases for readUInt32LE
747+
proto.Set("readUint32LE", b.readUInt32LE)
748+
749+
proto.Set("readUIntBE", b.readUIntBE)
750+
// aliases for readUIntBE
751+
proto.Set("readUintBE", b.readUIntBE)
752+
753+
proto.Set("readUIntLE", b.readUIntLE)
754+
// aliases for readUIntLE
755+
proto.Set("readUintLE", b.readUIntLE)
630756

631757
ctor.Set("prototype", proto)
632758
ctor.Set("poolSize", 8192)

0 commit comments

Comments
 (0)