vertexcache 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/vertexcache/comm/client_connector.rb +111 -0
- data/lib/vertexcache/comm/gcm_crypto_helper.rb +98 -0
- data/lib/vertexcache/comm/key_parser_helper.rb +64 -0
- data/lib/vertexcache/comm/message_codec.rb +76 -0
- data/lib/vertexcache/comm/read_write_stream.rb +55 -0
- data/lib/vertexcache/comm/socket_helper.rb +70 -0
- data/lib/vertexcache/comm/ssl_helper.rb +75 -0
- data/lib/vertexcache/command/command_base.rb +88 -0
- data/lib/vertexcache/command/command_type.rb +29 -0
- data/lib/vertexcache/command/impl/del_command.rb +45 -0
- data/lib/vertexcache/command/impl/get_command.rb +50 -0
- data/lib/vertexcache/command/impl/get_secondary_idx_one_command.rb +50 -0
- data/lib/vertexcache/command/impl/get_secondary_idx_two_command.rb +50 -0
- data/lib/vertexcache/command/impl/ping_command.rb +35 -0
- data/lib/vertexcache/command/impl/set_command.rb +78 -0
- data/lib/vertexcache/model/client_option.rb +129 -0
- data/lib/vertexcache/model/command_result.rb +36 -0
- data/lib/vertexcache/model/encryption_mode.rb +32 -0
- data/lib/vertexcache/model/get_result.rb +30 -0
- data/lib/vertexcache/model/vertex_cache_sdk_exception.rb +24 -0
- data/lib/vertexcache/version.rb +3 -0
- data/lib/vertexcache_sdk.rb +76 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 42487afddcfc0a68780c8ece4a39e7a56d5ce43e9d5119763b8dde6dd0b523c9
|
4
|
+
data.tar.gz: 0ede333ce914f2c1030150613b615bb199d62cbd714464e08a3a0758ed7a9368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1617b92ae33cd851091f74eb27304a73713ac061be14a3c7c7124fbd4e847791b4e6b84fe317d0180788a23283276b1520f733639fe1b60f43d34c1b46ad7dfd
|
7
|
+
data.tar.gz: 4bc6093994482292e506b270508c0fa9e8c5796ed6d7ee47ba096ece2cd8d2ea6d293516056ce24b4fbb9f802f02848c52f8eba4b0f1f10282460bb69859910f
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'socket'
|
20
|
+
require 'openssl'
|
21
|
+
require 'stringio'
|
22
|
+
require 'vertexcache/comm/gcm_crypto_helper'
|
23
|
+
require 'vertexcache/comm/key_parser_helper'
|
24
|
+
require 'vertexcache/comm/message_codec'
|
25
|
+
require 'vertexcache/comm/socket_helper'
|
26
|
+
require 'vertexcache/model/vertex_cache_sdk_exception'
|
27
|
+
|
28
|
+
module VertexCache
|
29
|
+
module Comm
|
30
|
+
class ClientConnector
|
31
|
+
attr_reader :connected
|
32
|
+
|
33
|
+
def initialize(client_option)
|
34
|
+
@options = client_option
|
35
|
+
@stream = nil
|
36
|
+
@connected = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def connect
|
40
|
+
@stream = if @options.enable_tls_encryption
|
41
|
+
SocketHelper.create_secure_socket(@options)
|
42
|
+
else
|
43
|
+
SocketHelper.create_socket_non_tls(@options)
|
44
|
+
end
|
45
|
+
|
46
|
+
writer = @stream.to_io
|
47
|
+
ident_command = @options.build_ident_command
|
48
|
+
payload = encrypt_if_enabled(ident_command.encode('UTF-8'))
|
49
|
+
MessageCodec.write_framed_message(writer, payload)
|
50
|
+
writer.flush
|
51
|
+
|
52
|
+
version, response_payload = MessageCodec.read_framed_message(@stream)
|
53
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Missing payload' if response_payload.nil?
|
54
|
+
|
55
|
+
response = response_payload.force_encoding('UTF-8').strip
|
56
|
+
unless response.start_with?('+OK')
|
57
|
+
raise VertexCache::Model::VertexCacheSdkException, "Authorization failed: #{response}"
|
58
|
+
end
|
59
|
+
|
60
|
+
@connected = true
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def send(message)
|
65
|
+
raise VertexCache::Model::VertexCacheSdkException, 'No active connection' unless connected?
|
66
|
+
|
67
|
+
payload = encrypt_if_enabled(message.encode('UTF-8'))
|
68
|
+
|
69
|
+
writer = @stream.to_io
|
70
|
+
MessageCodec.write_framed_message(writer, payload)
|
71
|
+
writer.flush
|
72
|
+
|
73
|
+
_version, response_payload = MessageCodec.read_framed_message(@stream)
|
74
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Missing payload' if response_payload.nil?
|
75
|
+
|
76
|
+
response_payload.force_encoding('UTF-8')
|
77
|
+
end
|
78
|
+
|
79
|
+
def encrypt_if_enabled(plain)
|
80
|
+
case @options.encryption_mode
|
81
|
+
when VertexCache::Model::EncryptionMode::ASYMMETRIC
|
82
|
+
pem = @options.public_key
|
83
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Missing public key' if pem.nil?
|
84
|
+
|
85
|
+
MessageCodec.switch_to_asymmetric
|
86
|
+
KeyParserHelper.encrypt_with_rsa(pem, plain)
|
87
|
+
when VertexCache::Model::EncryptionMode::SYMMETRIC
|
88
|
+
key = @options.shared_encryption_key_as_bytes
|
89
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Missing shared encryption key' if key.nil?
|
90
|
+
|
91
|
+
MessageCodec.switch_to_symmetric
|
92
|
+
GcmCryptoHelper.encrypt(plain, key)
|
93
|
+
else
|
94
|
+
plain
|
95
|
+
end
|
96
|
+
rescue => e
|
97
|
+
raise VertexCache::Model::VertexCacheSdkException, e.message
|
98
|
+
end
|
99
|
+
|
100
|
+
def connected?
|
101
|
+
@connected && !@stream.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def close
|
105
|
+
@stream.close if @stream
|
106
|
+
@stream = nil
|
107
|
+
@connected = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'openssl'
|
20
|
+
require 'base64'
|
21
|
+
|
22
|
+
module VertexCache
|
23
|
+
module Comm
|
24
|
+
class GcmCryptoHelper
|
25
|
+
IV_LENGTH = 12
|
26
|
+
TAG_LENGTH = 16
|
27
|
+
KEY_LENGTH = 32
|
28
|
+
|
29
|
+
def self.encrypt(data, key)
|
30
|
+
raise ArgumentError, 'Invalid key length' unless key.bytesize == 16 || key.bytesize == 32
|
31
|
+
|
32
|
+
iv = OpenSSL::Random.random_bytes(IV_LENGTH)
|
33
|
+
cipher = OpenSSL::Cipher.new("aes-#{key.bytesize * 8}-gcm")
|
34
|
+
cipher.encrypt
|
35
|
+
cipher.key = key
|
36
|
+
cipher.iv = iv
|
37
|
+
|
38
|
+
ciphertext = cipher.update(data) + cipher.final
|
39
|
+
tag = cipher.auth_tag(TAG_LENGTH)
|
40
|
+
|
41
|
+
iv + ciphertext + tag
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.decrypt(encrypted, key)
|
45
|
+
raise ArgumentError, 'Encrypted data too short' if encrypted.bytesize < IV_LENGTH + TAG_LENGTH
|
46
|
+
|
47
|
+
iv = encrypted[0, IV_LENGTH]
|
48
|
+
tag = encrypted[-TAG_LENGTH, TAG_LENGTH]
|
49
|
+
ciphertext = encrypted[IV_LENGTH..-(TAG_LENGTH + 1)]
|
50
|
+
|
51
|
+
cipher = OpenSSL::Cipher.new("aes-#{key.bytesize * 8}-gcm")
|
52
|
+
cipher.decrypt
|
53
|
+
cipher.key = key
|
54
|
+
cipher.iv = iv
|
55
|
+
cipher.auth_tag = tag
|
56
|
+
|
57
|
+
cipher.update(ciphertext) + cipher.final
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.encode_base64_key(key)
|
61
|
+
Base64.strict_encode64(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.decode_base64_key(encoded)
|
65
|
+
Base64.strict_decode64(encoded)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.generate_base64_key
|
69
|
+
Base64.strict_encode64(OpenSSL::Random.random_bytes(KEY_LENGTH))
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.encrypt_with_iv(data, key, iv)
|
73
|
+
cipher = OpenSSL::Cipher.new("aes-#{key.bytesize * 8}-gcm")
|
74
|
+
cipher.encrypt
|
75
|
+
cipher.key = key
|
76
|
+
cipher.iv = iv
|
77
|
+
|
78
|
+
ciphertext = cipher.update(data) + cipher.final
|
79
|
+
tag = cipher.auth_tag(TAG_LENGTH)
|
80
|
+
|
81
|
+
iv + ciphertext + tag
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.decrypt_with_iv(encrypted, key, iv)
|
85
|
+
tag = encrypted[-TAG_LENGTH, TAG_LENGTH]
|
86
|
+
ciphertext = encrypted[IV_LENGTH..-(TAG_LENGTH + 1)]
|
87
|
+
|
88
|
+
cipher = OpenSSL::Cipher.new("aes-#{key.bytesize * 8}-gcm")
|
89
|
+
cipher.decrypt
|
90
|
+
cipher.key = key
|
91
|
+
cipher.iv = iv
|
92
|
+
cipher.auth_tag = tag
|
93
|
+
|
94
|
+
cipher.update(ciphertext) + cipher.final
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# You may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'base64'
|
20
|
+
require 'openssl'
|
21
|
+
require 'vertexcache/model/vertex_cache_sdk_exception'
|
22
|
+
|
23
|
+
|
24
|
+
module VertexCache
|
25
|
+
module Comm
|
26
|
+
class KeyParserHelper
|
27
|
+
def self.config_public_key_if_enabled(pem_string)
|
28
|
+
begin
|
29
|
+
cleaned = pem_string.gsub(/-----BEGIN PUBLIC KEY-----/, '')
|
30
|
+
.gsub(/-----END PUBLIC KEY-----/, '')
|
31
|
+
.gsub(/\s+/, '')
|
32
|
+
decoded = Base64.strict_decode64(cleaned)
|
33
|
+
OpenSSL::PKey.read(decoded)
|
34
|
+
decoded
|
35
|
+
rescue
|
36
|
+
raise VertexCache::Model::VertexCacheSdkException.new('Invalid public key')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.config_shared_key_if_enabled(base64_string)
|
41
|
+
begin
|
42
|
+
decoded = Base64.strict_decode64(base64_string)
|
43
|
+
if Base64.strict_encode64(decoded) != base64_string.gsub(/\s+/, '')
|
44
|
+
raise VertexCache::Model::VertexCacheSdkException.new('Invalid shared key')
|
45
|
+
end
|
46
|
+
decoded
|
47
|
+
rescue
|
48
|
+
raise VertexCache::Model::VertexCacheSdkException.new('Invalid shared key')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.encrypt_with_rsa(pem_string, plaintext)
|
53
|
+
begin
|
54
|
+
public_key = OpenSSL::PKey::RSA.new(pem_string)
|
55
|
+
cipher_text = public_key.public_encrypt(plaintext, OpenSSL::PKey::RSA::PKCS1_PADDING)
|
56
|
+
cipher_text
|
57
|
+
rescue
|
58
|
+
raise VertexCache::Model::VertexCacheSdkException.new('RSA encryption failed')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'stringio'
|
20
|
+
require 'vertexcache/model/vertex_cache_sdk_exception'
|
21
|
+
|
22
|
+
module VertexCache
|
23
|
+
module Comm
|
24
|
+
class MessageCodec
|
25
|
+
HEADER_LEN = 8
|
26
|
+
MAX_PAYLOAD_SIZE = 10 * 1024 * 1024
|
27
|
+
MAX_MESSAGE_SIZE = 4 * 1024 * 1024
|
28
|
+
|
29
|
+
PROTOCOL_VERSION_RSA_PKCS1 = 0x00000101
|
30
|
+
PROTOCOL_VERSION_AES_GCM = 0x00000181
|
31
|
+
DEFAULT_PROTOCOL_VERSION = PROTOCOL_VERSION_RSA_PKCS1
|
32
|
+
|
33
|
+
@@protocol_version = DEFAULT_PROTOCOL_VERSION
|
34
|
+
|
35
|
+
def self.switch_to_asymmetric
|
36
|
+
@@protocol_version = PROTOCOL_VERSION_RSA_PKCS1
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.switch_to_symmetric
|
40
|
+
@@protocol_version = PROTOCOL_VERSION_AES_GCM
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.write_framed_message(io, payload)
|
44
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Payload must be non-empty' if payload.nil? || payload.empty?
|
45
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Payload too large' if payload.bytesize > MAX_MESSAGE_SIZE
|
46
|
+
|
47
|
+
length_prefix = [payload.bytesize].pack('N') # 4 bytes big-endian
|
48
|
+
version_bytes = [@@protocol_version].pack('N') # 4 bytes big-endian
|
49
|
+
io.write(length_prefix + version_bytes + payload)
|
50
|
+
io.flush
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.read_framed_message(io)
|
54
|
+
header = io.read(HEADER_LEN)
|
55
|
+
return nil if header.nil? || header.bytesize < HEADER_LEN
|
56
|
+
|
57
|
+
length = header[0, 4].unpack1('N')
|
58
|
+
version = header[4, 4].unpack1('N')
|
59
|
+
|
60
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Invalid message length' if length <= 0 || length > MAX_PAYLOAD_SIZE
|
61
|
+
|
62
|
+
payload = io.read(length)
|
63
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Unexpected end of payload' if payload.nil? || payload.bytesize != length
|
64
|
+
|
65
|
+
[version, payload]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.hex_dump(payload)
|
69
|
+
io = StringIO.new
|
70
|
+
write_framed_message(io, payload)
|
71
|
+
io.rewind
|
72
|
+
io.read.unpack1('H*').upcase
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
module VertexCache
|
20
|
+
module Comm
|
21
|
+
class ReadWriteStream
|
22
|
+
def initialize(io)
|
23
|
+
@io = io
|
24
|
+
end
|
25
|
+
|
26
|
+
def read(n)
|
27
|
+
@io.read(n)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(data)
|
31
|
+
@io.write(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def flush
|
35
|
+
@io.flush
|
36
|
+
end
|
37
|
+
|
38
|
+
def close
|
39
|
+
@io.close
|
40
|
+
end
|
41
|
+
|
42
|
+
def closed?
|
43
|
+
@io.closed?
|
44
|
+
end
|
45
|
+
|
46
|
+
def io
|
47
|
+
@io
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_io
|
51
|
+
@io
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'socket'
|
20
|
+
require 'openssl'
|
21
|
+
require_relative 'ssl_helper'
|
22
|
+
require_relative 'read_write_stream'
|
23
|
+
require_relative '../model/vertex_cache_sdk_exception'
|
24
|
+
|
25
|
+
module VertexCache
|
26
|
+
module Comm
|
27
|
+
class SocketHelper
|
28
|
+
def self.create_secure_socket(options)
|
29
|
+
begin
|
30
|
+
tcp_socket = create_tcp_socket(options)
|
31
|
+
context = if options.verify_certificate
|
32
|
+
SSLHelper.create_verified_ssl_context(options.tls_certificate)
|
33
|
+
else
|
34
|
+
SSLHelper.create_insecure_ssl_context
|
35
|
+
end
|
36
|
+
|
37
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
|
38
|
+
ssl_socket.sync_close = true
|
39
|
+
ssl_socket.connect
|
40
|
+
|
41
|
+
ReadWriteStream.new(ssl_socket)
|
42
|
+
rescue => _
|
43
|
+
raise VertexCache::Model::VertexCacheSdkException.new("Failed to create Secure Socket")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.create_socket_non_tls(options)
|
48
|
+
begin
|
49
|
+
tcp_socket = create_tcp_socket(options)
|
50
|
+
ReadWriteStream.new(tcp_socket)
|
51
|
+
rescue => _
|
52
|
+
raise VertexCache::Model::VertexCacheSdkException.new("Failed to create Non Secure Socket")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def self.create_tcp_socket(options)
|
59
|
+
socket = TCPSocket.new(options.server_host, options.server_port)
|
60
|
+
seconds = options.read_timeout.to_i
|
61
|
+
timeval = [seconds, 0].pack("l_2")
|
62
|
+
|
63
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval)
|
64
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, timeval)
|
65
|
+
|
66
|
+
socket
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
require 'openssl'
|
20
|
+
require 'vertexcache/model/vertex_cache_sdk_exception'
|
21
|
+
|
22
|
+
module VertexCache
|
23
|
+
module Comm
|
24
|
+
class SSLHelper
|
25
|
+
# Modern secure cipher suites aligned with server and industry standards
|
26
|
+
MODERN_CIPHERS = [
|
27
|
+
"TLS_AES_128_GCM_SHA256",
|
28
|
+
"TLS_AES_256_GCM_SHA384",
|
29
|
+
"TLS_CHACHA20_POLY1305_SHA256",
|
30
|
+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
31
|
+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
32
|
+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
33
|
+
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
34
|
+
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
35
|
+
].join(":")
|
36
|
+
|
37
|
+
def self.create_verified_ssl_context(pem_cert)
|
38
|
+
raise_invalid_cert unless valid_cert_format?(pem_cert)
|
39
|
+
|
40
|
+
cert = OpenSSL::X509::Certificate.new(pem_cert)
|
41
|
+
store = OpenSSL::X509::Store.new
|
42
|
+
store.add_cert(cert)
|
43
|
+
|
44
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
45
|
+
ctx.cert_store = store
|
46
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
47
|
+
ctx.ciphers = MODERN_CIPHERS
|
48
|
+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
49
|
+
ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
|
50
|
+
ctx
|
51
|
+
rescue OpenSSL::X509::CertificateError
|
52
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Failed to parse PEM certificate'
|
53
|
+
rescue => e
|
54
|
+
raise VertexCache::Model::VertexCacheSdkException, 'Failed to build secure socket context'
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.create_insecure_ssl_context
|
58
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
59
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
60
|
+
ctx.ciphers = MODERN_CIPHERS
|
61
|
+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
62
|
+
ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
|
63
|
+
ctx
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.valid_cert_format?(pem_cert)
|
67
|
+
pem_cert && pem_cert.include?("BEGIN CERTIFICATE")
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.raise_invalid_cert
|
71
|
+
raise VertexCache::Model::VertexCacheSdkException, "Invalid certificate format"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# ------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
require_relative '../comm/client_connector'
|
18
|
+
require_relative '../model/vertex_cache_sdk_exception'
|
19
|
+
|
20
|
+
module VertexCache
|
21
|
+
module Command
|
22
|
+
class CommandBase
|
23
|
+
RESPONSE_OK = 'OK'
|
24
|
+
COMMAND_SPACER = ' '
|
25
|
+
|
26
|
+
attr_reader :success, :response, :error
|
27
|
+
|
28
|
+
def execute(client)
|
29
|
+
begin
|
30
|
+
raw = client.send(build_command).strip
|
31
|
+
|
32
|
+
if raw.start_with?('+')
|
33
|
+
@response = raw[1..]
|
34
|
+
parse_response(@response)
|
35
|
+
@success = @error.nil?
|
36
|
+
elsif raw.start_with?('-')
|
37
|
+
@success = false
|
38
|
+
@error = raw[1..]
|
39
|
+
else
|
40
|
+
@success = false
|
41
|
+
@error = "Unexpected response: #{raw}"
|
42
|
+
end
|
43
|
+
rescue VertexCache::Model::VertexCacheSdkException => e
|
44
|
+
@success = false
|
45
|
+
@error = e.message
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def success?
|
51
|
+
@success
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_response
|
55
|
+
@response
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_error
|
59
|
+
@error
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_status_message
|
63
|
+
success? ? get_response : get_error
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_success(response = RESPONSE_OK)
|
67
|
+
@success = true
|
68
|
+
@response = response
|
69
|
+
@error = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_failure(message)
|
73
|
+
@success = false
|
74
|
+
@error = message
|
75
|
+
end
|
76
|
+
|
77
|
+
# override in subclasses
|
78
|
+
def parse_response(_body)
|
79
|
+
# default no-op
|
80
|
+
end
|
81
|
+
|
82
|
+
# must override
|
83
|
+
def build_command
|
84
|
+
raise NotImplementedError, 'Subclasses must implement build_command'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Copyright 2025 to Present, Jason Lam - VertexCache (https://github.com/vertexcache/vertexcache)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# ------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
module VertexCache
|
18
|
+
module Command
|
19
|
+
module CommandType
|
20
|
+
PING = 'PING'
|
21
|
+
SET = 'SET'
|
22
|
+
DEL = 'DEL'
|
23
|
+
IDX1 = 'IDX1'
|
24
|
+
IDX2 = 'IDX2'
|
25
|
+
|
26
|
+
ALL = [PING, SET, DEL, IDX1, IDX2].freeze
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|