Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 1 | package ipc |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "reflect" |
| 6 | "sync" |
| 7 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 8 | "v.io/core/veyron2/ipc" |
| 9 | "v.io/core/veyron2/ipc/stream" |
| 10 | "v.io/core/veyron2/security" |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 11 | ) |
| 12 | |
| 13 | // clientEncodeBlessings gets or inserts the blessings into the cache. |
| 14 | func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) ipc.BlessingsRequest { |
| 15 | blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache) |
| 16 | blessingsCache := blessingsCacheAny.(*clientBlessingsCache) |
| 17 | return blessingsCache.getOrInsert(blessings) |
| 18 | } |
| 19 | |
| 20 | // clientAckBlessings verifies that the server has updated its cache to include blessings. |
| 21 | // This means that subsequent rpcs from the client with blessings can send only a cache key. |
| 22 | func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) { |
| 23 | blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache) |
| 24 | blessingsCache := blessingsCacheAny.(*clientBlessingsCache) |
| 25 | blessingsCache.acknowledge(blessings) |
| 26 | } |
| 27 | |
| 28 | // serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only |
| 29 | // key is provided in req. |
| 30 | func serverDecodeBlessings(cache stream.VCDataCache, req ipc.BlessingsRequest, stats *ipcStats) (security.Blessings, error) { |
| 31 | blessingsCacheAny := cache.GetOrInsert(serverBlessingsKey{}, newServerBlessingsCache) |
| 32 | blessingsCache := blessingsCacheAny.(*serverBlessingsCache) |
| 33 | return blessingsCache.getOrInsert(req, stats) |
| 34 | } |
| 35 | |
| 36 | // IMPLEMENTATION DETAILS BELOW |
| 37 | |
| 38 | // clientBlessingsCache is a thread-safe map from blessings to cache key. |
| 39 | type clientBlessingsCache struct { |
| 40 | sync.RWMutex |
| 41 | m map[security.Blessings]clientCacheValue |
| 42 | key uint64 |
| 43 | } |
| 44 | |
| 45 | type clientCacheValue struct { |
| 46 | key uint64 |
| 47 | // ack is set to true once the server has confirmed receipt of the cache key. |
| 48 | // Clients that insert into the cache when ack is false must send both the key |
| 49 | // and the blessings. |
| 50 | ack bool |
| 51 | } |
| 52 | |
| 53 | // clientBlessingsKey is the key used to retrieve the clientBlessingsCache from the VCDataCache. |
| 54 | type clientBlessingsKey struct{} |
| 55 | |
| 56 | func newClientBlessingsCache() interface{} { |
| 57 | return &clientBlessingsCache{m: make(map[security.Blessings]clientCacheValue)} |
| 58 | } |
| 59 | |
| 60 | func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) ipc.BlessingsRequest { |
| 61 | c.RLock() |
| 62 | val, exists := c.m[blessings] |
| 63 | c.RUnlock() |
| 64 | if exists { |
| 65 | return c.makeBlessingsRequest(val, blessings) |
| 66 | } |
| 67 | // if the val doesn't exist we must create a new key, update the cache, and send the key and blessings. |
| 68 | c.Lock() |
| 69 | defer c.Unlock() |
| 70 | // we must check that the val wasn't inserted in the time we changed locks. |
| 71 | val, exists = c.m[blessings] |
| 72 | if exists { |
| 73 | return c.makeBlessingsRequest(val, blessings) |
| 74 | } |
| 75 | newVal := clientCacheValue{key: c.nextKeyLocked()} |
| 76 | c.m[blessings] = newVal |
| 77 | return c.makeBlessingsRequest(newVal, blessings) |
| 78 | } |
| 79 | |
| 80 | func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) { |
| 81 | c.Lock() |
| 82 | val := c.m[blessings] |
| 83 | val.ack = true |
| 84 | c.m[blessings] = val |
| 85 | c.Unlock() |
| 86 | } |
| 87 | |
| 88 | func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) ipc.BlessingsRequest { |
| 89 | if val.ack { |
| 90 | // when the value is acknowledged, only send the key, since the server has confirmed that it knows the key. |
| 91 | return ipc.BlessingsRequest{Key: val.key} |
| 92 | } |
| 93 | // otherwise we still need to send both key and blessings, but we must ensure that we send the same key. |
| 94 | wireBlessings := security.MarshalBlessings(blessings) |
| 95 | return ipc.BlessingsRequest{val.key, &wireBlessings} |
| 96 | } |
| 97 | |
| 98 | // nextKeyLocked creates a new key for inserting blessings. It must be called after acquiring a writer lock. |
| 99 | func (c *clientBlessingsCache) nextKeyLocked() uint64 { |
| 100 | c.key++ |
| 101 | return c.key |
| 102 | } |
| 103 | |
| 104 | // serverBlessingsCache is a thread-safe map from cache key to blessings. |
| 105 | type serverBlessingsCache struct { |
| 106 | sync.RWMutex |
| 107 | m map[uint64]security.Blessings |
| 108 | } |
| 109 | |
| 110 | // serverBlessingsKey is the key used to retrieve the serverBlessingsCache from the VCDataCache. |
| 111 | type serverBlessingsKey struct{} |
| 112 | |
| 113 | func newServerBlessingsCache() interface{} { |
| 114 | return &serverBlessingsCache{m: make(map[uint64]security.Blessings)} |
| 115 | } |
| 116 | |
| 117 | func (c *serverBlessingsCache) getOrInsert(req ipc.BlessingsRequest, stats *ipcStats) (security.Blessings, error) { |
| 118 | // In the case that the key sent is 0, we are running in VCSecurityNone and should |
| 119 | // return nil for the client Blessings. |
| 120 | if req.Key == 0 { |
| 121 | return nil, nil |
| 122 | } |
| 123 | if req.Blessings == nil { |
| 124 | // Fastpath, lookup based on the key. |
| 125 | c.RLock() |
| 126 | cached, exists := c.m[req.Key] |
| 127 | c.RUnlock() |
| 128 | if !exists { |
| 129 | return nil, fmt.Errorf("ipc: key was not in the cache") |
| 130 | } |
| 131 | stats.recordBlessingCache(true) |
| 132 | return cached, nil |
| 133 | } |
Suharsh Sivakumar | defea2b | 2015-01-13 09:33:50 -0800 | [diff] [blame^] | 134 | // Always count the slow path as a cache miss since we do not get the benefit of sending only the cache key. |
| 135 | stats.recordBlessingCache(false) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 136 | // Slowpath, might need to update the cache, or check that the received blessings are |
| 137 | // the same as what's in the cache. |
| 138 | recv, err := security.NewBlessings(*req.Blessings) |
| 139 | if err != nil { |
| 140 | return nil, fmt.Errorf("ipc: create new client blessings failed: %v", err) |
| 141 | } |
| 142 | c.Lock() |
| 143 | defer c.Unlock() |
| 144 | if cached, exists := c.m[req.Key]; exists { |
| 145 | // TODO(suharshs): Replace this reflect.DeepEqual() with a less expensive check. |
| 146 | if !reflect.DeepEqual(cached, recv) { |
| 147 | return nil, fmt.Errorf("client sent invalid Blessings") |
| 148 | } |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 149 | return cached, nil |
| 150 | } |
| 151 | c.m[req.Key] = recv |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 152 | return recv, nil |
| 153 | } |