/* * * Copyright 2020 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package certprovider import ( "context" "sync" "google.golang.org/grpc/internal/grpcsync" ) // Distributor makes it easy for provider implementations to furnish new key // materials by handling synchronization between the producer and consumers of // the key material. // // Provider implementations which choose to use a Distributor should do the // following: // - create a new Distributor using the NewDistributor() function. // - invoke the Set() method whenever they have new key material or errors to // report. // - delegate to the distributor when handing calls to KeyMaterial(). // - invoke the Stop() method when they are done using the distributor. type Distributor struct { // mu protects the underlying key material. mu sync.Mutex km *KeyMaterial pErr error // ready channel to unblock KeyMaterial() invocations blocked on // availability of key material. ready *grpcsync.Event // done channel to notify provider implementations and unblock any // KeyMaterial() calls, once the Distributor is closed. closed *grpcsync.Event } // NewDistributor returns a new Distributor. func NewDistributor() *Distributor { return &Distributor{ ready: grpcsync.NewEvent(), closed: grpcsync.NewEvent(), } } // Set updates the key material in the distributor with km. // // Provider implementations which use the distributor must not modify the // contents of the KeyMaterial struct pointed to by km. // // A non-nil err value indicates the error that the provider implementation ran // into when trying to fetch key material, and makes it possible to surface the // error to the user. A non-nil error value passed here causes distributor's // KeyMaterial() method to return nil key material. func (d *Distributor) Set(km *KeyMaterial, err error) { d.mu.Lock() d.km = km d.pErr = err if err != nil { // If a non-nil err is passed, we ignore the key material being passed. d.km = nil } d.ready.Fire() d.mu.Unlock() } // KeyMaterial returns the most recent key material provided to the Distributor. // If no key material was provided at the time of this call, it will block until // the deadline on the context expires or fresh key material arrives. func (d *Distributor) KeyMaterial(ctx context.Context) (*KeyMaterial, error) { if d.closed.HasFired() { return nil, errProviderClosed } if d.ready.HasFired() { return d.keyMaterial() } select { case <-ctx.Done(): return nil, ctx.Err() case <-d.closed.Done(): return nil, errProviderClosed case <-d.ready.Done(): return d.keyMaterial() } } func (d *Distributor) keyMaterial() (*KeyMaterial, error) { d.mu.Lock() defer d.mu.Unlock() return d.km, d.pErr } // Stop turns down the distributor, releases allocated resources and fails any // active KeyMaterial() call waiting for new key material. func (d *Distributor) Stop() { d.closed.Fire() }