package libp2pwebtransport

import (
	
	
	
	
	
	
	

	
	ic 
	ma 
	
)

// Allow for a bit of clock skew.
// When we generate a certificate, the NotBefore time is set to clockSkewAllowance before the current time.
// Similarly, we stop using a certificate one clockSkewAllowance before its expiry time.
const clockSkewAllowance = time.Hour
const validityMinusTwoSkew = certValidity - (2 * clockSkewAllowance)

type certConfig struct {
	tlsConf *tls.Config
	sha256  [32]byte // cached from the tlsConf
}

func ( *certConfig) () time.Time { return .tlsConf.Certificates[0].Leaf.NotBefore }
func ( *certConfig) () time.Time   { return .tlsConf.Certificates[0].Leaf.NotAfter }

func newCertConfig( ic.PrivKey, ,  time.Time) (*certConfig, error) {
	,  := getTLSConf(, , )
	if  != nil {
		return nil, 
	}
	return &certConfig{
		tlsConf: ,
		sha256:  sha256.Sum256(.Certificates[0].Leaf.Raw),
	}, nil
}

// Certificate renewal logic:
//  1. On startup, we generate one cert that is valid from now (-1h, to allow for clock skew), and another
//     cert that is valid from the expiry date of the first certificate (again, with allowance for clock skew).
//  2. Once we reach 1h before expiry of the first certificate, we switch over to the second certificate.
//     At the same time, we stop advertising the certhash of the first cert and generate the next cert.
type certManager struct {
	clock     clock.Clock
	ctx       context.Context
	ctxCancel context.CancelFunc
	refCount  sync.WaitGroup

	mx            sync.RWMutex
	lastConfig    *certConfig // initially nil
	currentConfig *certConfig
	nextConfig    *certConfig // nil until we have passed half the certValidity of the current config
	addrComp      ma.Multiaddr

	serializedCertHashes [][]byte
}

func newCertManager( ic.PrivKey,  clock.Clock) (*certManager, error) {
	 := &certManager{clock: }
	.ctx, .ctxCancel = context.WithCancel(context.Background())
	if  := .init();  != nil {
		return nil, 
	}

	.background()
	return , nil
}

// getCurrentBucketStartTime returns the canonical start time of the given time as
// bucketed by ranges of certValidity since unix epoch (plus an offset). This
// lets you get the same time ranges across reboots without having to persist
// state.
// ```
// ... v--- epoch + offset
// ... |--------|    |--------|        ...
// ...        |--------|    |--------| ...
// ```
func getCurrentBucketStartTime( time.Time,  time.Duration) time.Time {
	 := (.UnixMilli() - .Milliseconds()) / validityMinusTwoSkew.Milliseconds()
	return time.UnixMilli(.Milliseconds() + *validityMinusTwoSkew.Milliseconds())
}

func ( *certManager) ( ic.PrivKey) error {
	 := .clock.Now()
	,  := .GetPublic().Raw()
	if  != nil {
		return 
	}

	// We want to add a random offset to each start time so that not all certs
	// rotate at the same time across the network. The offset represents moving
	// the bucket start time some `offset` earlier.
	 := (time.Duration(binary.LittleEndian.Uint16()) * time.Minute) % certValidity

	// We want the certificate have been valid for at least one clockSkewAllowance
	 = .Add(-clockSkewAllowance)
	 := getCurrentBucketStartTime(, )
	.nextConfig,  = newCertConfig(, , .Add(certValidity))
	if  != nil {
		return 
	}
	return .rollConfig()
}

func ( *certManager) ( ic.PrivKey) error {
	// We stop using the current certificate clockSkewAllowance before its expiry time.
	// At this point, the next certificate needs to be valid for one clockSkewAllowance.
	 := .nextConfig.End().Add(-2 * clockSkewAllowance)
	,  := newCertConfig(, , .Add(certValidity))
	if  != nil {
		return 
	}
	.lastConfig = .currentConfig
	.currentConfig = .nextConfig
	.nextConfig = 
	if  := .cacheSerializedCertHashes();  != nil {
		return 
	}
	return .cacheAddrComponent()
}

func ( *certManager) ( ic.PrivKey) {
	 := .currentConfig.End().Add(-clockSkewAllowance).Sub(.clock.Now())
	log.Debugw("setting timer", "duration", .String())
	 := .clock.Timer()
	.refCount.Add(1)

	go func() {
		defer .refCount.Done()
		defer .Stop()

		for {
			select {
			case <-.ctx.Done():
				return
			case <-.C:
				 := .clock.Now()
				.mx.Lock()
				if  := .rollConfig();  != nil {
					log.Errorw("rolling config failed", "error", )
				}
				 := .currentConfig.End().Add(-clockSkewAllowance).Sub()
				log.Debugw("rolling certificates", "next", .String())
				.Reset()
				.mx.Unlock()
			}
		}
	}()
}

func ( *certManager) () *tls.Config {
	.mx.RLock()
	defer .mx.RUnlock()
	return .currentConfig.tlsConf
}

func ( *certManager) () ma.Multiaddr {
	.mx.RLock()
	defer .mx.RUnlock()
	return .addrComp
}

func ( *certManager) () [][]byte {
	return .serializedCertHashes
}

func ( *certManager) () error {
	 := make([][32]byte, 0, 3)
	if .lastConfig != nil {
		 = append(, .lastConfig.sha256)
	}
	 = append(, .currentConfig.sha256)
	if .nextConfig != nil {
		 = append(, .nextConfig.sha256)
	}

	.serializedCertHashes = .serializedCertHashes[:0]
	for ,  := range  {
		,  := multihash.Encode([:], multihash.SHA2_256)
		if  != nil {
			return fmt.Errorf("failed to encode certificate hash: %w", )
		}
		.serializedCertHashes = append(.serializedCertHashes, )
	}
	return nil
}

func ( *certManager) () error {
	var  ma.Multiaddr
	,  := addrComponentForCert(.currentConfig.sha256[:])
	if  != nil {
		return 
	}
	 = .AppendComponent()
	if .nextConfig != nil {
		,  := addrComponentForCert(.nextConfig.sha256[:])
		if  != nil {
			return 
		}
		 = .AppendComponent()
	}
	.addrComp = 
	return nil
}

func ( *certManager) () error {
	.ctxCancel()
	.refCount.Wait()
	return nil
}