~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
102 lines · 3.4 KB Raw History
package requester

import (
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"slices"
	"strings"

	commonv1 "github.com/johnandersen777/socialweb-computer-kcp/api/common/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/cloudinit"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
)

const maxNetworkAddress = 2048

var (
	networkTagRE = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$`)

	dtlsFingerprintRE = regexp.MustCompile(`^sha-256 [0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){31}$`)

	errShellWithoutSecrets = errors.New("a shell needs its token in spec.secretsRef: the secrets bundle writes the shell's envFile on the guest")
)

func tunnelsOptions(contract v1alpha1.ComputeContract) (cloudinit.TunnelsOptions, error) {
	opts := cloudinit.TunnelsOptions{}
	shell := false
	for _, t := range contract.Spec.Tunnels {
		tunnel := cloudinit.Tunnel{Tag: t.Tag, Port: t.Port, Server: t.Server, Transport: string(t.Transport)}
		if t.Shell != nil {
			tunnel.ShellEnvFile = strings.TrimSpace(t.Shell.EnvFile)
			if tunnel.ShellEnvFile == "" {
				tunnel.ShellEnvFile = cloudinit.DefaultShellEnvFile
			}
			shell = true
		}
		opts.Tunnels = append(opts.Tunnels, tunnel)
	}
	if shell && contract.Spec.SecretsRef == nil {
		return opts, errShellWithoutSecrets
	}
	if err := opts.Validate(); err != nil {
		return opts, err
	}
	return opts, nil
}

func ValidateNetworkAddress(kind, tag, address string) error {
	if !networkTagRE.MatchString(tag) {
		return fmt.Errorf("requester: tag %q is not a DNS label of at most 32 characters", tag)
	}
	if len(address) > maxNetworkAddress {
		return fmt.Errorf("requester: the %s address is longer than %d bytes", tag, maxNetworkAddress)
	}
	switch kind {
	case market.OnNetworkKindHTTPS:
		u, err := url.Parse(address)
		if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil {
			return fmt.Errorf("requester: the %s address %q is not an https URL", tag, address)
		}
	case market.OnNetworkKindWebRTC:
		if !dtlsFingerprintRE.MatchString(address) {
			return fmt.Errorf("requester: the %s address %q is not a DTLS fingerprint \"sha-256 XX:XX:...\"", tag, address)
		}
	default:
		return fmt.Errorf("requester: the %s address names kind %q; a tagged address is https or webrtc", tag, kind)
	}
	return nil
}

func recordNetwork(state *v1alpha1.NodeClaimContract, parsed *GuestEvent) bool {
	if state == nil || parsed == nil || parsed.Tag == "" {
		return false
	}
	next := v1alpha1.ContractNetworkAddress{Tag: parsed.Tag, Kind: v1alpha1.NetworkKind(parsed.NetworkKind), Address: parsed.Address,
		At: parsed.ObservedAt, Event: commonv1.StrongRef{URI: parsed.Event.URI, CID: parsed.Event.CID}}
	i := slices.IndexFunc(state.Networks, func(n v1alpha1.ContractNetworkAddress) bool { return n.Tag == parsed.Tag })
	if i < 0 {
		state.Networks = append(state.Networks, next)
		return true
	}
	current := state.Networks[i]
	if current.Address == next.Address && current.Kind == next.Kind && current.Event.URI == next.Event.URI {
		return false
	}
	state.Networks[i] = next
	return true
}

func networkRecorded(state *v1alpha1.NodeClaimContract, parsed *GuestEvent) bool {
	if state == nil || parsed == nil {
		return false
	}
	for _, n := range state.Networks {
		if n.Tag == parsed.Tag && n.Address == parsed.Address && string(n.Kind) == parsed.NetworkKind {
			return true
		}
	}
	return false
}