1
1
package device
2
2
3
3
import (
4
- "encoding/json"
4
+ "sync"
5
+ "sync/atomic"
5
6
6
7
"github.com/segmentio/ksuid"
7
8
"github.com/spf13/cast"
@@ -28,107 +29,101 @@ func init() {
28
29
}
29
30
30
31
// Metadata contains information such as security credentials
31
- // related to a device.
32
- type Metadata map [string ]interface {}
33
-
34
- // JWTClaims returns the JWT claims attached to a device. If no claims exist,
35
- // they are initialized appropiately.
36
- func (m Metadata ) JWTClaims () JWTClaims { // returns the type and such type has getter/setter
37
- if jwtClaims , ok := m [JWTClaimsKey ].(JWTClaims ); ok {
38
- return deepCopyMap (jwtClaims )
39
- }
40
- return deepCopyMap (m .initJWTClaims ())
41
- }
42
-
43
- // SetJWTClaims sets the JWT claims attached to a device.
44
- func (m Metadata ) SetJWTClaims (jwtClaims JWTClaims ) {
45
- m [JWTClaimsKey ] = jwtClaims
32
+ // related to a device. Read operations are optimized with a
33
+ // copy-on-write strategy. Client code must further synchronize concurrent
34
+ // writers to avoid stale data.
35
+ // Metadata uses an atomic.Value internally and thus it should not be copied
36
+ // after creation.
37
+ type Metadata struct {
38
+ v atomic.Value
39
+ once sync.Once
46
40
}
47
41
48
42
// SessionID returns the UUID associated with a device's current connection
49
- // to the cluster.
50
- func (m Metadata ) SessionID () string {
51
- if sessionID , ok := m [SessionIDKey ].(string ); ok {
52
- return sessionID
53
- }
54
-
55
- return m .initSessionID ()
56
- }
57
-
58
- func (m Metadata ) initSessionID () string {
59
- sessionID := ksuid .New ().String ()
60
- m [SessionIDKey ] = sessionID
61
- return sessionID
43
+ // to the cluster if one has been set. The zero value is returned as default.
44
+ func (m * Metadata ) SessionID () (sessionID string ) {
45
+ sessionID , _ = m .loadData ()[SessionIDKey ].(string )
46
+ return
62
47
}
63
48
64
- func (m Metadata ) initJWTClaims () JWTClaims {
65
- jwtClaims := JWTClaims (make (map [string ]interface {}))
66
- m .SetJWTClaims (jwtClaims )
67
- return jwtClaims
49
+ // SetSessionID sets the UUID associated the device's current connection to the cluster.
50
+ // It uses sync.Once to ensure the sessionID is unchanged through the metadata's lifecycle.
51
+ func (m * Metadata ) SetSessionID (sessionID string ) {
52
+ m .once .Do (func () {
53
+ m .copyAndStore (SessionIDKey , sessionID )
54
+ })
68
55
}
69
56
70
- // Load allows retrieving values from a device's metadata
71
- func (m Metadata ) Load (key string ) interface {} {
72
- return m [key ]
57
+ // Load returns the value associated with the given key in the metadata map.
58
+ // It is not recommended modifying values returned by reference.
59
+ func (m * Metadata ) Load (key string ) interface {} {
60
+ return m .loadData ()[key ]
73
61
}
74
62
75
- // Store allows writing values into the device's metadata given
76
- // a key. Boolean results indicates whether the operation was successful.
77
- // Note: operations will fail for reserved keys.
78
- func (m Metadata ) Store (key string , value interface {}) bool {
63
+ // Store updates the key value mapping in the device metadata map.
64
+ // A boolean result is given indicating whether the operation was successful.
65
+ // Operations will fail for reserved keys.
66
+ // To avoid updating keys with stale data/value, client code will need to
67
+ // synchronize the entire transaction of reading, copying, modifying and
68
+ // writing back the value.
69
+ func (m * Metadata ) Store (key string , value interface {}) bool {
79
70
if reservedMetadataKeys [key ] {
80
71
return false
81
72
}
82
- m [ key ] = value
73
+ m . copyAndStore ( key , value )
83
74
return true
84
75
}
85
76
86
- // NewDeviceMetadata returns a metadata object ready for use.
87
- func NewDeviceMetadata () Metadata {
88
- return NewDeviceMetadataWithClaims (make (map [string ]interface {}))
77
+ // SetClaims updates the claims associated with the device that's
78
+ // owner of the metadata.
79
+ // To avoid updating the claims with stale data, client code will need to
80
+ // synchronize the entire transaction of reading, copying, modifying and
81
+ // writing back the value.
82
+ func (m * Metadata ) SetClaims (claims map [string ]interface {}) {
83
+ m .copyAndStore (JWTClaimsKey , deepCopyMap (claims ))
89
84
}
90
85
91
- // NewDeviceMetadataWithClaims returns a metadata object ready for use with the
92
- // given claims.
93
- func NewDeviceMetadataWithClaims (claims map [string ]interface {}) Metadata {
94
- m := make (Metadata )
95
- m .SetJWTClaims (deepCopyMap (claims ))
96
- m .initSessionID ()
97
- return m
86
+ // Claims returns the claims attached to a device. The returned map
87
+ // should not be modified to avoid any race conditions. To update the claims,
88
+ // take a look at the ClaimsCopy() function
89
+ func (m * Metadata ) Claims () (claims map [string ]interface {}) {
90
+ claims , _ = m .loadData ()[JWTClaimsKey ].(map [string ]interface {})
91
+ return
98
92
}
99
93
100
- // JWTClaims defines the interface of a device's security claims.
101
- // One current use case is providing security credentials the device
102
- // presented at registration time.
103
- type JWTClaims map [string ]interface {}
94
+ // ClaimsCopy returns a deep copy of the claims. Use this, along with the
95
+ // SetClaims() method to update the claims.
96
+ func (m * Metadata ) ClaimsCopy () map [string ]interface {} {
97
+ return deepCopyMap (m .Claims ())
98
+ }
104
99
105
- // Trust returns the device's trust level claim
100
+ // TrustClaim returns the device's trust level claim.
106
101
// By Default, a device is untrusted (trust = 0).
107
- func (c JWTClaims ) Trust () int {
108
- if trust , ok := c [TrustClaimKey ].(int ); ok {
109
- return trust
110
- }
111
- return 0
102
+ func (m * Metadata ) TrustClaim () (trust int ) {
103
+ trust , _ = m .Claims ()[TrustClaimKey ].(int )
104
+ return
112
105
}
113
106
114
- // PartnerID returns the partner ID claim.
107
+ // PartnerIDClaim returns the partner ID claim.
115
108
// If no claim is found, the zero value is returned.
116
- func (c JWTClaims ) PartnerID () string {
117
- if partnerID , ok := c [PartnerIDClaimKey ].(string ); ok {
118
- return partnerID
119
- }
120
- return "" // no partner by default
109
+ func (m * Metadata ) PartnerIDClaim () (partnerID string ) {
110
+ partnerID , _ = m .Claims ()[PartnerIDClaimKey ].(string )
111
+ return
112
+ }
113
+
114
+ func (m * Metadata ) loadData () (data map [string ]interface {}) {
115
+ data , _ = m .v .Load ().(map [string ]interface {})
116
+ return
121
117
}
122
118
123
- // SetTrust modifies the trust level of the device which owns these
124
- // claims.
125
- func (c JWTClaims ) SetTrust (trust int ) {
126
- c [TrustClaimKey ] = trust
119
+ func (m * Metadata ) storeData (data map [string ]interface {}) {
120
+ m .v .Store (data )
127
121
}
128
122
129
- // MarshalJSON allows easy JSON representation of the JWTClaims underlying claims map.
130
- func (c JWTClaims ) MarshalJSON () ([]byte , error ) {
131
- return json .Marshal (c )
123
+ func (m * Metadata ) copyAndStore (key string , val interface {}) {
124
+ data := deepCopyMap (m .loadData ())
125
+ data [key ] = val
126
+ m .storeData (data )
132
127
}
133
128
134
129
func deepCopyMap (m map [string ]interface {}) map [string ]interface {} {
0 commit comments