Skip to content

Commit

Permalink
feat: custom metadata support
Browse files Browse the repository at this point in the history
Add OptMetadata, which allows supplying custom metadata when calling
NewDescriptorInput. Add GetMetadata, which allows a user to parse custom
metadata from a Descriptor.
  • Loading branch information
tri-adam committed Mar 2, 2023
1 parent a29fb45 commit 59a5446
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 24 deletions.
69 changes: 50 additions & 19 deletions pkg/sif/descriptor.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <[email protected]> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
Expand All @@ -10,6 +10,7 @@ package sif
import (
"bytes"
"crypto"
"encoding"
"encoding/binary"
"errors"
"fmt"
Expand Down Expand Up @@ -78,28 +79,49 @@ func (d *rawDescriptor) setName(name string) error {

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

// setExtra encodes v into the extra field of d.
func (d *rawDescriptor) setExtra(v interface{}) error {
// setExtra encodes v into the extra field of d. If the encoding.BinaryMarshaler interface is
// implemented by v, it is used for marshaling. Otherwise, binary.Write() is used.
func (d *rawDescriptor) setExtra(v any) error {
if v == nil {
return nil
}

if binary.Size(v) > len(d.Extra) {
return errExtraTooLarge
var extra []byte

if m, ok := v.(encoding.BinaryMarshaler); ok {
b, err := m.MarshalBinary()
if err != nil {
return err
}
extra = b
} else {
b := new(bytes.Buffer)
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
return err
}
extra = b.Bytes()
}

b := new(bytes.Buffer)
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
return err
if len(extra) > len(d.Extra) {
return errExtraTooLarge
}

for i := copy(d.Extra[:], b.Bytes()); i < len(d.Extra); i++ {
for i := copy(d.Extra[:], extra); i < len(d.Extra); i++ {
d.Extra[i] = 0
}

return nil
}

// getExtra decodes the extra fields of d into v. If the encoding.BinaryUnmarshaler interface is
// implemented by v, it is used for unmarshaling. Otherwise, binary.Read() is used.
func (d *rawDescriptor) getExtra(v any) error {
if u, ok := v.(encoding.BinaryUnmarshaler); ok {
return u.UnmarshalBinary(d.Extra[:])
}
return binary.Read(bytes.NewReader(d.Extra[:]), binary.LittleEndian, v)
}

// getPartitionMetadata gets metadata for a partition data object.
func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error) {
if got, want := d.DataType, DataPartition; got != want {
Expand All @@ -108,9 +130,8 @@ func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error)

var p partition

b := bytes.NewReader(d.Extra[:])
if err := binary.Read(b, binary.LittleEndian, &p); err != nil {
return 0, 0, "", fmt.Errorf("%w", err)
if err := d.getExtra(&p); err != nil {
return 0, 0, "", err
}

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

// GetMetadata reads metadata from d into v. If the encoding.BinaryUnmarshaler interface is
// implemented by v, it is used for unmarshaling. Otherwise, binary.Read() is used.
func (d Descriptor) GetMetadata(v any) error {
if err := d.raw.getExtra(v); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}

// PartitionMetadata gets metadata for a partition data object.
//
//nolint:nonamedreturns // Named returns effective as documentation.
func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
return d.raw.getPartitionMetadata()
fs, pt, arch, err = d.raw.getPartitionMetadata()
if err != nil {
return 0, 0, "", fmt.Errorf("%w", err)
}
return fs, pt, arch, err
}

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

var s signature

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

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

var m cryptoMessage

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

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

var s sbom

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

Expand Down
16 changes: 13 additions & 3 deletions pkg/sif/descriptor_input.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand All @@ -19,7 +19,7 @@ type descriptorOpts struct {
linkID uint32
alignment int
name string
extra interface{}
extra any
t time.Time
}

Expand Down Expand Up @@ -92,6 +92,15 @@ func OptObjectTime(t time.Time) DescriptorInputOpt {
}
}

// OptMetadata sets v as the metadata for a data object. If the encoding.BinaryMarshaler interface
// is implemented by v, it is used for marshaling. Otherwise, binary.Write() is used.
func OptMetadata(v any) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
opts.extra = v
return nil
}
}

type unexpectedDataTypeError struct {
got DataType
want []DataType
Expand Down Expand Up @@ -259,7 +268,8 @@ const DefaultObjectGroup = 1
//
// It is possible (and often necessary) to store additional metadata related to certain types of
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
// OptSignatureMetadata, and OptSBOMMetadata for this purpose.
// OptSignatureMetadata, and OptSBOMMetadata for this purpose. To set custom metadata, use
// OptMetadata.
//
// By default, the data object will be placed in the default data object group (1). To override
// this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or
Expand Down
9 changes: 8 additions & 1 deletion pkg/sif/descriptor_input_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -118,6 +118,13 @@ func TestNewDescriptorInput(t *testing.T) {
OptObjectTime(time.Unix(946702800, 0)),
},
},
{
name: "OptMetadata",
t: DataGeneric,
opts: []DescriptorInputOpt{
OptMetadata(testMetadata{100}),
},
},
{
name: "OptCryptoMessageMetadataUnexpectedDataType",
t: DataGeneric,
Expand Down
55 changes: 54 additions & 1 deletion pkg/sif/descriptor_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand All @@ -8,10 +8,12 @@ package sif
import (
"bytes"
"crypto"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/sebdah/goldie/v2"
Expand Down Expand Up @@ -102,6 +104,57 @@ func TestDescriptor_Name(t *testing.T) {
}
}

type testMetadata struct {
Value int
}

func (m testMetadata) MarshalBinary() ([]byte, error) {
return json.Marshal(m)
}

func (m *testMetadata) UnmarshalBinary(b []byte) error {
return json.Unmarshal(bytes.TrimRight(b, "\x00"), m)
}

func TestDescriptor_GetMetadata(t *testing.T) {
md := testMetadata{100}

rd := rawDescriptor{
DataType: DataGeneric,
}
if err := rd.setExtra(md); err != nil {
t.Fatal(err)
}

tests := []struct {
name string
rd rawDescriptor
wantMD any
wantErr error
}{
{
name: "OK",
rd: rd,
wantMD: md,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := Descriptor{raw: tt.rd}

var md testMetadata

if got, want := d.GetMetadata(&md), tt.wantErr; !errors.Is(got, want) {
t.Fatalf("got error %v, want %v", got, want)
}

if got, want := md, tt.wantMD; !reflect.DeepEqual(got, want) {
t.Fatalf("got metadata %v, want %v", got, want)
}
})
}
}

func TestDescriptor_PartitionMetadata(t *testing.T) {
p := partition{
Fstype: FsSquash,
Expand Down
Binary file not shown.

0 comments on commit 59a5446

Please sign in to comment.