diff --git a/src/gopheros/device/acpi/aml/stream_reader.go b/src/gopheros/device/acpi/aml/stream_reader.go new file mode 100644 index 0000000..31e6e5b --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader.go @@ -0,0 +1,84 @@ +package aml + +import ( + "errors" + "io" + "reflect" + "unsafe" +) + +var ( + errInvalidUnreadByte = errors.New("amlStreamReader: invalid use of UnreadByte") +) + +type amlStreamReader struct { + offset uint32 + data []byte +} + +// Init sets up the reader so it can read up to dataLen bytes from the virtual +// memory address dataAddr. If a non-zero initialOffset is specified, it will +// be used as the current offset in the stream. +func (r *amlStreamReader) Init(dataAddr uintptr, dataLen, initialOffset uint32) { + // Overlay a byte slice on top of the memory block to be accessed. + r.data = *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ + Len: int(dataLen), + Cap: int(dataLen), + Data: dataAddr, + })) + + r.SetOffset(initialOffset) +} + +// EOF returns true if the end of the stream has been reached. +func (r *amlStreamReader) EOF() bool { + return r.offset == uint32(len(r.data)) +} + +// ReadByte returns the next byte from the stream. +func (r *amlStreamReader) ReadByte() (byte, error) { + if r.EOF() { + return 0, io.EOF + } + + r.offset++ + return r.data[r.offset-1], nil +} + +// PeekByte returns the next byte from the stream without advancing the read pointer. +func (r *amlStreamReader) PeekByte() (byte, error) { + if r.EOF() { + return 0, io.EOF + } + + return r.data[r.offset], nil +} + +// LastByte returns the last byte read off the stream +func (r *amlStreamReader) LastByte() (byte, error) { + if r.offset == 0 { + return 0, io.EOF + } + + return r.data[r.offset-1], nil +} + +// UnreadByte moves back the read pointer by one byte. +func (r *amlStreamReader) UnreadByte() error { + if r.offset == 0 { + return errInvalidUnreadByte + } + + r.offset-- + return nil +} + +// Offset returns the current offset. +func (r *amlStreamReader) Offset() uint32 { + return r.offset +} + +// SetOffset sets the reader offset to the supplied value. +func (r *amlStreamReader) SetOffset(off uint32) { + r.offset = off +} diff --git a/src/gopheros/device/acpi/aml/stream_reader_test.go b/src/gopheros/device/acpi/aml/stream_reader_test.go new file mode 100644 index 0000000..560a7ca --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader_test.go @@ -0,0 +1,97 @@ +package aml + +import ( + "io" + "testing" + "unsafe" +) + +func TestAMLStreamReader(t *testing.T) { + buf := make([]byte, 16) + for i := 0; i < len(buf); i++ { + buf[i] = byte(i) + } + + t.Run("without offset", func(t *testing.T) { + var r amlStreamReader + r.Init( + uintptr(unsafe.Pointer(&buf[0])), + uint32(len(buf)), + 0, + ) + + if r.EOF() { + t.Fatal("unexpected EOF") + } + + if err := r.UnreadByte(); err != errInvalidUnreadByte { + t.Fatalf("expected errInvalidUnreadByte; got %v", err) + } + + if _, err := r.LastByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + + for i := 0; i < len(buf); i++ { + exp := byte(i) + + next, err := r.PeekByte() + if err != nil { + t.Fatal(err) + } + if next != exp { + t.Fatalf("expected PeekByte to return %d; got %d", exp, next) + } + + next, err = r.ReadByte() + if err != nil { + t.Fatal(err) + } + if next != exp { + t.Fatalf("expected ReadByte to return %d; got %d", exp, next) + } + + last, err := r.LastByte() + if err != nil { + t.Fatal(err) + } + if last != exp { + t.Fatalf("expected LastByte to return %d; got %d", exp, last) + } + } + + if _, err := r.PeekByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + if _, err := r.ReadByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + exp := byte(len(buf) - 1) + if last, _ := r.LastByte(); last != exp { + t.Fatalf("expected LastByte to return %d; got %d", exp, last) + } + + }) + + t.Run("with offset", func(t *testing.T) { + var r amlStreamReader + r.Init( + uintptr(unsafe.Pointer(&buf[0])), + uint32(len(buf)), + 8, + ) + + if r.EOF() { + t.Fatal("unexpected EOF") + } + + if exp, got := uint32(8), r.Offset(); got != exp { + t.Fatalf("expected Offset() to return %d; got %d", exp, got) + } + + exp := byte(8) + if next, _ := r.ReadByte(); next != exp { + t.Fatalf("expected ReadByte to return %d; got %d", exp, next) + } + }) +}