Skip to content

Commit 59a5446

Browse files
committed
feat: custom metadata support
Add OptMetadata, which allows supplying custom metadata when calling NewDescriptorInput. Add GetMetadata, which allows a user to parse custom metadata from a Descriptor.
1 parent a29fb45 commit 59a5446

File tree

5 files changed

+125
-24
lines changed

5 files changed

+125
-24
lines changed

pkg/sif/descriptor.go

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
1+
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
22
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
33
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
44
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -10,6 +10,7 @@ package sif
1010
import (
1111
"bytes"
1212
"crypto"
13+
"encoding"
1314
"encoding/binary"
1415
"errors"
1516
"fmt"
@@ -78,28 +79,49 @@ func (d *rawDescriptor) setName(name string) error {
7879

7980
var errExtraTooLarge = errors.New("extra value too large")
8081

81-
// setExtra encodes v into the extra field of d.
82-
func (d *rawDescriptor) setExtra(v interface{}) error {
82+
// setExtra encodes v into the extra field of d. If the encoding.BinaryMarshaler interface is
83+
// implemented by v, it is used for marshaling. Otherwise, binary.Write() is used.
84+
func (d *rawDescriptor) setExtra(v any) error {
8385
if v == nil {
8486
return nil
8587
}
8688

87-
if binary.Size(v) > len(d.Extra) {
88-
return errExtraTooLarge
89+
var extra []byte
90+
91+
if m, ok := v.(encoding.BinaryMarshaler); ok {
92+
b, err := m.MarshalBinary()
93+
if err != nil {
94+
return err
95+
}
96+
extra = b
97+
} else {
98+
b := new(bytes.Buffer)
99+
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
100+
return err
101+
}
102+
extra = b.Bytes()
89103
}
90104

91-
b := new(bytes.Buffer)
92-
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
93-
return err
105+
if len(extra) > len(d.Extra) {
106+
return errExtraTooLarge
94107
}
95108

96-
for i := copy(d.Extra[:], b.Bytes()); i < len(d.Extra); i++ {
109+
for i := copy(d.Extra[:], extra); i < len(d.Extra); i++ {
97110
d.Extra[i] = 0
98111
}
99112

100113
return nil
101114
}
102115

116+
// getExtra decodes the extra fields of d into v. If the encoding.BinaryUnmarshaler interface is
117+
// implemented by v, it is used for unmarshaling. Otherwise, binary.Read() is used.
118+
func (d *rawDescriptor) getExtra(v any) error {
119+
if u, ok := v.(encoding.BinaryUnmarshaler); ok {
120+
return u.UnmarshalBinary(d.Extra[:])
121+
}
122+
return binary.Read(bytes.NewReader(d.Extra[:]), binary.LittleEndian, v)
123+
}
124+
103125
// getPartitionMetadata gets metadata for a partition data object.
104126
func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error) {
105127
if got, want := d.DataType, DataPartition; got != want {
@@ -108,9 +130,8 @@ func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error)
108130

109131
var p partition
110132

111-
b := bytes.NewReader(d.Extra[:])
112-
if err := binary.Read(b, binary.LittleEndian, &p); err != nil {
113-
return 0, 0, "", fmt.Errorf("%w", err)
133+
if err := d.getExtra(&p); err != nil {
134+
return 0, 0, "", err
114135
}
115136

116137
return p.Fstype, p.Parttype, p.Arch.GoArch(), nil
@@ -168,11 +189,24 @@ func (d Descriptor) ModifiedAt() time.Time { return time.Unix(d.raw.ModifiedAt,
168189
// Name returns the name of the data object.
169190
func (d Descriptor) Name() string { return strings.TrimRight(string(d.raw.Name[:]), "\000") }
170191

192+
// GetMetadata reads metadata from d into v. If the encoding.BinaryUnmarshaler interface is
193+
// implemented by v, it is used for unmarshaling. Otherwise, binary.Read() is used.
194+
func (d Descriptor) GetMetadata(v any) error {
195+
if err := d.raw.getExtra(v); err != nil {
196+
return fmt.Errorf("%w", err)
197+
}
198+
return nil
199+
}
200+
171201
// PartitionMetadata gets metadata for a partition data object.
172202
//
173203
//nolint:nonamedreturns // Named returns effective as documentation.
174204
func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
175-
return d.raw.getPartitionMetadata()
205+
fs, pt, arch, err = d.raw.getPartitionMetadata()
206+
if err != nil {
207+
return 0, 0, "", fmt.Errorf("%w", err)
208+
}
209+
return fs, pt, arch, err
176210
}
177211

178212
var errHashUnsupported = errors.New("hash algorithm unsupported")
@@ -204,8 +238,7 @@ func (d Descriptor) SignatureMetadata() (ht crypto.Hash, fp []byte, err error) {
204238

205239
var s signature
206240

207-
b := bytes.NewReader(d.raw.Extra[:])
208-
if err := binary.Read(b, binary.LittleEndian, &s); err != nil {
241+
if err := d.raw.getExtra(&s); err != nil {
209242
return ht, fp, fmt.Errorf("%w", err)
210243
}
211244

@@ -232,8 +265,7 @@ func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) {
232265

233266
var m cryptoMessage
234267

235-
b := bytes.NewReader(d.raw.Extra[:])
236-
if err := binary.Read(b, binary.LittleEndian, &m); err != nil {
268+
if err := d.raw.getExtra(&m); err != nil {
237269
return 0, 0, fmt.Errorf("%w", err)
238270
}
239271

@@ -248,8 +280,7 @@ func (d Descriptor) SBOMMetadata() (SBOMFormat, error) {
248280

249281
var s sbom
250282

251-
b := bytes.NewReader(d.raw.Extra[:])
252-
if err := binary.Read(b, binary.LittleEndian, &s); err != nil {
283+
if err := d.raw.getExtra(&s); err != nil {
253284
return 0, fmt.Errorf("%w", err)
254285
}
255286

pkg/sif/descriptor_input.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
1+
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
22
// This software is licensed under a 3-clause BSD license. Please consult the
33
// LICENSE file distributed with the sources of this project regarding your
44
// rights to use or distribute this software.
@@ -19,7 +19,7 @@ type descriptorOpts struct {
1919
linkID uint32
2020
alignment int
2121
name string
22-
extra interface{}
22+
extra any
2323
t time.Time
2424
}
2525

@@ -92,6 +92,15 @@ func OptObjectTime(t time.Time) DescriptorInputOpt {
9292
}
9393
}
9494

95+
// OptMetadata sets v as the metadata for a data object. If the encoding.BinaryMarshaler interface
96+
// is implemented by v, it is used for marshaling. Otherwise, binary.Write() is used.
97+
func OptMetadata(v any) DescriptorInputOpt {
98+
return func(t DataType, opts *descriptorOpts) error {
99+
opts.extra = v
100+
return nil
101+
}
102+
}
103+
95104
type unexpectedDataTypeError struct {
96105
got DataType
97106
want []DataType
@@ -259,7 +268,8 @@ const DefaultObjectGroup = 1
259268
//
260269
// It is possible (and often necessary) to store additional metadata related to certain types of
261270
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
262-
// OptSignatureMetadata, and OptSBOMMetadata for this purpose.
271+
// OptSignatureMetadata, and OptSBOMMetadata for this purpose. To set custom metadata, use
272+
// OptMetadata.
263273
//
264274
// By default, the data object will be placed in the default data object group (1). To override
265275
// this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or

pkg/sif/descriptor_input_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
1+
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
22
// This software is licensed under a 3-clause BSD license. Please consult the
33
// LICENSE file distributed with the sources of this project regarding your
44
// rights to use or distribute this software.
@@ -118,6 +118,13 @@ func TestNewDescriptorInput(t *testing.T) {
118118
OptObjectTime(time.Unix(946702800, 0)),
119119
},
120120
},
121+
{
122+
name: "OptMetadata",
123+
t: DataGeneric,
124+
opts: []DescriptorInputOpt{
125+
OptMetadata(testMetadata{100}),
126+
},
127+
},
121128
{
122129
name: "OptCryptoMessageMetadataUnexpectedDataType",
123130
t: DataGeneric,

pkg/sif/descriptor_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
1+
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
22
// This software is licensed under a 3-clause BSD license. Please consult the
33
// LICENSE file distributed with the sources of this project regarding your
44
// rights to use or distribute this software.
@@ -8,10 +8,12 @@ package sif
88
import (
99
"bytes"
1010
"crypto"
11+
"encoding/json"
1112
"errors"
1213
"io"
1314
"os"
1415
"path/filepath"
16+
"reflect"
1517
"testing"
1618

1719
"github.com/sebdah/goldie/v2"
@@ -102,6 +104,57 @@ func TestDescriptor_Name(t *testing.T) {
102104
}
103105
}
104106

107+
type testMetadata struct {
108+
Value int
109+
}
110+
111+
func (m testMetadata) MarshalBinary() ([]byte, error) {
112+
return json.Marshal(m)
113+
}
114+
115+
func (m *testMetadata) UnmarshalBinary(b []byte) error {
116+
return json.Unmarshal(bytes.TrimRight(b, "\x00"), m)
117+
}
118+
119+
func TestDescriptor_GetMetadata(t *testing.T) {
120+
md := testMetadata{100}
121+
122+
rd := rawDescriptor{
123+
DataType: DataGeneric,
124+
}
125+
if err := rd.setExtra(md); err != nil {
126+
t.Fatal(err)
127+
}
128+
129+
tests := []struct {
130+
name string
131+
rd rawDescriptor
132+
wantMD any
133+
wantErr error
134+
}{
135+
{
136+
name: "OK",
137+
rd: rd,
138+
wantMD: md,
139+
},
140+
}
141+
for _, tt := range tests {
142+
t.Run(tt.name, func(t *testing.T) {
143+
d := Descriptor{raw: tt.rd}
144+
145+
var md testMetadata
146+
147+
if got, want := d.GetMetadata(&md), tt.wantErr; !errors.Is(got, want) {
148+
t.Fatalf("got error %v, want %v", got, want)
149+
}
150+
151+
if got, want := md, tt.wantMD; !reflect.DeepEqual(got, want) {
152+
t.Fatalf("got metadata %v, want %v", got, want)
153+
}
154+
})
155+
}
156+
}
157+
105158
func TestDescriptor_PartitionMetadata(t *testing.T) {
106159
p := partition{
107160
Fstype: FsSquash,
585 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)