net-tns 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/LICENSE +202 -0
- data/bin/tns_oradb_version.rb +15 -0
- data/bin/tns_sid_enumeration.rb +52 -0
- data/bin/tns_sid_list.rb +43 -0
- data/lib/net/tns/client.rb +94 -0
- data/lib/net/tns/connection.rb +201 -0
- data/lib/net/tns/exceptions.rb +34 -0
- data/lib/net/tns/gem_version.rb +5 -0
- data/lib/net/tns/helpers/string_helpers.rb +49 -0
- data/lib/net/tns/packet.rb +82 -0
- data/lib/net/tns/packets/abort_packet.rb +12 -0
- data/lib/net/tns/packets/accept_packet.rb +19 -0
- data/lib/net/tns/packets/ack_packet.rb +7 -0
- data/lib/net/tns/packets/attention_packet.rb +9 -0
- data/lib/net/tns/packets/connect_packet.rb +55 -0
- data/lib/net/tns/packets/control_packet.rb +7 -0
- data/lib/net/tns/packets/data_packet.rb +20 -0
- data/lib/net/tns/packets/marker_packet.rb +18 -0
- data/lib/net/tns/packets/null_packet.rb +7 -0
- data/lib/net/tns/packets/redirect_packet.rb +10 -0
- data/lib/net/tns/packets/refuse_packet.rb +12 -0
- data/lib/net/tns/packets/resend_packet.rb +7 -0
- data/lib/net/tns/version.rb +15 -0
- data/lib/net/tns.rb +20 -0
- data/lib/net/tti/client.rb +90 -0
- data/lib/net/tti/connection.rb +142 -0
- data/lib/net/tti/crypto.rb +189 -0
- data/lib/net/tti/data_types/chunked_string.rb +63 -0
- data/lib/net/tti/data_types/key_value_pair.rb +25 -0
- data/lib/net/tti/data_types.rb +2 -0
- data/lib/net/tti/exceptions.rb +62 -0
- data/lib/net/tti/message.rb +50 -0
- data/lib/net/tti/messages/data_type_negotiation_request.rb +133 -0
- data/lib/net/tti/messages/data_type_negotiation_response.rb +9 -0
- data/lib/net/tti/messages/error_message.rb +14 -0
- data/lib/net/tti/messages/function_call.rb +46 -0
- data/lib/net/tti/messages/function_calls/authentication.rb +45 -0
- data/lib/net/tti/messages/function_calls/authentication_x64.rb +42 -0
- data/lib/net/tti/messages/function_calls/authentication_x86.rb +53 -0
- data/lib/net/tti/messages/function_calls/pre_authentication_response.rb +32 -0
- data/lib/net/tti/messages/protocol_negotiation_request.rb +29 -0
- data/lib/net/tti/messages/protocol_negotiation_response.rb +44 -0
- data/lib/net/tti.rb +18 -0
- metadata +128 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Net::TNS
|
2
|
+
# This module includes common string helper methods for monkey-patching
|
3
|
+
# or mixing-in to string objects.
|
4
|
+
module StringHelpers
|
5
|
+
HEXCHARS = [("0".."9").to_a, ("a".."f").to_a].flatten
|
6
|
+
|
7
|
+
#From the Ruby Black Bag (http://github.com/emonti/rbkb/)
|
8
|
+
# Convert a string to ASCII hex string. Supports a few options for format:
|
9
|
+
#
|
10
|
+
# :delim - delimter between each hex byte
|
11
|
+
# :prefix - prefix before each hex byte
|
12
|
+
# :suffix - suffix after each hex byte
|
13
|
+
#
|
14
|
+
def tns_hexify(opts={})
|
15
|
+
delim = opts[:delim]
|
16
|
+
pre = (opts[:prefix] || "")
|
17
|
+
suf = (opts[:suffix] || "")
|
18
|
+
|
19
|
+
if (rx=opts[:rx]) and not rx.kind_of? Regexp
|
20
|
+
raise "rx must be a regular expression for a character class"
|
21
|
+
end
|
22
|
+
|
23
|
+
out=Array.new
|
24
|
+
|
25
|
+
self.each_byte do |c|
|
26
|
+
hc = if (rx and not rx.match c.chr)
|
27
|
+
c.chr
|
28
|
+
else
|
29
|
+
pre + (HEXCHARS[(c >> 4)] + HEXCHARS[(c & 0xf )]) + suf
|
30
|
+
end
|
31
|
+
out << (hc)
|
32
|
+
end
|
33
|
+
out.join(delim)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert ASCII hex string to raw.
|
37
|
+
#
|
38
|
+
# Parameters:
|
39
|
+
#
|
40
|
+
# d = optional 'delimiter' between hex bytes (zero+ spaces by default)
|
41
|
+
def tns_unhexify(d=/\s*/)
|
42
|
+
self.strip.gsub(/([A-Fa-f0-9]{1,2})#{d}?/) { $1.hex.chr }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class String
|
48
|
+
include Net::TNS::StringHelpers
|
49
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "bindata"
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module TNS
|
5
|
+
class Header < BinData::Record
|
6
|
+
LENGTH = 8
|
7
|
+
|
8
|
+
uint16be :packet_length
|
9
|
+
uint16be :packet_checksum
|
10
|
+
uint8 :packet_type
|
11
|
+
uint8 :flags
|
12
|
+
uint16be :checksum
|
13
|
+
end
|
14
|
+
|
15
|
+
class Packet < BinData::Record
|
16
|
+
SESSION_DATA_UNIT_SIZE = 2048
|
17
|
+
MAX_PAYLOAD_SIZE = SESSION_DATA_UNIT_SIZE - Header::LENGTH
|
18
|
+
|
19
|
+
# BinData fields
|
20
|
+
header :header
|
21
|
+
|
22
|
+
|
23
|
+
def self.register_tns_type(tns_type)
|
24
|
+
@@tns_packet_classes ||= {}
|
25
|
+
@@tns_packet_types ||= {}
|
26
|
+
if @@tns_packet_classes.has_key?(tns_type)
|
27
|
+
existing_class = @@tns_packet_classes[tns_type]
|
28
|
+
raise("Duplicate TNS Types Defined: #{existing_class} and #{self} both have a type of #{tns_type}")
|
29
|
+
end
|
30
|
+
|
31
|
+
@@tns_packet_classes[tns_type] = self
|
32
|
+
@@tns_packet_types[self] = tns_type
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_socket( socket )
|
37
|
+
Net::TNS.logger.debug("Attempting to read header")
|
38
|
+
# TODO: allow sockets to implement their own timeout behavior
|
39
|
+
require "timeout"
|
40
|
+
begin
|
41
|
+
header_raw = Timeout::timeout(5) do
|
42
|
+
socket.read(Header::LENGTH)
|
43
|
+
end
|
44
|
+
rescue Timeout::Error
|
45
|
+
raise Exceptions::ReceiveTimeoutExceeded
|
46
|
+
end
|
47
|
+
|
48
|
+
if header_raw.nil? || header_raw.length != Header::LENGTH
|
49
|
+
raise Exceptions::ProtocolException
|
50
|
+
end
|
51
|
+
|
52
|
+
header = Header.new()
|
53
|
+
header.read( header_raw )
|
54
|
+
Net::TNS.logger.debug("Read header. Reported packet length is #{header.packet_length} bytes")
|
55
|
+
raise Exceptions::ProtocolException if header.packet_length > SESSION_DATA_UNIT_SIZE
|
56
|
+
|
57
|
+
payload_raw = socket.read( header.packet_length - Header::LENGTH )
|
58
|
+
packet_raw = header_raw + payload_raw
|
59
|
+
|
60
|
+
unless payload_class = @@tns_packet_classes[ header.packet_type ]
|
61
|
+
raise Net::TNS::Exceptions::TnsException.new( "Unknown TNS packet type: #{header.packet_type}" )
|
62
|
+
end
|
63
|
+
|
64
|
+
new_packet = payload_class.read( packet_raw )
|
65
|
+
return new_packet
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_header()
|
69
|
+
self.header.packet_type = @@tns_packet_types[self.class]
|
70
|
+
self.header.packet_length = self.num_bytes
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_binary_s()
|
74
|
+
update_header()
|
75
|
+
return super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
require "pathname"
|
82
|
+
Dir.glob("#{Pathname.new(__FILE__).dirname}/packets/*.rb") { |file| require file }
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Net
|
2
|
+
module TNS
|
3
|
+
class AcceptPacket < Packet
|
4
|
+
register_tns_type 2
|
5
|
+
|
6
|
+
uint16be :version
|
7
|
+
uint16be :service_flags
|
8
|
+
uint16be :sdu_size # session data unit size
|
9
|
+
uint16be :maximum_tdu_size # maximum transmission data unit size
|
10
|
+
uint16be :byte_order # 0x0001 for little endian 0x0100 for big endian
|
11
|
+
uint16be :data_length
|
12
|
+
uint16be :data_offset # Offset to Connect Data (from parent header start)
|
13
|
+
uint8 :flags1
|
14
|
+
uint8 :flags2
|
15
|
+
string :padding, :read_length => lambda {data_offset - 24}
|
16
|
+
string :data, :read_length => :data_length
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "net/tns/version"
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module TNS
|
5
|
+
class ConnectPacket < Packet
|
6
|
+
register_tns_type 1
|
7
|
+
|
8
|
+
uint16be :maximum_version, :initial_value => Net::TNS::Version::VERSION_11G
|
9
|
+
uint16be :minimum_version, :initial_value => Net::TNS::Version::ALL_VERSIONS.min
|
10
|
+
uint16be :service_flags
|
11
|
+
# session data unit size
|
12
|
+
uint16be :sdu_size, :initial_value => 0x0800
|
13
|
+
# maximum transmission data unit size
|
14
|
+
uint16be :maximum_tdu_size, :initial_value => 0x7fff
|
15
|
+
uint16be :protocol_flags, :initial_value => 0xc608
|
16
|
+
# Described as "Max packets before ACK" by http://www.pythian.com/blog/repost-oracle-protocol/
|
17
|
+
uint16be :line_turnaround_value
|
18
|
+
# 0x0001 for little endian 0x0100 for big endian
|
19
|
+
uint16be :byte_order, :initial_value => 0x0001
|
20
|
+
uint16be :data_length, :value => lambda { data.length } # Length of Connect Data
|
21
|
+
uint16be :data_offset, :value => lambda { supports_trace? ? 58 : 34 } # Offset to Connect Data (from start of TNS header)
|
22
|
+
# Maximum Receivable Connect Data
|
23
|
+
uint32be :maximum_connect_receive
|
24
|
+
uint8 :flags1, :initial_value => 0x41
|
25
|
+
uint8 :flags2, :initial_value => 0x41
|
26
|
+
|
27
|
+
uint32be :trace_item1, :onlyif => :supports_trace?
|
28
|
+
uint32be :trace_item2, :onlyif => :supports_trace?
|
29
|
+
uint64be :trace_connection_id, :onlyif => :supports_trace?
|
30
|
+
uint64be :unknown, :onlyif => :supports_trace?
|
31
|
+
|
32
|
+
string :data
|
33
|
+
|
34
|
+
def self.make_connect_request(dst_host, dst_port, target_clause)
|
35
|
+
conn_info = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=%s)(PORT=%i))(CONNECT_DATA=(SERVER=DEDICATED)%s))" %
|
36
|
+
[ dst_host, dst_port, target_clause ]
|
37
|
+
return self.new(:data => conn_info)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.make_connection_by_sid(dst_host, dst_port, sid)
|
41
|
+
target_clause = "(SID=#{sid})"
|
42
|
+
return make_connect_request(dst_host, dst_port, target_clause)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.make_connection_by_service_name(dst_host, dst_port, service_name)
|
46
|
+
target_clause = "(SERVICE_NAME=#{service_name})"
|
47
|
+
return make_connect_request(dst_host, dst_port, target_clause)
|
48
|
+
end
|
49
|
+
|
50
|
+
def supports_trace?
|
51
|
+
return maximum_version > 308
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Net
|
2
|
+
module TNS
|
3
|
+
class DataPacket < Packet
|
4
|
+
register_tns_type 6
|
5
|
+
|
6
|
+
uint16be :flags
|
7
|
+
rest :data
|
8
|
+
|
9
|
+
def self.max_data_length
|
10
|
+
return Net::TNS::Packet::MAX_PAYLOAD_SIZE - 2 # 2 = flags.length
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.make_disconnect_request
|
14
|
+
packet = self.new()
|
15
|
+
packet.flags = 0x0040
|
16
|
+
return packet
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Net
|
2
|
+
module TNS
|
3
|
+
class MarkerPacket < Packet
|
4
|
+
register_tns_type 12
|
5
|
+
|
6
|
+
uint8 :marker_type
|
7
|
+
rest :data
|
8
|
+
|
9
|
+
def self.create_request
|
10
|
+
request = self.new
|
11
|
+
request.marker_type = 1
|
12
|
+
request.data = "0002".tns_unhexify
|
13
|
+
|
14
|
+
return request
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module Net
|
3
|
+
module TNS
|
4
|
+
module Version
|
5
|
+
VERSION_9I = 312
|
6
|
+
VERSION_10G = 313
|
7
|
+
VERSION_11G = 314
|
8
|
+
# Other important versions include:
|
9
|
+
# 308 - The last version before some significant changes to TNS, particularly noticeable in Connect packets
|
10
|
+
# 300 - Many Oracle clients send this as their minimum version
|
11
|
+
|
12
|
+
ALL_VERSIONS = [ VERSION_9I, VERSION_10G, VERSION_11G ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/net/tns.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Net
|
2
|
+
module TNS
|
3
|
+
require "net/tns/packet"
|
4
|
+
require "net/tns/exceptions"
|
5
|
+
require "net/tns/client"
|
6
|
+
require "net/tns/connection"
|
7
|
+
require "net/tns/gem_version"
|
8
|
+
require "net/tns/helpers/string_helpers"
|
9
|
+
|
10
|
+
def self.logger
|
11
|
+
unless defined?(@@logger)
|
12
|
+
require "logger"
|
13
|
+
@@logger = Logger.new(STDERR)
|
14
|
+
@@logger.progname = "Net::TNS"
|
15
|
+
@@logger.sev_threshold = 6 unless $DEBUG
|
16
|
+
end
|
17
|
+
return @@logger
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "net/tti"
|
2
|
+
require "net/tns"
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module TTI
|
6
|
+
class Client
|
7
|
+
def connect(opts={})
|
8
|
+
socket_opts = {}
|
9
|
+
socket_opts_keys = [:host, :port]
|
10
|
+
socket_opts_keys.each {|key| socket_opts[key] = opts.delete(key) if opts.has_key?(key)}
|
11
|
+
|
12
|
+
connect_opts = {}
|
13
|
+
connect_opts_keys = [:sid, :service_name]
|
14
|
+
connect_opts_keys.each {|key| connect_opts[key] = opts.delete(key) if opts.has_key?(key)}
|
15
|
+
|
16
|
+
unless opts.empty?
|
17
|
+
raise ArgumentError.new("Unrecognized #connect options: #{opts.keys.join(",")}")
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
@tti_conn = Net::TTI::Connection.new(socket_opts)
|
22
|
+
@tti_conn.connect(connect_opts)
|
23
|
+
rescue
|
24
|
+
(@tti_conn.disconnect rescue nil) unless @tti_conn.nil?
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
Net::TTI.logger.info("TTI connection established. #{@tti_conn.conn_params}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticate( username, password )
|
31
|
+
pre_auth_request = get_pre_auth_request( username )
|
32
|
+
|
33
|
+
pre_auth_response_raw = @tti_conn.send_and_receive(pre_auth_request)
|
34
|
+
pre_auth_response = PreAuthenticationResponse.read(pre_auth_response_raw)
|
35
|
+
|
36
|
+
auth_request = get_auth_request(username, password, pre_auth_response)
|
37
|
+
|
38
|
+
begin
|
39
|
+
@tti_conn.send_and_receive(auth_request)
|
40
|
+
rescue Exceptions::ErrorMessageReceived => error
|
41
|
+
case error.error_code
|
42
|
+
when 1017, 9275
|
43
|
+
raise Exceptions::InvalidCredentialsError.new( error.message )
|
44
|
+
when 28000
|
45
|
+
raise Exceptions::AccountLockedOutError.new( error.message )
|
46
|
+
when 28001
|
47
|
+
raise Exceptions::PasswordExpiredError.new( error.message )
|
48
|
+
when 28002
|
49
|
+
# This is "password will expire in 7 days," i.e. a successful login
|
50
|
+
else
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_pre_auth_request(username)
|
59
|
+
pre_auth_request = Authentication.create_pre_auth_request( @tti_conn.conn_params.architecture )
|
60
|
+
pre_auth_request.username = username
|
61
|
+
return pre_auth_request
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_auth_request(username, password, pre_auth_response)
|
65
|
+
auth_sesskey = pre_auth_response.auth_sesskey
|
66
|
+
|
67
|
+
case @tti_conn.conn_params.tns_version
|
68
|
+
when Net::TNS::Version::VERSION_10G
|
69
|
+
enc_password, enc_client_session_key = Net::TTI::Crypto.get_10g_auth_values( username, password, auth_sesskey )
|
70
|
+
when Net::TNS::Version::VERSION_11G
|
71
|
+
auth_vfr_data = pre_auth_response.auth_vfr_data
|
72
|
+
enc_password, enc_client_session_key = Net::TTI::Crypto.get_11g_auth_values( password, auth_sesskey, auth_vfr_data )
|
73
|
+
else
|
74
|
+
raise Exceptions::UnsupportedTNSVersion.new( @tti_conn.conn_params.tns_version )
|
75
|
+
end
|
76
|
+
|
77
|
+
auth_request = Authentication.create_auth_request( @tti_conn.conn_params.architecture )
|
78
|
+
auth_request.username = username
|
79
|
+
auth_request.enc_client_session_key = enc_client_session_key
|
80
|
+
auth_request.enc_password = enc_password
|
81
|
+
|
82
|
+
return auth_request
|
83
|
+
end
|
84
|
+
|
85
|
+
def disconnect
|
86
|
+
@tti_conn.disconnect() unless @tti_conn.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "net/tns"
|
2
|
+
require "net/tti/message"
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module TTI
|
6
|
+
class ConnectionParameters
|
7
|
+
attr_accessor :platform
|
8
|
+
attr_accessor :architecture
|
9
|
+
attr_accessor :ttc_version
|
10
|
+
attr_accessor :tns_version
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
return "Platform: #{@platform||nil}" +
|
14
|
+
"; Architecture: #{@architecture||nil}" +
|
15
|
+
"; TTC? Version: #{@ttc_version||nil}" +
|
16
|
+
"; TNS Version: #{@tns_version||nil}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Connection
|
21
|
+
attr_reader :conn_params
|
22
|
+
|
23
|
+
def initialize(opts={})
|
24
|
+
Net::TTI.logger.debug("Creating new TNS Connection")
|
25
|
+
@tns_connection = Net::TNS::Connection.new(opts)
|
26
|
+
@conn_params = ConnectionParameters.new()
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect(opts={})
|
30
|
+
Net::TTI.logger.debug("Connection#connect called")
|
31
|
+
@tns_connection.connect(opts)
|
32
|
+
@conn_params.tns_version = @tns_connection.tns_protocol_version
|
33
|
+
|
34
|
+
Net::TTI.logger.debug("Sending protocol negotiation request")
|
35
|
+
proto_nego_request = ProtocolNegotiationRequest.create_request()
|
36
|
+
proto_nego_response_raw = send_and_receive( proto_nego_request )
|
37
|
+
|
38
|
+
proto_nego_response = ProtocolNegotiationResponse.read( proto_nego_response_raw )
|
39
|
+
proto_nego_response.populate_connection_parameters( @conn_params )
|
40
|
+
|
41
|
+
Net::TTI.logger.debug("Sending data type negotiation request")
|
42
|
+
dt_nego_request = DataTypeNegotiationRequest.create_request( @conn_params.platform )
|
43
|
+
dt_nego_response_raw = send_and_receive( dt_nego_request )
|
44
|
+
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def disconnect()
|
49
|
+
@tns_connection.disconnect
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_and_receive( tti_message )
|
53
|
+
send_tti_message(tti_message)
|
54
|
+
receive_tti_message()
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sends a TTI message. This function takes a TTI payload, embeds it in one
|
58
|
+
# or more TNS Data packets and sends those packets.
|
59
|
+
def send_tti_message( tti_message )
|
60
|
+
raw_message = tti_message.to_binary_s
|
61
|
+
Net::TTI.logger.debug( "Connection#send_tti_message called with #{raw_message.length}-byte #{tti_message.class} message" )
|
62
|
+
|
63
|
+
# Split the message into multiple packets if necessary
|
64
|
+
max_data = Net::TNS::DataPacket.max_data_length
|
65
|
+
raw_message.scan(/.{1,#{max_data}}/m).each do |raw_message_part|
|
66
|
+
tns_packet = Net::TNS::DataPacket.new()
|
67
|
+
tns_packet.data = raw_message_part
|
68
|
+
Net::TTI.logger.debug( "Sending data packet (#{tns_packet.num_bytes} bytes total)" )
|
69
|
+
@tns_connection.send_tns_packet( tns_packet )
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def receive_tti_message( waiting_for_error_message = false, max_message_length=10_000 )
|
74
|
+
# This is structured as a loop in order to handle messages (e.g. Markers)
|
75
|
+
# that need to be handled without returning to the caller. To keep a malicious
|
76
|
+
# server from making us loop continuously, we set an arbitrary limit of
|
77
|
+
# 10 loops without a "real" message before we throw an exception.
|
78
|
+
receive_count = 0
|
79
|
+
while ( true )
|
80
|
+
receive_count += 1
|
81
|
+
if ( receive_count >= 3 )
|
82
|
+
raise Exceptions::TnsException.new( "Maximum receive attempts exceeded - too many Markers received." )
|
83
|
+
end
|
84
|
+
|
85
|
+
Net::TTI.logger.debug("Attempting to receive packet (try ##{receive_count})")
|
86
|
+
case tns_packet = @tns_connection.receive_tns_packet()
|
87
|
+
when Net::TNS::DataPacket
|
88
|
+
message_data = tns_packet.data
|
89
|
+
# If this is a long packet, the data may have hit the max length and
|
90
|
+
# carried into an additional packet. We wouldn't need to do this if
|
91
|
+
# we could fully parse every message (or at least know lengths to
|
92
|
+
# read). I'm looking at you, DataTypeNegotiationResponse.
|
93
|
+
if tns_packet.num_bytes > 2000
|
94
|
+
begin
|
95
|
+
max_message_length -= message_data.length
|
96
|
+
message_data += receive_tti_message(waiting_for_error_message, max_message_length)
|
97
|
+
rescue Net::TNS::Exceptions::ReceiveTimeoutExceeded
|
98
|
+
Net::TTI.logger.debug("Hit receive timeout trying to read another Data packet")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
return message_data
|
103
|
+
|
104
|
+
# We received an error notification
|
105
|
+
when Net::TNS::MarkerPacket
|
106
|
+
Net::TTI.logger.info("Received MarkerPacket")
|
107
|
+
# TNS Markers seem to come in pairs. If we've already got one and sent
|
108
|
+
# the request for the error message, we'll ignore subsequent markers
|
109
|
+
# until we get the error message.
|
110
|
+
unless waiting_for_error_message
|
111
|
+
error_message = get_error_message()
|
112
|
+
raise Exceptions::ErrorMessageReceived.new( error_message )
|
113
|
+
end
|
114
|
+
|
115
|
+
# We received something else
|
116
|
+
else
|
117
|
+
Net::TTI.logger.warn("Received #{tns_packet.class} instead of Data")
|
118
|
+
if waiting_for_error_message
|
119
|
+
raise Exceptions::ProtocolException.new( "Invalid response while waiting for error message - got #{tns_packet.class}" )
|
120
|
+
else
|
121
|
+
raise Exceptions::ProtocolException.new( "Received #{tns_packet.class} instead of TNS data packet (for TTI)" )
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_error_message
|
128
|
+
error_request = Net::TNS::MarkerPacket.create_request()
|
129
|
+
@tns_connection.send_tns_packet( error_request )
|
130
|
+
|
131
|
+
raw_response = receive_tti_message(true)
|
132
|
+
response = Message.from_data_string(raw_response)
|
133
|
+
|
134
|
+
unless response.is_a?(ErrorMessage)
|
135
|
+
raise Exceptions::ProtocolException.new( "Received #{response.class} instead of error message" )
|
136
|
+
end
|
137
|
+
|
138
|
+
return response.message
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|