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..850059c --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader.go @@ -0,0 +1,106 @@ +package aml + +import ( + "gopheros/kernel" + "reflect" + "unsafe" +) + +var ( + errInvalidUnreadByte = &kernel.Error{Module: "acpi_aml_parser", Message: "bad call to UnreadByte; stream offset is 0"} + errInvalidPkgEnd = &kernel.Error{Module: "acpi_aml_parser", Message: "attempted to set pkgEnd past the end of the stream"} + errReadPastPkgEnd = &kernel.Error{Module: "acpi_aml_parser", Message: "attempted to read past pkgEnd"} +) + +type amlStreamReader struct { + offset uint32 + data []byte + pkgEnd uint32 +} + +// 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 pkg has been reached. +func (r *amlStreamReader) EOF() bool { + return r.offset == r.pkgEnd +} + +func (r *amlStreamReader) SetPkgEnd(pkgEnd uint32) error { + if pkgEnd > uint32(len(r.data)) { + return errInvalidPkgEnd + } + + r.pkgEnd = pkgEnd + return nil +} + +// ReadByte returns the next byte from the stream. +func (r *amlStreamReader) ReadByte() (byte, error) { + if r.EOF() { + return 0, errReadPastPkgEnd + } + + 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, errReadPastPkgEnd + } + + 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, errReadPastPkgEnd + } + + 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 +} + +// DataPtr returns a pointer to the stream contents at the current stream offset. +func (r *amlStreamReader) DataPtr() uintptr { + if r.EOF() { + return 0 + } + return uintptr(unsafe.Pointer(&r.data[r.offset])) +} + +// SetOffset sets the reader offset to the supplied value. +func (r *amlStreamReader) SetOffset(off uint32) { + if max := uint32(len(r.data)); off > max { + off = max + } + 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..4fab06d --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader_test.go @@ -0,0 +1,131 @@ +package aml + +import ( + "math" + "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 err := r.SetPkgEnd(uint32(len(buf) + 1)); err != errInvalidPkgEnd { + t.Fatalf("expected to get errInvalidPkgEnd; got: %v", err) + } + + if err := r.SetPkgEnd(uint32(len(buf))); err != nil { + t.Fatal(err) + } + + 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 != errReadPastPkgEnd { + 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.UnreadByte(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Set offset past EOF; reader should cap the offset to len(buf) + r.SetOffset(math.MaxUint32) + + if _, err := r.PeekByte(); err != errReadPastPkgEnd { + t.Fatalf("unexpected error: %v", err) + } + if _, err := r.ReadByte(); err != errReadPastPkgEnd { + 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) + } + }) + + t.Run("ptr to data", 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") + } + + r.SetOffset(2) + ptr := r.DataPtr() + if got := *((*byte)(unsafe.Pointer(ptr))); got != buf[2] { + t.Fatal("expected DataPtr to return a pointer to buf[2]") + } + }) +}