-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathclient.go
More file actions
170 lines (157 loc) · 4.64 KB
/
Copy pathclient.go
File metadata and controls
170 lines (157 loc) · 4.64 KB
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
// Copyright 2024 Blink Labs Software
//
// 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 keepalive
import (
"fmt"
"sync"
"time"
"github.com/blinklabs-io/gouroboros/protocol"
)
// Client implements the keep-alive protocol client, responsible for sending keep-alive messages and handling responses.
type Client struct {
*protocol.Protocol
config *Config
callbackContext CallbackContext
timer *time.Timer
timerMutex sync.Mutex
onceStart sync.Once
}
// NewClient creates and returns a new keep-alive protocol client with the given options and configuration.
func NewClient(protoOptions protocol.ProtocolOptions, cfg *Config) *Client {
if cfg == nil {
tmpCfg := NewConfig()
cfg = &tmpCfg
}
c := &Client{
config: cfg,
}
c.callbackContext = CallbackContext{
Client: c,
ConnectionId: protoOptions.ConnectionId,
}
// Update state map with timeout
stateMap := StateMap.Copy()
if entry, ok := stateMap[StateServer]; ok {
entry.Timeout = c.config.Timeout
stateMap[StateServer] = entry
}
// Configure underlying Protocol
protoConfig := protocol.ProtocolConfig{
Name: ProtocolName,
ProtocolId: ProtocolId,
Muxer: protoOptions.Muxer,
Logger: protoOptions.Logger,
ErrorChan: protoOptions.ErrorChan,
Mode: protoOptions.Mode,
Role: protocol.ProtocolRoleClient,
MessageHandlerFunc: c.messageHandler,
MessageFromCborFunc: NewMsgFromCbor,
StateMap: stateMap,
InitialState: StateClient,
}
c.Protocol = protocol.New(protoConfig)
return c
}
// Start begins the keep-alive protocol client and starts sending keep-alive messages at the configured interval.
func (c *Client) Start() {
c.onceStart.Do(func() {
c.Protocol.Logger().
Debug("starting client protocol",
"component", "network",
"protocol", ProtocolName,
"connection_id", c.callbackContext.ConnectionId.String(),
)
c.Protocol.Start()
// Start goroutine to cleanup resources on protocol shutdown
go func() {
<-c.DoneChan()
// Stop any existing timer
c.timerMutex.Lock()
if c.timer != nil {
c.timer.Stop()
}
c.timerMutex.Unlock()
}()
c.sendKeepAlive()
})
}
// sendKeepAlive sends a keep-alive message and schedules the next one.
func (c *Client) sendKeepAlive() {
msg := NewMsgKeepAlive(c.config.Cookie)
if err := c.SendMessage(msg); err != nil {
c.SendError(err)
}
// Schedule timer
c.startTimer()
}
// startTimer starts or resets the keep-alive timer for periodic keep-alive messages.
func (c *Client) startTimer() {
c.timerMutex.Lock()
defer c.timerMutex.Unlock()
// Stop any existing timer
if c.timer != nil {
c.timer.Stop()
}
// Create new timer
c.timer = time.AfterFunc(c.config.Period, c.sendKeepAlive)
}
// Stop stops the keep-alive protocol client and cancels any pending timers.
func (c *Client) Stop() {
c.timerMutex.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.timerMutex.Unlock()
c.Protocol.Stop()
}
// messageHandler handles incoming protocol messages for the client.
func (c *Client) messageHandler(msg protocol.Message) error {
var err error
switch msg.Type() {
case MessageTypeKeepAliveResponse:
err = c.handleKeepAliveResponse(msg)
default:
err = fmt.Errorf(
"%s: received unexpected message type %d",
ProtocolName,
msg.Type(),
)
}
return err
}
// handleKeepAliveResponse processes a keep-alive response message from the server.
func (c *Client) handleKeepAliveResponse(msgGeneric protocol.Message) error {
c.Protocol.Logger().
Debug("keepalive response",
"component", "network",
"protocol", ProtocolName,
"role", "client",
"connection_id", c.callbackContext.ConnectionId.String(),
)
msg := msgGeneric.(*MsgKeepAliveResponse)
if msg.Cookie != c.config.Cookie {
return fmt.Errorf(
"%s: unexpected cookie in response, expected %d but received %d",
ProtocolName,
c.config.Cookie,
msg.Cookie,
)
}
// Call optional notification callback if provided
if c.config != nil && c.config.OnKeepAliveResponseReceived != nil {
c.config.OnKeepAliveResponseReceived(c.callbackContext.ConnectionId, msg.Cookie)
}
return nil
}