add beginning of parser
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Melora Hugues 2023-08-27 15:09:30 +02:00
parent 12ce11bb6e
commit 54794b57a4
12 changed files with 457 additions and 8 deletions

64
base.go
View file

@ -1,7 +1,67 @@
package devicepath
type DevicePathNode interface{}
import (
"errors"
"strings"
)
type DevicePath interface {
type DeviceNodeType uint8
const (
HardwareDevicePath DeviceNodeType = 0x01
ACPIDevicePath DeviceNodeType = 0x02
MessagingDevicePath DeviceNodeType = 0x03
MediaDevicePath DeviceNodeType = 0x04
BIOSBootSpecificationsDevicePath DeviceNodeType = 0x05
EndOfHardwareDevicePath DeviceNodeType = 0x7F
)
type DeviceNodeSubType uint8
const (
PCIHardware DeviceNodeSubType = 0x01
)
const (
SATAMessaging DeviceNodeSubType = 18
)
const EndOfEntireDevicePath DeviceNodeSubType = 0xFF
const ACPISubType DeviceNodeSubType = 1
type DevicePathNode interface {
Type() DeviceNodeType
SubType() DeviceNodeSubType
ParseString(raw string) error
String() string
Bytes() []byte
}
var ErrPathComplete = errors.New("device path already complete")
type DevicePath struct {
nodes []DevicePathNode
}
func (dp *DevicePath) complete() bool {
return len(dp.nodes) > 0 && dp.nodes[len(dp.nodes)-1].Type() == EndOfHardwareDevicePath
}
func (dp *DevicePath) PushNode(node DevicePathNode) error {
if dp.complete() {
return ErrPathComplete
}
dp.nodes = append(dp.nodes, node)
return nil
}
func (dp *DevicePath) String() string {
var res []string
for _, node := range dp.nodes {
if node.String() != "" {
res = append(res, node.String())
}
}
return strings.Join(res, "/")
}

26
end.go Normal file
View file

@ -0,0 +1,26 @@
package devicepath
import "errors"
type FinalDevicePath struct {
}
func (fp *FinalDevicePath) Type() DeviceNodeType {
return EndOfHardwareDevicePath
}
func (fp *FinalDevicePath) SubType() DeviceNodeSubType {
return EndOfEntireDevicePath
}
func (fp *FinalDevicePath) Bytes() []byte {
return nil
}
func (fp *FinalDevicePath) String() string {
return ""
}
func (fp *FinalDevicePath) ParseString(raw string) error {
return errors.New("unparsable")
}

8
go.mod
View file

@ -1,3 +1,11 @@
module git.faercol.me/faercol/device-path
go 1.20
require github.com/stretchr/testify v1.8.4
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum Normal file
View file

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,9 +1,65 @@
package devicepath
func ParseNodeString(string) (DevicePathNode, error) {
return nil, nil
import (
"errors"
"fmt"
"strings"
)
var (
ErrUnknownSubType = errors.New("unknown device sub-type")
ErrMalformedString = errors.New("malformed device string")
)
type ErrInvalidArguments struct {
subErr error
}
func ParsePathString(string) (DevicePath, error) {
return nil, nil
func (e *ErrInvalidArguments) Error() string {
return fmt.Sprintf("invalid argument: %s", e.subErr.Error())
}
type ErrInvalidPath struct {
subErr error
node string
}
func (e *ErrInvalidPath) Error() string {
return fmt.Sprintf("invalid device path on section %q: %s", e.node, e.subErr.Error())
}
type nodeGenerator func() DevicePathNode
var prefixTypeAssociation = map[string]nodeGenerator{
sataPrefix: func() DevicePathNode { return &SataDevicePath{} },
pciPrefix: func() DevicePathNode { return &PCIDevicePath{} },
pciRootPrefix: func() DevicePathNode { return &PCIRootDevicePath{} },
}
func Parsenode(raw string) (DevicePathNode, error) {
prefix := getPrefix(raw)
devGen, ok := prefixTypeAssociation[prefix]
if !ok {
return nil, ErrUnknownSubType
}
dev := devGen()
if err := dev.ParseString(raw); err != nil {
return nil, &ErrInvalidArguments{subErr: err}
}
return dev, nil
}
func ParseDevicePath(raw string) (*DevicePath, error) {
dp := DevicePath{}
for _, nodeStr := range strings.Split(raw, "/") {
node, err := Parsenode(nodeStr)
if err != nil {
return nil, &ErrInvalidPath{subErr: err, node: nodeStr}
}
if err := dp.PushNode(node); err != nil {
return nil, err
}
}
dp.PushNode(&FinalDevicePath{})
return &dp, nil
}

View file

@ -1,7 +1,25 @@
package devicepath_test
import "testing"
import (
"testing"
func TestTest(t *testing.T) {
devicepath "git.faercol.me/faercol/device-path"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestOK(t *testing.T) {
input := "Sata(1,65535,0)"
res, err := devicepath.Parsenode(input)
require.NoError(t, err)
assert.Equal(t, devicepath.MessagingDevicePath, res.Type())
assert.Equal(t, devicepath.SATAMessaging, res.SubType())
assert.Equal(t, "Sata(1,65535,0)", res.String())
}
func TestParseDevicePathOK(t *testing.T) {
input := "PciRoot(0x0)/Pci(0x1,0x2)/Pci(0x0,0x1)/Sata(0,65535,0)"
res, err := devicepath.ParseDevicePath(input)
require.NoError(t, err)
assert.Equal(t, "PciRoot(0x0)/Pci(0x1,0x2)/Pci(0x0,0x1)/Sata(0,65535,0)", res.String())
}

72
pci.go Normal file
View file

@ -0,0 +1,72 @@
package devicepath
import (
"encoding/binary"
"errors"
"fmt"
"strconv"
"strings"
)
const (
pciLength uint16 = 6
pciPrefix string = "Pci"
pciStringLength int = 12
)
type PCIDevicePath struct {
function uint8
device uint8
}
func (pcp *PCIDevicePath) Type() DeviceNodeType {
return HardwareDevicePath
}
func (pcp *PCIDevicePath) SubType() DeviceNodeSubType {
return PCIHardware
}
func (pcp *PCIDevicePath) Bytes() []byte {
res := make([]byte, pciLength)
res[0] = byte(pcp.Type())
res[1] = byte(pcp.SubType())
binary.LittleEndian.PutUint16(res[2:3], pciLength)
res[4] = pcp.function
res[5] = pcp.device
return res
}
func (pcp *PCIDevicePath) String() string {
return fmt.Sprintf("%s(0x%x,0x%x)", pciPrefix, pcp.function, pcp.device)
}
func (pcp *PCIDevicePath) ParseString(raw string) error {
if !checkStringFormat(raw, pciPrefix, pciStringLength) {
return ErrMalformedString
}
args := strings.Split(raw[len(pciPrefix)+1:pciStringLength-1], ",")
if len(args) != 2 {
return errors.New("unexpected number of arguments")
}
if function, err := strconv.ParseUint(args[0], 0, 8); err != nil {
return err
} else {
fmt.Println("function", function)
pcp.function = uint8(function)
}
if device, err := strconv.ParseUint(args[1], 0, 8); err != nil {
return err
} else {
fmt.Println("device", device)
pcp.device = uint8(device)
}
return nil
}
func NewPCIDevicePath(function, device uint8) *PCIDevicePath {
return &PCIDevicePath{
function: function,
device: device,
}
}

22
pci_test.go Normal file
View file

@ -0,0 +1,22 @@
package devicepath_test
import (
"testing"
devicepath "git.faercol.me/faercol/device-path"
"github.com/stretchr/testify/assert"
)
func TestToString(t *testing.T) {
t.Run("Only digits", func(t *testing.T) {
expected := "Pci(0x0,0x1)"
dev := devicepath.NewPCIDevicePath(0, 1)
assert.Equal(t, expected, dev.String())
})
t.Run("Letters", func(t *testing.T) {
expected := "Pci(0x2,0xa)"
dev := devicepath.NewPCIDevicePath(2, 10)
assert.Equal(t, expected, dev.String())
})
}

46
pciroot.go Normal file
View file

@ -0,0 +1,46 @@
package devicepath
import (
"errors"
"fmt"
"strconv"
"strings"
)
const pciRootPrefix string = "PciRoot"
type PCIRootDevicePath struct {
uid int32
}
func (prp *PCIRootDevicePath) Type() DeviceNodeType {
return ACPIDevicePath
}
func (prp *PCIRootDevicePath) SubType() DeviceNodeSubType {
return ACPISubType
}
func (prp *PCIRootDevicePath) Bytes() []byte {
return nil
}
func (prp *PCIRootDevicePath) String() string {
return fmt.Sprintf("%s(0x%x)", pciRootPrefix, prp.uid)
}
func (prp *PCIRootDevicePath) ParseString(raw string) error {
if !checkStringFormat(raw, pciRootPrefix, -1) {
return ErrMalformedString
}
args := strings.Split(raw[len(pciRootPrefix)+1:len(raw)-1], ",")
if len(args) != 1 {
return errors.New("unexpected number of arguments")
}
if uid, err := strconv.ParseInt(args[0], 0, 32); err != nil {
return err
} else {
prp.uid = int32(uid)
}
return nil
}

71
sata.go Normal file
View file

@ -0,0 +1,71 @@
package devicepath
import (
"errors"
"fmt"
"strconv"
"strings"
)
const (
defaultPortMultiplier = 0xffff
defaultLogicalUnitNumber = 0
sataLength uint16 = 10
sataPrefix string = "Sata"
)
type SataDevicePath struct {
hbaPortNumber uint16
portMultiplierPortNumber uint16
logicalUnitNumber uint16
}
func (sp *SataDevicePath) Type() DeviceNodeType {
return MessagingDevicePath
}
func (sp *SataDevicePath) SubType() DeviceNodeSubType {
return SATAMessaging
}
func (sp *SataDevicePath) Bytes() []byte {
return nil
}
func (sp *SataDevicePath) String() string {
return fmt.Sprintf("%s(%d,%d,%d)", sataPrefix, sp.hbaPortNumber, sp.portMultiplierPortNumber, sp.logicalUnitNumber)
}
func (sp *SataDevicePath) ParseString(raw string) error {
if !checkStringFormat(raw, sataPrefix, -1) {
return ErrMalformedString
}
args := strings.Split(raw[len(sataPrefix)+1:len(raw)-1], ",")
if len(args) != 3 {
return errors.New("unexpected number of arguments")
}
if hbaPortNumber, err := strconv.Atoi(args[0]); err != nil {
return err
} else {
sp.hbaPortNumber = uint16(hbaPortNumber)
}
if portMultiplierPortNumber, err := strconv.Atoi(args[1]); err != nil {
return err
} else {
sp.portMultiplierPortNumber = uint16(portMultiplierPortNumber)
}
if logicalUnitNumber, err := strconv.Atoi(args[2]); err != nil {
return err
} else {
sp.logicalUnitNumber = uint16(logicalUnitNumber)
}
return nil
}
func NewSataFromPortNumber(portNumber uint16) *SataDevicePath {
return &SataDevicePath{
hbaPortNumber: portNumber,
portMultiplierPortNumber: defaultPortMultiplier,
logicalUnitNumber: defaultLogicalUnitNumber,
}
}

42
sata_test.go Normal file
View file

@ -0,0 +1,42 @@
package devicepath_test
import (
"testing"
devicepath "git.faercol.me/faercol/device-path"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseString(t *testing.T) {
t.Run("OK", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
require.NoError(t, sataDev.ParseString("Sata(1,65535,0)"))
assert.Equal(t, "Sata(1,65535,0)", sataDev.String())
})
t.Run("Err invalid prefix", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
assert.ErrorIs(t, sataDev.ParseString("PciRoot(0x0)"), devicepath.ErrMalformedString)
})
t.Run("Err invalid number arguments", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
assert.ErrorContains(t, sataDev.ParseString("Sata(1,2,3,4)"), "unexpected number of arguments")
})
t.Run("Err invalid format arg0", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
assert.ErrorContains(t, sataDev.ParseString("Sata(toto,65535,0)"), `strconv.Atoi: parsing "toto": invalid syntax`)
})
t.Run("Err invalid format arg1", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
assert.ErrorContains(t, sataDev.ParseString("Sata(0,toto,0)"), `strconv.Atoi: parsing "toto": invalid syntax`)
})
t.Run("Err invalid format arg2", func(t *testing.T) {
var sataDev devicepath.SataDevicePath
assert.ErrorContains(t, sataDev.ParseString("Sata(0,65535,toto)"), `strconv.Atoi: parsing "toto": invalid syntax`)
})
}

18
utils.go Normal file
View file

@ -0,0 +1,18 @@
package devicepath
import "strings"
func getPrefix(raw string) string {
firstCharAfterId := strings.Index(raw, "(")
if firstCharAfterId == -1 {
return ""
}
return raw[0:firstCharAfterId]
}
func checkStringFormat(raw, prefix string, length int) bool {
if length > 0 && len(raw) != length {
return false
}
return strings.HasPrefix(raw, prefix+"(") && strings.HasSuffix(raw, ")")
}