• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *
3  * Copyright 2018 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #include <string>
20 #include <unordered_set>
21 
22 #include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
23 #include "test/core/util/test_config.h"
24 
25 #include <grpc/grpc.h>
26 #include <grpc/support/log.h>
27 #include <gtest/gtest.h>
28 
29 namespace grpc_core {
30 
31 namespace {
32 
33 class SessionTracker;
34 
35 struct SessionExDataId {
36   SessionTracker* tracker;
37   long id;
38 };
39 
40 class SessionTracker {
41  public:
SessionTracker()42   SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); }
43 
~SessionTracker()44   ~SessionTracker() { SSL_CTX_free(ssl_context_); }
45 
NewSession(long id)46   tsi::SslSessionPtr NewSession(long id) {
47     static int ex_data_id = SSL_SESSION_get_ex_new_index(
48         0, nullptr, nullptr, nullptr, DestroyExData);
49     GPR_ASSERT(ex_data_id != -1);
50     // OpenSSL and different version of BoringSSL don't agree on API
51     // so try both.
52     tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new);
53     SessionExDataId* data = new SessionExDataId{this, id};
54     int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data);
55     EXPECT_EQ(result, 1);
56     alive_sessions_.insert(id);
57     return session;
58   }
59 
IsAlive(long id) const60   bool IsAlive(long id) const {
61     return alive_sessions_.find(id) != alive_sessions_.end();
62   }
63 
AliveCount() const64   size_t AliveCount() const { return alive_sessions_.size(); }
65 
66  private:
NewSessionInternal(SSL_SESSION * (* cb)())67   tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) {
68     return tsi::SslSessionPtr(cb());
69   }
70 
NewSessionInternal(SSL_SESSION * (* cb)(const SSL_CTX *))71   tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) {
72     return tsi::SslSessionPtr(cb(ssl_context_));
73   }
74 
DestroyExData(void * parent,void * ptr,CRYPTO_EX_DATA * ad,int index,long argl,void * argp)75   static void DestroyExData(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
76                             int index, long argl, void* argp) {
77     SessionExDataId* data = static_cast<SessionExDataId*>(ptr);
78     data->tracker->alive_sessions_.erase(data->id);
79     delete data;
80   }
81 
82   SSL_CTX* ssl_context_;
83   std::unordered_set<long> alive_sessions_;
84 };
85 
TEST(SslSessionCacheTest,InitialState)86 TEST(SslSessionCacheTest, InitialState) {
87   SessionTracker tracker;
88   // Verify session initial state.
89   {
90     tsi::SslSessionPtr tmp_sess = tracker.NewSession(1);
91     EXPECT_TRUE(tracker.IsAlive(1));
92     EXPECT_EQ(tracker.AliveCount(), 1);
93   }
94   EXPECT_FALSE(tracker.IsAlive(1));
95   EXPECT_EQ(tracker.AliveCount(), 0);
96 }
97 
TEST(SslSessionCacheTest,LruCache)98 TEST(SslSessionCacheTest, LruCache) {
99   SessionTracker tracker;
100   {
101     RefCountedPtr<tsi::SslSessionLRUCache> cache =
102         tsi::SslSessionLRUCache::Create(3);
103     tsi::SslSessionPtr sess2 = tracker.NewSession(2);
104     SSL_SESSION* sess2_ptr = sess2.get();
105     cache->Put("first.dropbox.com", std::move(sess2));
106     EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr);
107     EXPECT_TRUE(tracker.IsAlive(2));
108     EXPECT_EQ(tracker.AliveCount(), 1);
109     // Putting element with the same key destroys old session.
110     tsi::SslSessionPtr sess3 = tracker.NewSession(3);
111     SSL_SESSION* sess3_ptr = sess3.get();
112     cache->Put("first.dropbox.com", std::move(sess3));
113     EXPECT_FALSE(tracker.IsAlive(2));
114     EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr);
115     EXPECT_TRUE(tracker.IsAlive(3));
116     EXPECT_EQ(tracker.AliveCount(), 1);
117     // Putting three more elements discards current one.
118     for (long id = 4; id < 7; id++) {
119       EXPECT_TRUE(tracker.IsAlive(3));
120       std::string domain = std::to_string(id) + ".random.domain";
121       cache->Put(domain.c_str(), tracker.NewSession(id));
122     }
123     EXPECT_EQ(cache->Size(), 3);
124     EXPECT_FALSE(tracker.IsAlive(3));
125     EXPECT_EQ(tracker.AliveCount(), 3);
126     // Accessing element moves it into front of the queue.
127     EXPECT_TRUE(cache->Get("4.random.domain"));
128     EXPECT_TRUE(tracker.IsAlive(4));
129     EXPECT_TRUE(tracker.IsAlive(5));
130     EXPECT_TRUE(tracker.IsAlive(6));
131     // One element has to be evicted from cache->
132     cache->Put("7.random.domain", tracker.NewSession(7));
133     EXPECT_TRUE(tracker.IsAlive(4));
134     EXPECT_FALSE(tracker.IsAlive(5));
135     EXPECT_TRUE(tracker.IsAlive(6));
136     EXPECT_TRUE(tracker.IsAlive(7));
137     EXPECT_EQ(tracker.AliveCount(), 3);
138   }
139   // Cache destructor destroys all sessions.
140   EXPECT_EQ(tracker.AliveCount(), 0);
141 }
142 
143 }  // namespace
144 }  // namespace grpc_core
145 
main(int argc,char ** argv)146 int main(int argc, char** argv) {
147   ::testing::InitGoogleTest(&argc, argv);
148   grpc_test_init(argc, argv);
149   grpc_init();
150   int ret = RUN_ALL_TESTS();
151   grpc_shutdown();
152   return ret;
153 }
154