1package stdlib
2
3import (
4	"fmt"
5	"reflect"
6
7	"github.com/zclconf/go-cty/cty"
8	"github.com/zclconf/go-cty/cty/function"
9	"github.com/zclconf/go-cty/cty/gocty"
10)
11
12// Bytes is a capsule type that can be used with the binary functions to
13// support applications that need to support raw buffers in addition to
14// UTF-8 strings.
15var Bytes = cty.Capsule("bytes", reflect.TypeOf([]byte(nil)))
16
17// BytesVal creates a new Bytes value from the given buffer, which must be
18// non-nil or this function will panic.
19//
20// Once a byte slice has been wrapped in a Bytes capsule, its underlying array
21// must be considered immutable.
22func BytesVal(buf []byte) cty.Value {
23	if buf == nil {
24		panic("can't make Bytes value from nil slice")
25	}
26
27	return cty.CapsuleVal(Bytes, &buf)
28}
29
30// BytesLen is a Function that returns the length of the buffer encapsulated
31// in a Bytes value.
32var BytesLenFunc = function.New(&function.Spec{
33	Params: []function.Parameter{
34		{
35			Name:             "buf",
36			Type:             Bytes,
37			AllowDynamicType: true,
38		},
39	},
40	Type: function.StaticReturnType(cty.Number),
41	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
42		bufPtr := args[0].EncapsulatedValue().(*[]byte)
43		return cty.NumberIntVal(int64(len(*bufPtr))), nil
44	},
45})
46
47// BytesSlice is a Function that returns a slice of the given Bytes value.
48var BytesSliceFunc = function.New(&function.Spec{
49	Params: []function.Parameter{
50		{
51			Name:             "buf",
52			Type:             Bytes,
53			AllowDynamicType: true,
54		},
55		{
56			Name:             "offset",
57			Type:             cty.Number,
58			AllowDynamicType: true,
59		},
60		{
61			Name:             "length",
62			Type:             cty.Number,
63			AllowDynamicType: true,
64		},
65	},
66	Type: function.StaticReturnType(Bytes),
67	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
68		bufPtr := args[0].EncapsulatedValue().(*[]byte)
69
70		var offset, length int
71
72		var err error
73		err = gocty.FromCtyValue(args[1], &offset)
74		if err != nil {
75			return cty.NilVal, err
76		}
77		err = gocty.FromCtyValue(args[2], &length)
78		if err != nil {
79			return cty.NilVal, err
80		}
81
82		if offset < 0 || length < 0 {
83			return cty.NilVal, fmt.Errorf("offset and length must be non-negative")
84		}
85
86		if offset > len(*bufPtr) {
87			return cty.NilVal, fmt.Errorf(
88				"offset %d is greater than total buffer length %d",
89				offset, len(*bufPtr),
90			)
91		}
92
93		end := offset + length
94
95		if end > len(*bufPtr) {
96			return cty.NilVal, fmt.Errorf(
97				"offset %d + length %d is greater than total buffer length %d",
98				offset, length, len(*bufPtr),
99			)
100		}
101
102		return BytesVal((*bufPtr)[offset:end]), nil
103	},
104})
105
106func BytesLen(buf cty.Value) (cty.Value, error) {
107	return BytesLenFunc.Call([]cty.Value{buf})
108}
109
110func BytesSlice(buf cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) {
111	return BytesSliceFunc.Call([]cty.Value{buf, offset, length})
112}
113