/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

// Package nodelock handles locking operations on the node.
package nodelock

import (
	"io"
	"sync/atomic"

	"github.com/edgelesssys/constellation/v2/internal/attestation/initialize"
	"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
)

// Lock locks the node once there the join or the init is at a point
// where there is no turning back and the other operation does not need
// to continue.
//
// This can be viewed as a state machine with two states: unlocked and locked.
// There is no way to unlock, so the state changes only once from unlock to
// locked.
type Lock struct {
	tpm    vtpm.TPMOpenFunc
	marker markAsBootstrapped
	inner  atomic.Bool
}

// New creates a new NodeLock, which is unlocked.
func New(tpm vtpm.TPMOpenFunc) *Lock {
	return &Lock{
		tpm:    tpm,
		marker: initialize.MarkNodeAsBootstrapped,
	}
}

// TryLockOnce tries to lock the node. If the node is already locked, it
// returns false. If the node is unlocked, it locks it and returns true.
func (l *Lock) TryLockOnce(clusterID []byte) (bool, error) {
	// CompareAndSwap first checks if the node is currently unlocked.
	// If it was already locked, it returns early.
	// If it is unlocked, it swaps the value to locked atomically and continues.
	if !l.inner.CompareAndSwap(unlocked, locked) {
		return false, nil
	}

	return true, l.marker(l.tpm, clusterID)
}

// markAsBootstrapped is a function that marks the node as bootstrapped in the TPM.
type markAsBootstrapped func(openDevice func() (io.ReadWriteCloser, error), clusterID []byte) error

const (
	unlocked = false
	locked   = true
)