~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
157 lines · 5.6 KB Raw History
package request

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	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/market"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/webrtcproxy"
)

const contractEventPrefix = "cli-"

var _ webrtcproxy.Signaling = (*ContractSignaling)(nil)

type ContractSignaling struct {
	k *Kcp

	contract string

	session string
}

func (k *Kcp) Signaling() *ContractSignaling {
	var b [6]byte
	_, _ = rand.Read(b[:])
	return &ContractSignaling{k: k, contract: k.ContractName(), session: hex.EncodeToString(b[:])}
}

func (s *ContractSignaling) eventName(suffix string) string {
	return contractEventPrefix + s.session + "-" + suffix
}

func (s *ContractSignaling) create(ctx context.Context, suffix string, spec v1alpha1.ContractEventSpec) error {
	spec.ContractRef = commonv1.LocalObjectRef{Name: s.contract}
	spec.Direction = v1alpha1.ContractEventOutbound
	event := v1alpha1.ContractEvent{TypeMeta: typeMeta("ContractEvent"),
		ObjectMeta: metav1.ObjectMeta{Name: s.eventName(suffix)}, Spec: spec}
	body, err := kube.ApplyBody(&event, false)
	if err != nil {
		return err
	}
	_, err = kube.Create[kube.Doc](ctx, s.k.dyn.Resource(v1alpha1.ContractEventGVR), body, metav1.CreateOptions{FieldManager: FieldManager})
	return err
}

func (s *ContractSignaling) PublishKey(ctx context.Context, tag, key string) error {
	return s.create(ctx, "key", v1alpha1.ContractEventSpec{NSID: v1alpha1.ContractEventNSIDGuestPublicKey,
		Kind: market.GuestPublicKeyKindAgeX25519, Tag: tag, Key: key})
}

func (s *ContractSignaling) PublishOffer(ctx context.Context, tag, ciphertext string) error {
	return s.create(ctx, "offer", v1alpha1.ContractEventSpec{NSID: v1alpha1.ContractEventNSIDGuestSignalWebRTC,
		Kind: market.GuestSignalKindOffer, Tag: tag, Ciphertext: ciphertext})
}

func (s *ContractSignaling) PublishR2Start(ctx context.Context, start v1alpha1.ContractEventR2Start) (string, error) {
	suffix := "r2-" + start.Name
	return s.eventName(suffix), s.create(ctx, suffix, v1alpha1.ContractEventSpec{NSID: v1alpha1.ContractEventNSIDGuestR2Start, R2Start: &start})
}

func (s *ContractSignaling) PublishAgentStart(ctx context.Context) (string, error) {
	return s.eventName("agent"), s.create(ctx, "agent", v1alpha1.ContractEventSpec{NSID: v1alpha1.ContractEventNSIDGuestAgentStart})
}

func (k *Kcp) WaitContractEvent(ctx context.Context, name string, timeout time.Duration, done func(*v1alpha1.ContractEvent) bool) (*v1alpha1.ContractEvent, error) {
	return kube.Await(ctx, k.dyn.Resource(v1alpha1.ContractEventGVR), name, timeout, func(e *v1alpha1.ContractEvent) (bool, error) {
		return e != nil && done(e), nil
	})
}

func (s *ContractSignaling) inbound(e v1alpha1.ContractEvent, nsid, kind, tag string) bool {
	return e.DeletionTimestamp == nil && e.Spec.Direction == v1alpha1.ContractEventInbound && e.Spec.ContractRef.Name == s.contract &&
		e.Spec.NSID == nsid && e.Spec.Kind == kind && e.Spec.Tag == tag && e.Status.Phase == v1alpha1.ContractEventReceived
}

func (s *ContractSignaling) Guest(ctx context.Context, tag string) (webrtcproxy.Guest, error) {
	var guest webrtcproxy.Guest
	var failed error
	_, err := kube.Await(ctx, s.k.dyn.Resource(v1alpha1.ComputeContractGVR), s.contract, 0, func(c *v1alpha1.ComputeContract) (bool, error) {
		if c == nil {
			return false, nil
		}
		switch c.Status.Phase {
		case v1alpha1.ComputeContractReleased, v1alpha1.ComputeContractFailed:
			failed = fmt.Errorf("the contract is %s: %s", c.Status.Phase, c.Status.Message)
			return true, nil
		}
		if c.Status.Contract == nil {
			return false, nil
		}
		for _, n := range c.Status.Contract.Networks {
			if n.Tag == tag && n.Kind == v1alpha1.NetworkKindWebRTC && strings.TrimSpace(n.Address) != "" {
				guest.Fingerprint = n.Address
				return true, nil
			}
		}
		return false, nil
	})
	if failed != nil {
		return guest, failed
	}
	if err != nil {
		return guest, fmt.Errorf("no onNetwork kind webrtc for the tag in status.contract.networks: %w", err)
	}
	_, err = kube.AwaitList(ctx, s.k.dyn.Resource(v1alpha1.ContractEventGVR), metav1.ListOptions{}, 0, func(items []v1alpha1.ContractEvent) (bool, error) {
		var newest *v1alpha1.ContractEvent
		for i := range items {
			e := &items[i]
			if !s.inbound(*e, v1alpha1.ContractEventNSIDGuestPublicKey, market.GuestPublicKeyKindAgeX25519, tag) || e.Spec.Key == "" {
				continue
			}
			if newest == nil || newest.CreationTimestamp.Before(&e.CreationTimestamp) {
				newest = e
			}
		}
		if newest == nil {
			return false, nil
		}
		guest.Key = newest.Spec.Key
		return true, nil
	})
	if err != nil {
		return guest, fmt.Errorf("no inbound guest publicKey ContractEvent for the tag: %w", err)
	}
	return guest, nil
}

func (s *ContractSignaling) Answers(ctx context.Context, tag string, each func(ciphertext string) bool) error {
	seen := map[string]bool{}
	_, err := kube.AwaitList(ctx, s.k.dyn.Resource(v1alpha1.ContractEventGVR), metav1.ListOptions{}, 0, func(items []v1alpha1.ContractEvent) (bool, error) {
		for _, e := range items {
			key := e.Name + "/" + string(e.UID)
			if seen[key] || !s.inbound(e, v1alpha1.ContractEventNSIDGuestSignalWebRTC, market.GuestSignalKindAnswer, tag) || e.Spec.Ciphertext == "" {
				continue
			}
			seen[key] = true
			if each(e.Spec.Ciphertext) {
				return true, nil
			}
		}
		return false, nil
	})
	if errors.Is(err, kube.ErrAwaitTimeout) {
		return context.DeadlineExceeded
	}
	return err
}