Skip to content

Commit 8c52202

Browse files
committed
tmp
1 parent 91d4cab commit 8c52202

File tree

7 files changed

+514
-0
lines changed

7 files changed

+514
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) 2025 Canonical Ltd
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License version 3 as
5+
// published by the Free Software Foundation.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU General Public License
13+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
15+
package pairingstate
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
)
21+
22+
var (
23+
// Registered pairing controller extensions.
24+
controllerExtensions = map[string]ControllerExtension{}
25+
)
26+
27+
// PairingAccessor provides access to the pairing manager functions to
28+
// control the pairing window.
29+
type PairingAccessor interface {
30+
EnablePairing() error
31+
DisablePairing() error
32+
}
33+
34+
type ControllerConfig = any
35+
36+
type ControllerExtension interface {
37+
// UnmarshalConfig unmarshals the controller configuration JSON into
38+
// a concrete backing type, and returns a pointer to the instance.
39+
UnmarshalConfig(json.RawMessage) (ControllerConfig, error)
40+
41+
// NewController creates a new pairing manager controller.
42+
NewController(PairingAccessor, ControllerConfig) (Controller, error)
43+
}
44+
45+
// RegisterPairingController registers a pairing controller extension.
46+
func RegisterPairingController(name string, ext ControllerExtension) {
47+
if _, ok := controllerExtensions[name]; ok {
48+
panic(fmt.Sprintf("internal error: controller %q already registered", name))
49+
}
50+
controllerExtensions[name] = ext
51+
}
52+
53+
// UnregisterPairingController removed a registered pairing controller extension.
54+
func UnregisterPairingController(name string) {
55+
delete(controllerExtensions, name)
56+
}
57+
58+
type PairingControllerConfigs struct {
59+
PairingControllers map[string]ControllerConfig `json:"pairing-controllers"`
60+
}
61+
62+
func (c *PairingControllerConfigs) UnmarshalJSON(data []byte) error {
63+
var rawCfg struct {
64+
PairingControllers map[string]json.RawMessage `json:"pairing-controllers"`
65+
}
66+
if err := json.Unmarshal(data, &rawCfg); err != nil {
67+
return err
68+
}
69+
70+
c.PairingControllers = make(map[string]ControllerConfig)
71+
for name, rawJSON := range rawCfg.PairingControllers {
72+
ext, ok := controllerExtensions[name]
73+
if !ok {
74+
// Ignore unsupported controller name.
75+
continue
76+
}
77+
ctrl, err := ext.UnmarshalConfig(rawJSON)
78+
if err != nil {
79+
return fmt.Errorf("cannot unmarshal pairing controller %q config: %w", name, err)
80+
}
81+
c.PairingControllers[name] = ctrl
82+
}
83+
return nil
84+
}
85+
86+
func (c *PairingControllerConfigs) CreateControllers(p PairingAccessor) ([]Controller, error) {
87+
controllers := []Controller{}
88+
for name, config := range c.PairingControllers {
89+
ext, ok := controllerExtensions[name]
90+
if !ok {
91+
// Ignore unsupported controller name.
92+
continue
93+
}
94+
controller, err := ext.NewController(p, config)
95+
if err != nil {
96+
return nil, err
97+
}
98+
controllers = append(controllers, controller)
99+
}
100+
return controllers, nil
101+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) 2025 Canonical Ltd
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License version 3 as
5+
// published by the Free Software Foundation.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU General Public License
13+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
15+
package pairingstate
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"sync"
21+
22+
"github.com/canonical/pebble/internals/overlord/state"
23+
)
24+
25+
// Controller defines the available interface the pairing manager
26+
// has to each controller.
27+
type Controller interface {
28+
// PairingDisabled is called by the pairing manager when the pairing
29+
// window is disabled. This may happen before the controller expires the
30+
// window, e.g. when an incoming pairing request completes.
31+
PairingDisabled()
32+
}
33+
34+
type pairingMode string
35+
36+
const (
37+
pairingModeDisabled pairingMode = "disabled"
38+
pairingModeSingle pairingMode = "single"
39+
pairingModeMultiple pairingMode = "multiple"
40+
)
41+
42+
type PairingManager struct {
43+
state *state.State
44+
pairingMode pairingMode
45+
46+
// The selected controller (or nil for no controller). The pairing manager
47+
// only supports a single pairing controller for now.
48+
controller Controller
49+
50+
mu sync.Mutex
51+
// pairingWindow reflect the current state of the pairing window.
52+
pairingWindowOpen bool
53+
// isPaired is true if any pairing request has succeeded in the past. This
54+
// state must be persisted.
55+
isPaired bool
56+
}
57+
58+
type Config struct {
59+
PairingMode pairingMode `json:"pairing-mode"`
60+
61+
// Inlined JSON with custom unmarshaller.
62+
*PairingControllerConfigs
63+
}
64+
65+
func NewPairingManager(state *state.State, config *Config) (*PairingManager, error) {
66+
m := &PairingManager{
67+
state: state,
68+
pairingMode: config.PairingMode,
69+
}
70+
// Create the controller specified in the configuration.
71+
controllers, err := config.PairingControllerConfigs.CreateControllers(m)
72+
if err != nil {
73+
return nil, err
74+
}
75+
// We only support a single controller right now.
76+
if len(controllers) > 1 {
77+
return nil, fmt.Errorf("cannot enable multiple pairing controllers: only supports a single controller at a time")
78+
}
79+
if len(controllers) == 1 {
80+
m.controller = controllers[0]
81+
}
82+
return m, nil
83+
}
84+
85+
func (m *PairingManager) EnablePairing() error {
86+
m.mu.Lock()
87+
defer m.mu.Unlock()
88+
89+
// Pairing window already open.
90+
if m.pairingWindowOpen {
91+
return errors.New("cannot enable pairing: already open")
92+
}
93+
94+
// Pairing conditions which disallows pairing.
95+
switch m.pairingMode {
96+
case pairingModeDisabled:
97+
return errors.New("cannot enable pairing: pairing disabled")
98+
case pairingModeSingle:
99+
if m.isPaired {
100+
return errors.New("cannot enable pairing: device already paired in single pairing mode")
101+
}
102+
case pairingModeMultiple:
103+
default:
104+
return fmt.Errorf("cannot enable pairing: unknown pairing mode %q", m.pairingMode)
105+
}
106+
107+
m.pairingWindowOpen = true
108+
return nil
109+
}
110+
111+
func (m *PairingManager) DisablePairing() error {
112+
m.mu.Lock()
113+
defer m.mu.Unlock()
114+
115+
m.pairingWindowOpen = false
116+
return nil
117+
}
118+
119+
// Ensure implements StateManager.Ensure.
120+
func (m *PairingManager) Ensure() error {
121+
return nil
122+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2025 Canonical Ltd
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License version 3 as
5+
// published by the Free Software Foundation.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU General Public License
13+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
15+
package pairingstate_test
16+
17+
import (
18+
"encoding/json"
19+
"time"
20+
21+
. "gopkg.in/check.v1"
22+
23+
"github.com/canonical/pebble/internals/overlord/pairingstate"
24+
)
25+
26+
func (ps *pairingSuite) TestManagerFromJSON(c *C) {
27+
pairingstate.RegisterPairingController(pairingstate.PowerOnControllerName, &pairingstate.PowerOnControllerExtension{})
28+
defer pairingstate.UnregisterPairingController(pairingstate.PowerOnControllerName)
29+
30+
configJSON := []byte(`
31+
{
32+
"pairing-mode": "single",
33+
"pairing-controllers": {
34+
"poweron": {
35+
"duration": "60s"
36+
}
37+
}
38+
}
39+
`[1:])
40+
41+
config := &pairingstate.Config{
42+
PairingControllerConfigs: &pairingstate.PairingControllerConfigs{},
43+
}
44+
err := json.Unmarshal(configJSON, &config)
45+
c.Assert(err, IsNil)
46+
47+
manager, err := pairingstate.NewPairingManager(nil, config)
48+
c.Assert(err, IsNil)
49+
50+
err = manager.Ensure()
51+
c.Assert(err, IsNil)
52+
}
53+
54+
func (ps *pairingSuite) TestManagerFromConfig(c *C) {
55+
pairingstate.RegisterPairingController(pairingstate.PowerOnControllerName, &pairingstate.PowerOnControllerExtension{})
56+
defer pairingstate.UnregisterPairingController(pairingstate.PowerOnControllerName)
57+
58+
config := &pairingstate.Config{
59+
PairingControllerConfigs: &pairingstate.PairingControllerConfigs{
60+
PairingControllers: map[string]pairingstate.ControllerConfig{
61+
pairingstate.PowerOnControllerName: &pairingstate.PowerOnControllerConfig{
62+
Duration: pairingstate.StringDuration(30 * time.Second),
63+
},
64+
},
65+
},
66+
}
67+
68+
manager, err := pairingstate.NewPairingManager(nil, config)
69+
c.Assert(err, IsNil)
70+
71+
err = manager.Ensure()
72+
c.Assert(err, IsNil)
73+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2025 Canonical Ltd
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License version 3 as
5+
// published by the Free Software Foundation.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU General Public License
13+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
15+
package pairingstate
16+
17+
import (
18+
_ "unsafe"
19+
)
20+
21+
// GetLinuxKernelRuntime returns the nanosecond duration since the start
22+
// of the Linux kernel. This function uses CLOCK_MONOTONIC under the hood
23+
// but binds on top of functionality already in the Golang runtime, but
24+
// not publically exposed.
25+
//
26+
// This function is not a replacement for the time package, which already
27+
// provides duration based on CLOCK_MONOTONIC. This is exclusively
28+
// intended for cases where time elapse since the Linux kernel start
29+
// is required, specifically for benchmarking.
30+
//
31+
//go:noescape
32+
//go:linkname GetLinuxKernelRuntime runtime.nanotime
33+
func GetLinuxKernelRuntime() int64
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (C) 2025 Canonical Ltd
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License version 3 as
5+
// published by the Free Software Foundation.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU General Public License
13+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
15+
package pairingstate_test
16+
17+
import (
18+
"testing"
19+
20+
. "gopkg.in/check.v1"
21+
)
22+
23+
// Hook up check.v1 into the "go test" runner.
24+
func Test(t *testing.T) { TestingT(t) }
25+
26+
type pairingSuite struct{}
27+
28+
var _ = Suite(&pairingSuite{})

0 commit comments

Comments
 (0)