mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 06:27:05 +00:00
Implement runtime.Framer for CBOR Sequences.
This commit is contained in:
parent
9b7a839bde
commit
e2b36a0f0c
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cbor
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
// NewFramer returns a runtime.Framer based on RFC 8742 CBOR Sequences. Each frame contains exactly
|
||||
// one encoded CBOR data item.
|
||||
func NewFramer() runtime.Framer {
|
||||
return framer{}
|
||||
}
|
||||
|
||||
var _ runtime.Framer = framer{}
|
||||
|
||||
type framer struct{}
|
||||
|
||||
func (framer) NewFrameReader(rc io.ReadCloser) io.ReadCloser {
|
||||
return &frameReader{
|
||||
decoder: cbor.NewDecoder(rc),
|
||||
closer: rc,
|
||||
}
|
||||
}
|
||||
|
||||
func (framer) NewFrameWriter(w io.Writer) io.Writer {
|
||||
// Each data item in a CBOR sequence is self-delimiting (like JSON objects).
|
||||
return w
|
||||
}
|
||||
|
||||
type frameReader struct {
|
||||
decoder *cbor.Decoder
|
||||
closer io.Closer
|
||||
|
||||
overflow []byte
|
||||
}
|
||||
|
||||
func (fr *frameReader) Read(dst []byte) (int, error) {
|
||||
if len(fr.overflow) > 0 {
|
||||
// We read a frame that was too large for the destination slice in a previous call
|
||||
// to Read and have bytes left over.
|
||||
n := copy(dst, fr.overflow)
|
||||
if n < len(fr.overflow) {
|
||||
fr.overflow = fr.overflow[n:]
|
||||
return n, io.ErrShortBuffer
|
||||
}
|
||||
fr.overflow = nil
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// The Reader contract allows implementations to use all of dst[0:len(dst)] as scratch
|
||||
// space, even if n < len(dst), but it does not allow implementations to use
|
||||
// dst[len(dst):cap(dst)]. Slicing it up-front allows us to append to it without worrying
|
||||
// about overwriting dst[len(dst):cap(dst)].
|
||||
m := cbor.RawMessage(dst[0:0:len(dst)])
|
||||
if err := fr.decoder.Decode(&m); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(m) > len(dst) {
|
||||
// The frame was too big, m has a newly-allocated underlying array to accommodate
|
||||
// it.
|
||||
fr.overflow = m[len(dst):]
|
||||
return copy(dst, m), io.ErrShortBuffer
|
||||
}
|
||||
|
||||
return len(m), nil
|
||||
}
|
||||
|
||||
func (fr *frameReader) Close() error {
|
||||
return fr.closer.Close()
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cbor_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// TestFrameReaderReadError tests that the frame reader does not resume after encountering a
|
||||
// well-formedness error in the input stream. According to RFC 8742 Section 2.8: "[...] if any data
|
||||
// item in the sequence is not well formed, it is not possible to reliably decode the rest of the
|
||||
// sequence."
|
||||
func TestFrameReaderReadError(t *testing.T) {
|
||||
input := []byte{
|
||||
0xff, // ill-formed initial break
|
||||
0xa0, // followed by well-formed empty map
|
||||
}
|
||||
fr := cbor.NewFramer().NewFrameReader(io.NopCloser(bytes.NewReader(input)))
|
||||
for i := 0; i < 3; i++ {
|
||||
n, err := fr.Read(nil)
|
||||
if err == nil || errors.Is(err, io.ErrShortBuffer) {
|
||||
t.Fatalf("expected a non-nil error other than io.ErrShortBuffer, got: %v", err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("expected 0 bytes read on error, got %d", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrameReaderRead(t *testing.T) {
|
||||
type ChunkedFrame [][]byte
|
||||
|
||||
for _, tc := range []struct {
|
||||
Name string
|
||||
Frames []ChunkedFrame
|
||||
}{
|
||||
{
|
||||
Name: "consecutive frames",
|
||||
Frames: []ChunkedFrame{
|
||||
[][]byte{{0xa0}},
|
||||
[][]byte{{0xa0}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "zero-length destination buffer",
|
||||
Frames: []ChunkedFrame{
|
||||
[][]byte{{}, {0xa0}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "overflow",
|
||||
Frames: []ChunkedFrame{
|
||||
[][]byte{
|
||||
{0x43},
|
||||
{'x'},
|
||||
{'y', 'z'},
|
||||
},
|
||||
[][]byte{
|
||||
{0xa1, 0x43, 'f', 'o', 'o'},
|
||||
{'b'},
|
||||
{'a', 'r'},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var concatenation []byte
|
||||
for _, f := range tc.Frames {
|
||||
for _, c := range f {
|
||||
concatenation = append(concatenation, c...)
|
||||
}
|
||||
}
|
||||
|
||||
fr := cbor.NewFramer().NewFrameReader(io.NopCloser(bytes.NewReader(concatenation)))
|
||||
|
||||
for _, frame := range tc.Frames {
|
||||
var want, got []byte
|
||||
for i, chunk := range frame {
|
||||
dst := make([]byte, len(chunk), 2*len(chunk))
|
||||
for i := len(dst); i < cap(dst); i++ {
|
||||
dst[:cap(dst)][i] = 0xff
|
||||
}
|
||||
n, err := fr.Read(dst)
|
||||
if n != len(chunk) {
|
||||
t.Errorf("expected %d bytes read, got %d", len(chunk), n)
|
||||
}
|
||||
if i == len(frame)-1 && err != nil {
|
||||
t.Errorf("unexpected non-nil error on last read of frame: %v", err)
|
||||
} else if i < len(frame)-1 && !errors.Is(err, io.ErrShortBuffer) {
|
||||
t.Errorf("expected io.ErrShortBuffer on all but the last read of a frame, got: %v", err)
|
||||
}
|
||||
for i := len(dst); i < cap(dst); i++ {
|
||||
if dst[:cap(dst)][i] != 0xff {
|
||||
t.Errorf("read mutated underlying array beyond slice length: %#v", dst[len(dst):cap(dst)])
|
||||
break
|
||||
}
|
||||
}
|
||||
want = append(want, chunk...)
|
||||
got = append(got, dst...)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("reassembled frame differs:\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeReadCloser struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (rc fakeReadCloser) Read(_ []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (rc fakeReadCloser) Close() error {
|
||||
return rc.err
|
||||
}
|
||||
|
||||
func TestFrameReaderClose(t *testing.T) {
|
||||
want := errors.New("test")
|
||||
if got := cbor.NewFramer().NewFrameReader(fakeReadCloser{err: want}).Close(); !errors.Is(got, want) {
|
||||
t.Errorf("got error %v, want %v", got, want)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user