6
6
"fmt"
7
7
"math"
8
8
"net"
9
+ "strconv"
9
10
"sync"
10
11
"time"
11
12
@@ -127,6 +128,58 @@ func (m *midElement) GetMessage(cc *Conn) (*pool.Message, bool, error) {
127
128
return msg , true , nil
128
129
}
129
130
131
+ // MessageCache is a cache of CoAP messages.
132
+ type MessageCache interface {
133
+ Load (key string , msg * pool.Message ) (bool , error )
134
+ Store (key string , msg * pool.Message ) error
135
+ CheckExpirations (time.Time )
136
+ }
137
+
138
+ // messageCache is a CoAP message cache backed by an in-memory cache.
139
+ type messageCache struct {
140
+ c * cache.Cache [string , []byte ]
141
+ }
142
+
143
+ // newMessageCache constructs a new CoAP message cache.
144
+ func newMessageCache () * messageCache {
145
+ return & messageCache {
146
+ c : cache .NewCache [string , []byte ](),
147
+ }
148
+ }
149
+
150
+ // Load loads a message from the cache if one exists with key.
151
+ func (m * messageCache ) Load (key string , msg * pool.Message ) (bool , error ) {
152
+ cachedResp := m .c .Load (key )
153
+ if cachedResp == nil {
154
+ return false , nil
155
+ }
156
+ if rawMsg := cachedResp .Data (); len (rawMsg ) > 0 {
157
+ _ , err := msg .UnmarshalWithDecoder (coder .DefaultCoder , rawMsg )
158
+ if err != nil {
159
+ return false , err
160
+ }
161
+ return true , nil
162
+ }
163
+ return false , nil
164
+ }
165
+
166
+ // Store stores a message in the cache.
167
+ func (m * messageCache ) Store (key string , msg * pool.Message ) error {
168
+ marshaledResp , err := msg .MarshalWithEncoder (coder .DefaultCoder )
169
+ if err != nil {
170
+ return err
171
+ }
172
+ cacheMsg := make ([]byte , len (marshaledResp ))
173
+ copy (cacheMsg , marshaledResp )
174
+ m .c .LoadOrStore (key , cache .NewElement (cacheMsg , time .Now ().Add (ExchangeLifetime ), nil ))
175
+ return nil
176
+ }
177
+
178
+ // CheckExpirations checks the cache for any expirations.
179
+ func (m * messageCache ) CheckExpirations (now time.Time ) {
180
+ m .c .CheckExpirations (now )
181
+ }
182
+
130
183
// Conn represents a virtual connection to a conceptual endpoint, to perform COAPs commands.
131
184
type Conn struct {
132
185
// This field needs to be the first in the struct to ensure proper word alignment on 32-bit platforms.
@@ -145,7 +198,7 @@ type Conn struct {
145
198
146
199
processReceivedMessage config.ProcessReceivedMessageFunc [* Conn ]
147
200
errors ErrorFunc
148
- responseMsgCache * cache. Cache [ string , [] byte ]
201
+ responseMsgCache MessageCache
149
202
msgIDMutex * MutexMap
150
203
151
204
tokenHandlerContainer * coapSync.Map [uint64 , HandlerFunc ]
@@ -192,6 +245,7 @@ type ConnOptions struct {
192
245
createBlockWise func (cc * Conn ) * blockwise.BlockWise [* Conn ]
193
246
inactivityMonitor InactivityMonitor
194
247
requestMonitor RequestMonitorFunc
248
+ responseMsgCache MessageCache
195
249
}
196
250
197
251
type Option = func (opts * ConnOptions )
@@ -220,6 +274,23 @@ func WithRequestMonitor(requestMonitor RequestMonitorFunc) Option {
220
274
}
221
275
}
222
276
277
+ // WithResponseMessageCache sets the cache used for response messages. All
278
+ // response messages are submitted to the cache, but it is up to the cache
279
+ // implementation to determine which messages are stored and for how long.
280
+ // Caching responses enables sending the same Acknowledgment for retransmitted
281
+ // confirmable messages within an EXCHANGE_LIFETIME. It may be desirable to
282
+ // relax this behavior in some scenarios.
283
+ // https://datatracker.ietf.org/doc/html/rfc7252#section-4.5
284
+ // The default response message cache stores all responses with an expiration of
285
+ // 247 seconds, which is EXCHANGE_LIFETIME when using default CoAP transmission
286
+ // parameters.
287
+ // https://datatracker.ietf.org/doc/html/rfc7252#section-4.8.2
288
+ func WithResponseMessageCache (cache MessageCache ) Option {
289
+ return func (opts * ConnOptions ) {
290
+ opts .responseMsgCache = cache
291
+ }
292
+ }
293
+
223
294
func NewConnWithOpts (session Session , cfg * Config , opts ... Option ) * Conn {
224
295
if cfg .Errors == nil {
225
296
cfg .Errors = func (error ) {
@@ -248,6 +319,10 @@ func NewConnWithOpts(session Session, cfg *Config, opts ...Option) *Conn {
248
319
for _ , o := range opts {
249
320
o (& cfgOpts )
250
321
}
322
+ // Only construct cache if one was not set via options.
323
+ if cfgOpts .responseMsgCache == nil {
324
+ cfgOpts .responseMsgCache = newMessageCache ()
325
+ }
251
326
cc := Conn {
252
327
session : session ,
253
328
transmission : & Transmission {
@@ -262,7 +337,7 @@ func NewConnWithOpts(session Session, cfg *Config, opts ...Option) *Conn {
262
337
processReceivedMessage : cfg .ProcessReceivedMessage ,
263
338
errors : cfg .Errors ,
264
339
msgIDMutex : NewMutexMap (),
265
- responseMsgCache : cache . NewCache [ string , [] byte ]() ,
340
+ responseMsgCache : cfgOpts . responseMsgCache ,
266
341
inactivityMonitor : cfgOpts .inactivityMonitor ,
267
342
requestMonitor : cfgOpts .requestMonitor ,
268
343
messagePool : cfg .MessagePool ,
@@ -609,34 +684,14 @@ func (cc *Conn) Sequence() uint64 {
609
684
return cc .sequence .Add (1 )
610
685
}
611
686
612
- func (cc * Conn ) responseMsgCacheID (msgID int32 ) string {
613
- return fmt .Sprintf ("resp-%v-%d" , cc .RemoteAddr (), msgID )
687
+ // getResponseFromCache gets a message from the response message cache.
688
+ func (cc * Conn ) getResponseFromCache (mid int32 , resp * pool.Message ) (bool , error ) {
689
+ return cc .responseMsgCache .Load (strconv .Itoa (int (mid )), resp )
614
690
}
615
691
692
+ // addResponseToCache adds a message to the response message cache.
616
693
func (cc * Conn ) addResponseToCache (resp * pool.Message ) error {
617
- marshaledResp , err := resp .MarshalWithEncoder (coder .DefaultCoder )
618
- if err != nil {
619
- return err
620
- }
621
- cacheMsg := make ([]byte , len (marshaledResp ))
622
- copy (cacheMsg , marshaledResp )
623
- cc .responseMsgCache .LoadOrStore (cc .responseMsgCacheID (resp .MessageID ()), cache .NewElement (cacheMsg , time .Now ().Add (ExchangeLifetime ), nil ))
624
- return nil
625
- }
626
-
627
- func (cc * Conn ) getResponseFromCache (mid int32 , resp * pool.Message ) (bool , error ) {
628
- cachedResp := cc .responseMsgCache .Load (cc .responseMsgCacheID (mid ))
629
- if cachedResp == nil {
630
- return false , nil
631
- }
632
- if rawMsg := cachedResp .Data (); len (rawMsg ) > 0 {
633
- _ , err := resp .UnmarshalWithDecoder (coder .DefaultCoder , rawMsg )
634
- if err != nil {
635
- return false , err
636
- }
637
- return true , nil
638
- }
639
- return false , nil
694
+ return cc .responseMsgCache .Store (strconv .Itoa (int (resp .MessageID ())), resp )
640
695
}
641
696
642
697
// checkMyMessageID compare client msgID against peer messageID and if it is near < 0xffff/4 then increase msgID.
0 commit comments