-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathto2_module.go
171 lines (155 loc) · 5.85 KB
/
to2_module.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// SPDX-FileCopyrightText: (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache 2.0
package fdo
import (
"context"
"fmt"
"io"
"strings"
"github.com/fido-device-onboard/go-fdo/cbor"
"github.com/fido-device-onboard/go-fdo/serviceinfo"
)
// Buffer service info send and receive queues. This buffer may grow
// indefinitely if owner modules are not well behaved. For example, if an
// owner service sends 100s of upload requests, the requests will be processed
// as they are received and may fill up the send buffer until the device is out
// of memory.
//
// While in this case, it may seem obvious that the upload requests should be
// buffered and then processed rather than handled sequentially, it would be
// equally unsafe to implement this behavior, because the request buffer may
// also grow past the MTU if IsMoreServiceInfo is used, keeping the device from
// processing its received service info.
//
// In the end, there's no general way to exchange arbitrary data between two
// parties where each piece of data one party receives may cause it to put any
// number of pieces of data on its send queue and the other party gets to
// choose when it may flush its queue.
//
// Buffering both queues and relying on good behavior of owner modules is the
// best and only real option. Both queues should be buffered because there can
// be an asymmetric use of queues in either direction. Many file upload
// requests results in a small device receive queue and large device send
// queue. Many file downloads result in the opposite.
func handleOwnerModuleMessages(ctx context.Context, prevModuleName string, modules deviceModuleMap, ownerInfo *serviceinfo.UnchunkReader, send *serviceinfo.UnchunkWriter) string {
for {
// Get next service info from the owner service and handle it.
key, messageBody, ok := ownerInfo.NextServiceInfo()
if !ok {
if mod, active := modules.Lookup(prevModuleName); active {
if err := handleOwnerModuleYield(ctx, mod, prevModuleName, send); err != nil {
_ = send.CloseWithError(err)
return prevModuleName
}
}
_ = send.Close()
return prevModuleName
}
moduleName, messageName, _ := strings.Cut(key, ":")
prevModuleName = moduleName
// Automatically receive and respond to active messages. This send is
// expected to be buffered until all receives are processed, unlike
// modules which must wait for all receives to occur before sending.
// This is allowed because the data is small and the send buffer is
// large enough for many more "active" responses than is practical to
// expect in the real world.
mod, active := modules.Lookup(moduleName)
if messageName == "active" {
newActive, err := handleActive(active, mod, moduleName, messageBody, send)
if err != nil {
_ = send.CloseWithError(err)
return prevModuleName
}
modules.active[moduleName] = newActive
continue
}
if !active {
_ = send.CloseWithError(fmt.Errorf("device has not activated module %q", moduleName))
return prevModuleName
}
// Call device module and provide it a function which can be used to
// send zero or more service info KVs. The function returns a writer to
// write the value part of the service info KV.
//
// If the device module returns an error then the pipe will be closed
// with an error, causing the error to propagate to the chunk reader,
// which is used in the ServiceInfo send loop.
if err := handleOwnerModuleMessage(ctx, mod, moduleName, messageName, messageBody, send); err != nil {
_ = send.CloseWithError(err)
return prevModuleName
}
}
}
func handleActive(prevActive bool, mod serviceinfo.DeviceModule, moduleName string, messageBody io.Reader, send *serviceinfo.UnchunkWriter) (bool, error) {
// Receive active message of true or false
var active bool
if err := cbor.NewDecoder(messageBody).Decode(&active); err != nil {
return false, err
}
// Check err after getting sender
_, _ = io.Copy(io.Discard, messageBody)
// Transition internal state
if active != prevActive {
if err := mod.Transition(active); err != nil {
return false, err
}
}
// Send active message when appropriate
if !active || prevActive {
return active, nil
}
if _, isUnknown := mod.(serviceinfo.UnknownModule); isUnknown && moduleName != "devmod" {
active = false
}
if err := send.NextServiceInfo(moduleName, "active"); err != nil {
return false, err
}
if err := cbor.NewEncoder(send).Encode(active); err != nil {
return false, err
}
return active, nil
}
func handleOwnerModuleYield(ctx context.Context, mod serviceinfo.DeviceModule, moduleName string, send *serviceinfo.UnchunkWriter) error {
respond := func(messageName string) io.Writer {
_ = send.NextServiceInfo(moduleName, messageName)
return send
}
yield := func() {
_ = send.ForceNewMessage()
}
return mod.Yield(ctx, respond, yield)
}
func handleOwnerModuleMessage(ctx context.Context, mod serviceinfo.DeviceModule, moduleName, messageName string, messageBody io.Reader, send *serviceinfo.UnchunkWriter) error {
// Construct respond/yield callback functions
respond := func(messageName string) io.Writer {
_ = send.NextServiceInfo(moduleName, messageName)
return send
}
yield := func() {
_ = send.ForceNewMessage()
}
// Handle message
if err := mod.Receive(ctx, messageName, messageBody, respond, yield); err != nil {
return err
}
// Ensure that buffer was drained
if n, err := io.Copy(io.Discard, messageBody); err != nil {
return err
} else if n > 0 {
return fmt.Errorf(
"device module did not read full body of message '%s:%s'",
moduleName, messageName)
}
return nil
}
type deviceModuleMap struct {
modules map[string]serviceinfo.DeviceModule
active map[string]bool
}
func (fm deviceModuleMap) Lookup(moduleName string) (mod serviceinfo.DeviceModule, active bool) {
module, known := fm.modules[moduleName]
if !known {
module = serviceinfo.UnknownModule{}
}
return module, fm.active[moduleName]
}