240 lines
4.9 KiB
Go
240 lines
4.9 KiB
Go
|
// package bootprotocol contains the elements necessary to use the custom network boot protocol
|
||
|
package bootprotocol
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/google/uuid"
|
||
|
)
|
||
|
|
||
|
type Action int8
|
||
|
|
||
|
const (
|
||
|
ActionRequest Action = iota
|
||
|
ActionAccept
|
||
|
ActionDeny
|
||
|
ActionUnknown
|
||
|
)
|
||
|
|
||
|
var spaceByte = []byte(" ")
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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())
|
||
|
params := [][]byte{
|
||
|
[]byte(fmt.Sprintf("%s=%s", keyID, am.id.String())),
|
||
|
[]byte(fmt.Sprintf("%s=%s", keyEfiApp, am.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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
}
|
||
|
}
|