http-boot-server/bootserver/bootprotocol/bootprotocol.go

258 lines
5.6 KiB
Go
Raw Normal View History

// package bootprotocol contains the elements necessary to use the custom network boot protocol
package bootprotocol
import (
"bytes"
"encoding"
"errors"
"fmt"
2023-09-01 16:42:37 +00:00
"strings"
"github.com/google/uuid"
)
type Action int8
const (
ActionRequest Action = iota
ActionAccept
ActionDeny
ActionUnknown
)
var spaceByte = []byte(" ")
2023-09-01 16:42:37 +00:00
var commaByte = []byte(";")
const (
keyID = "id"
keyEfiApp = "efi_app"
keyReason = "reason"
)
var ErrInvalidFormat = errors.New("invalid format for message")
var ErrUnknownAction = errors.New("unknown action for message")
var ErrInvalidParam = errors.New("invalid parameter for message")
var ErrMissingParam = errors.New("missing parameter for message")
func (a Action) String() string {
switch a {
case ActionAccept:
return "BOOT_ACCEPT"
case ActionDeny:
return "BOOT_DENY"
case ActionRequest:
return "BOOT_REQUEST"
default:
return "unknown"
}
}
func newActionFromBytes(raw []byte) Action {
switch string(raw) {
case "BOOT_ACCEPT":
return ActionAccept
case "BOOT_DENY":
return ActionDeny
case "BOOT_REQUEST":
return ActionRequest
default:
return ActionUnknown
}
}
type Message interface {
encoding.BinaryUnmarshaler
encoding.BinaryMarshaler
Action() Action
ID() uuid.UUID
2023-07-29 19:23:36 +00:00
String() string
}
type requestMessage struct {
id uuid.UUID
}
func (rm *requestMessage) UnmarshalBinary(data []byte) error {
params := bytes.Split(data, commaByte)
for _, p := range params {
k, v, err := splitKeyValue(p)
if err != nil {
return fmt.Errorf("failed to parse parameter %q: %w", string(p), err)
}
if bytes.Equal(k, []byte(keyID)) {
parsedId, err := uuid.ParseBytes(v)
if err != nil {
return ErrInvalidParam
}
rm.id = parsedId
return nil
}
}
return ErrMissingParam
}
func (rm *requestMessage) MarshalBinary() (data []byte, err error) {
action := []byte(rm.Action().String())
params := []byte(fmt.Sprintf("%s=%s", keyID, rm.id.String()))
return bytes.Join([][]byte{action, params}, spaceByte), nil
}
func (rm *requestMessage) Action() Action {
return ActionRequest
}
func (rm *requestMessage) ID() uuid.UUID {
return rm.id
}
2023-07-29 19:23:36 +00:00
func (rm *requestMessage) String() string {
return fmt.Sprintf("%s from %s", ActionRequest.String(), rm.ID().String())
}
type acceptMessage struct {
id uuid.UUID
efiApp string
}
func (am *acceptMessage) UnmarshalBinary(data []byte) error {
params := bytes.Split(data, commaByte)
for _, p := range params {
k, v, err := splitKeyValue(p)
if err != nil {
return fmt.Errorf("failed to parse parameter %q: %w", string(p), err)
}
switch string(k) {
case keyID:
parsedId, err := uuid.ParseBytes(v)
if err != nil {
return ErrInvalidParam
}
am.id = parsedId
case keyEfiApp:
am.efiApp = string(v)
}
}
if am.id == uuid.Nil || am.efiApp == "" {
return ErrMissingParam
}
return nil
}
func (am *acceptMessage) MarshalBinary() (data []byte, err error) {
action := []byte(am.Action().String())
2023-09-01 16:42:37 +00:00
// efiApp := strings.ReplaceAll(am.efiApp, `\`, `\\`)
// efiApp = strings.ReplaceAll(efiApp, "File(", "")
efiApp := strings.ReplaceAll(am.efiApp, "File(", "")
efiApp, _ = strings.CutSuffix(efiApp, ")")
params := [][]byte{
[]byte(fmt.Sprintf("%s=%s", keyID, am.id.String())),
2023-09-01 16:42:37 +00:00
[]byte(fmt.Sprintf("%s=%s", keyEfiApp, efiApp)),
}
param_bytes := bytes.Join(params, commaByte)
return bytes.Join([][]byte{action, param_bytes}, spaceByte), nil
}
func (am *acceptMessage) Action() Action {
return ActionAccept
}
func (am *acceptMessage) ID() uuid.UUID {
return am.id
}
2023-07-29 19:23:36 +00:00
func (am *acceptMessage) String() string {
return fmt.Sprintf("%s from %s, app %s", ActionAccept.String(), am.ID().String(), am.efiApp)
}
type denyMessage struct {
id uuid.UUID
reason string
}
func (dm *denyMessage) UnmarshalBinary(data []byte) error {
params := bytes.Split(data, commaByte)
for _, p := range params {
k, v, err := splitKeyValue(p)
if err != nil {
return fmt.Errorf("failed to parse parameter %q: %w", string(p), err)
}
switch string(k) {
case keyID:
parsedId, err := uuid.ParseBytes(v)
if err != nil {
return ErrInvalidParam
}
dm.id = parsedId
case keyReason:
dm.reason = string(v)
}
}
if dm.id == uuid.Nil || dm.reason == "" {
return ErrMissingParam
}
return nil
}
func (dm *denyMessage) MarshalBinary() (data []byte, err error) {
action := []byte(dm.Action().String())
params := [][]byte{
[]byte(fmt.Sprintf("%s=%s", keyID, dm.id.String())),
[]byte(fmt.Sprintf("%s=%s", keyReason, dm.reason)),
}
param_bytes := bytes.Join(params, commaByte)
return bytes.Join([][]byte{action, param_bytes}, spaceByte), nil
}
func (dm *denyMessage) Action() Action {
return ActionDeny
}
func (dm *denyMessage) ID() uuid.UUID {
return dm.id
}
2023-07-29 19:23:36 +00:00
func (dm *denyMessage) String() string {
return fmt.Sprintf("%s from %s, reason %q", ActionDeny.String(), dm.ID().String(), dm.reason)
}
func MessageFromBytes(dat []byte) (Message, error) {
rawAction, content, found := bytes.Cut(dat, spaceByte)
if !found {
return nil, ErrInvalidFormat
}
var message Message
action := newActionFromBytes(rawAction)
switch action {
case ActionRequest:
message = &requestMessage{}
case ActionAccept:
message = &acceptMessage{}
case ActionDeny:
message = &denyMessage{}
default:
return nil, ErrUnknownAction
}
if err := message.UnmarshalBinary(content); err != nil {
return nil, fmt.Errorf("failed to parse message: %w", err)
}
return message, nil
}
func Accept(id uuid.UUID, efiApp string) Message {
return &acceptMessage{
id: id,
efiApp: efiApp,
}
}
func Deny(id uuid.UUID, reason string) Message {
return &denyMessage{
id: id,
reason: reason,
}
}