├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── bin ├── tns_oradb_version.rb ├── tns_sid_list.rb └── tns_sid_probing.rb ├── lib └── net │ ├── tns.rb │ ├── tns │ ├── client.rb │ ├── connection.rb │ ├── exceptions.rb │ ├── gem_version.rb │ ├── helpers │ │ └── string_helpers.rb │ ├── packet.rb │ ├── packets │ │ ├── abort_packet.rb │ │ ├── accept_packet.rb │ │ ├── ack_packet.rb │ │ ├── attention_packet.rb │ │ ├── connect_packet.rb │ │ ├── control_packet.rb │ │ ├── data_packet.rb │ │ ├── marker_packet.rb │ │ ├── null_packet.rb │ │ ├── redirect_packet.rb │ │ ├── refuse_packet.rb │ │ └── resend_packet.rb │ └── version.rb │ ├── tti.rb │ └── tti │ ├── capabilities.rb │ ├── client.rb │ ├── connection.rb │ ├── crypto.rb │ ├── data_types.rb │ ├── data_types │ ├── chunked_string.rb │ ├── flags.rb │ └── key_value_pair.rb │ ├── exceptions.rb │ ├── message.rb │ └── messages │ ├── data_type_negotiation_request.rb │ ├── data_type_negotiation_response.rb │ ├── error_message.rb │ ├── function_call.rb │ ├── function_calls │ ├── authentication.rb │ └── pre_authentication_response.rb │ ├── protocol_negotiation_request.rb │ └── protocol_negotiation_response.rb ├── net-tns.gemspec ├── notes.md └── spec ├── net ├── tns │ ├── client_spec.rb │ ├── connection_spec.rb │ ├── packet_spec.rb │ ├── packet_spec_helper.rb │ ├── packets │ │ ├── abort_packet_spec.rb │ │ ├── accept_packet_spec.rb │ │ ├── ack_packet_spec.rb │ │ ├── attention_packet_spec.rb │ │ ├── connect_packet_spec.rb │ │ ├── control_packet_spec.rb │ │ ├── data_packet_spec.rb │ │ ├── marker_packet_spec.rb │ │ ├── null_packet_spec.rb │ │ ├── redirect_packet_spec.rb │ │ ├── refuse_packet_spec.rb │ │ └── resend_packet_spec.rb │ └── raw │ │ ├── accept.raw │ │ ├── accept_with_data.raw │ │ ├── ano_negotiation_request.raw │ │ ├── ano_negotiation_response.raw │ │ ├── connect.raw │ │ ├── data_disconnect.raw │ │ ├── data_query_request.raw │ │ ├── data_query_response.raw │ │ ├── marker_request.raw │ │ ├── marker_response.raw │ │ ├── redirect.raw │ │ ├── refuse.raw │ │ └── resend.raw └── tti │ ├── crypto_spec.rb │ ├── data_types │ ├── chunked_string_spec.rb │ └── key_value_pair_spec.rb │ ├── messages │ ├── data_type_negotiation_request_spec.rb │ ├── data_type_negotiation_response_spec.rb │ ├── error_message_spec.rb │ ├── function_calls │ │ ├── authentication_spec.rb │ │ ├── pre_authentication_request_spec.rb │ │ └── pre_authentication_response_spec.rb │ ├── protocol_negotiation_request_spec.rb │ └── protocol_negotiation_response_spec.rb │ └── raw │ ├── data_type_negotiation_request_linux.raw │ ├── data_type_negotiation_request_solaris_11g2.raw │ ├── data_type_negotiation_request_windows_10g.raw │ ├── data_type_negotiation_request_windows_9i.raw │ ├── data_type_negotiation_response_linux.raw │ ├── data_type_negotiation_response_windows.raw │ ├── protocol_negotiation_response_linux_10g.raw │ ├── protocol_negotiation_response_linux_11g.raw │ ├── protocol_negotiation_response_linux_11g_r2.raw │ ├── protocol_negotiation_response_windows_10g.raw │ ├── protocol_negotiation_response_windows_11g.raw │ └── stub.raw ├── spec_helper.rb ├── tns_spec_helper.rb └── tti_spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swn 2 | *.swp 3 | *.swo 4 | *~ 5 | *# 6 | *#* 7 | .DS_Store 8 | .DS_Store? 9 | 10 | .rvmrc 11 | .ruby-version 12 | .ruby-gemset 13 | .rspec 14 | 15 | Gemfile.lock 16 | 17 | coverage 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false # Use Travis containers 3 | rvm: 4 | - 2.4.1 5 | - 2.3.4 6 | - 2.2.7 7 | - 2.1 8 | - 2.1.1 9 | # - 2.1.0 - commented out pending resolution of Issue #7 10 | script: bundle exec rspec spec 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org/" 2 | 3 | gemspec :name => 'net-tns' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Net::TNS for Ruby [![Build Status](https://travis-ci.org/SpiderLabs/net-tns.svg?branch=master)](https://travis-ci.org/SpiderLabs/net-tns) [![Coverage Status](https://coveralls.io/repos/SpiderLabs/net-tns/badge.svg?branch=master&service=github)](https://coveralls.io/github/SpiderLabs/net-tns?branch=master) 2 | 3 | Library for connecting to Oracle databases. 4 | 5 | ## Description 6 | 7 | Net::TNS for Ruby is a partial implementation of the TNS (Transparent Network Substrate) and TTI (Two-Task Interface, a.k.a. TTC) protocols used by Oracle Database. It allows users to connect and authenticate to databases; functionality beyond that point is limited. The library implements two protocols, and although TTI is the higher-level one, the library is named for TNS, which is more well-known. 8 | 9 | ## Requirements 10 | 11 | Net::TNS was written for and has been most tested with Ruby 2. The specs pass on 1.9.3, although their coverage is incomplete. It may be able to work with previous versions. 12 | 13 | ## Installation 14 | 15 | ```gem install net-tns``` 16 | 17 | ## Use 18 | 19 | Because TNS and TTI are highly related (and not very useful without the other), they are both implemented in this library, although in separate namespaces, Net::TNS and Net::TTI (```require "net/tti"``` is sufficient to load both protocols, while ```require "net/tns"``` will only load the TNS implementation). 20 | 21 | Each namespace includes a Client class, which provides access to the essential functionality for that protocol. 22 | 23 | Examples: 24 | 25 | #### Get the version of an Oracle DB server 26 | 27 | ```ruby 28 | require "net/tns" # require "net/tti" would work as well 29 | Net::TNS::Client.get_version(:host => "10.0.0.10") # => "11.2.0.2.0" 30 | ``` 31 | 32 | #### Try authenticating to a server 33 | 34 | ```ruby 35 | require "net/tti" # requiring TTI is necessary to get the higher-level functionality 36 | tti_client = Net::TTI::Client.new 37 | tti_client.connect( :host => "10.0.0.10", :sid => "ORCL" ) 38 | begin 39 | tti_client.authenticate( "jsmith", "bananas" ) 40 | # Authentication succeeded 41 | rescue Net::TTI::Exceptions::InvalidCredentialsError 42 | # Handle invalid credentials 43 | end 44 | tti_client.disconnect 45 | ``` 46 | 47 | ## Contribute 48 | 49 | Pull requests welcome! Once you've forked and cloned the project, you can ```bundle install``` to take care of the dependencies; after that, you're ready to code. 50 | 51 | You can also create issues for any bugs or feature requests, but they may take longer to get done, of course. 52 | 53 | ## Resources on TNS and TTI 54 | 55 | * http://www.csee.umbc.edu/portal/help/oracle8/network.815/a67440/ch2.htm#1007271 56 | * http://www.pythian.com/blog/repost-oracle-protocol/ 57 | * http://www.nyoug.org/Presentations/2008/Sep/Harris_Listening%20In.pdf 58 | * https://community.oracle.com/thread/555302?start=0&tstart=0 (for naming of high-level components) 59 | * http://ckng62.blogspot.com/2014/02/tns-data-packet-structure.html 60 | * https://www.thesprawl.org/research/oracle-tns-protocol/ 61 | * The Oracle Hacker's Handbook by David Litchfield 62 | -------------------------------------------------------------------------------- /bin/tns_oradb_version.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "net/tns" 4 | 5 | if (ARGV.size < 1 or ARGV[0] == '-h') 6 | STDERR.puts("Usage: #{File.basename $0} host [port]") 7 | exit 1 8 | end 9 | 10 | opts = {} 11 | opts[:host] = ARGV.shift 12 | port = ARGV.shift 13 | opts[:port] = port.to_i unless port.nil? 14 | 15 | puts Net::TNS::Client.get_version(opts) 16 | -------------------------------------------------------------------------------- /bin/tns_sid_list.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "net/tns" 4 | require "set" 5 | 6 | if (ARGV.size < 1 or ARGV[0] == '-h') 7 | STDERR.puts("Usage: #{File.basename $0} host [port]") 8 | exit 1 9 | end 10 | 11 | opts = {} 12 | opts[:host] = ARGV.shift 13 | port = ARGV.shift 14 | opts[:port] = port.to_i unless port.nil? 15 | 16 | begin 17 | sids = Set.new 18 | service_names = Set.new 19 | 20 | status_raw = Net::TNS::Client.get_status(opts) 21 | status_raw.scan(/(INSTANCE|SERVICE)_NAME=([^\)]+)/).each do |type, name| 22 | case type 23 | when "INSTANCE" 24 | sids << name 25 | when "SERVICE" 26 | service_names << name 27 | end 28 | end 29 | 30 | unless sids.empty? 31 | puts "SIDs:\n " + sids.to_a.join("\n ") 32 | end 33 | 34 | unless service_names.empty? 35 | puts "Service Names:\n " + service_names.to_a.join("\n ") 36 | end 37 | 38 | if sids.empty? && service_names.empty? 39 | puts "No SIDs or service names received." 40 | end 41 | rescue Net::TNS::Exceptions::RefuseMessageReceived 42 | warn "Server refused the request" 43 | end 44 | -------------------------------------------------------------------------------- /bin/tns_sid_probing.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "net/tns" 4 | require "set" 5 | 6 | if (ARGV.size < 1 or ARGV[0] == '-h') 7 | STDERR.puts("Usage: #{File.basename $0} host [port]") 8 | exit 1 9 | end 10 | 11 | @conn_opts = {} 12 | @conn_opts[:host] = ARGV.shift 13 | port = ARGV.shift 14 | @conn_opts[:port] = port.to_i unless port.nil? 15 | 16 | def valid_sid?(name) 17 | begin 18 | conn = Net::TNS::Connection.new(@conn_opts) 19 | conn.connect(:sid=>name) 20 | return true 21 | rescue Net::TNS::Exceptions::RefuseMessageReceived 22 | ensure 23 | conn.disconnect() if conn 24 | end 25 | return false 26 | end 27 | 28 | 29 | dictionary = [ 30 | "CLRExtProc", 31 | "ORACLE", 32 | "ORADB", 33 | "ORCL", 34 | "PLSExtProc", 35 | "TEST", 36 | "XDB", 37 | "XE" 38 | ] 39 | 40 | sids = Set.new 41 | begin 42 | dictionary.each do |name| 43 | sids << name if valid_sid?(name) 44 | end 45 | 46 | ensure 47 | unless sids.empty? 48 | puts "SIDs:\n " + sids.to_a.join("\n ") 49 | end 50 | 51 | if sids.empty? 52 | puts "No valid SIDs found" 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/net/tns.rb: -------------------------------------------------------------------------------- 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 21 | -------------------------------------------------------------------------------- /lib/net/tns/client.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class Client 4 | def self.ping?(opts={}) 5 | begin 6 | conn = Connection.new(opts) 7 | conn.open_socket() 8 | request = ConnectPacket.new(:data => "(CONNECT_DATA=(COMMAND=ping))") 9 | conn.send_and_receive( request ) 10 | return true 11 | rescue Exceptions::ConnectionClosed 12 | return false 13 | rescue Exceptions::TNSException 14 | return true 15 | ensure 16 | conn.close_socket() unless conn.nil? 17 | end 18 | end 19 | 20 | def self.get_version(opts={}) 21 | begin 22 | conn = Connection.new(opts) 23 | conn.open_socket() 24 | request = ConnectPacket.new(:data => "(CONNECT_DATA=(COMMAND=VERSION))") 25 | 26 | response = conn.send_and_receive( request ) 27 | raise Exceptions::ProtocolException.new("Expected AcceptPacket in response (got #{response.class})") unless response.is_a?(AcceptPacket) 28 | 29 | version_data = response.data 30 | rescue Exceptions::RefuseMessageReceived => refuse_err 31 | version_data = refuse_err.message 32 | ensure 33 | conn.close_socket() unless conn.nil? 34 | end 35 | 36 | return nil if version_data.nil? 37 | 38 | if ( version_match = version_data.match( /Version ((?:\d+.)+\d+)/ ) ) then 39 | return version_match[1] 40 | # If that didn't work, see if we got an encoded version number 41 | elsif ( version_match = version_data.match( /\(VSNNUM=(\d+)\)/ ) ) then 42 | return parse_vsnnum(version_match[1]) 43 | end 44 | end 45 | 46 | def self.get_status(opts={}) 47 | begin 48 | conn = Connection.new(opts) 49 | conn.open_socket() 50 | request = ConnectPacket.new(:data => "(CONNECT_DATA=(COMMAND=STATUS)(VERSION=186647552))") 51 | 52 | status_response_raw = "" 53 | begin 54 | response = conn.send_and_receive( request ) 55 | rescue EOFError 56 | # The first packet sent is an Accept with an unknown structure. 57 | # Attempting to parse it as an Accept results in an EOFError, due to 58 | # a data length that is greater than the available data. 59 | end 60 | 61 | response = conn.send_and_receive(ResendPacket.new()) 62 | status_response_raw += response.data 63 | # Successful responses are typically spread across multiple Data packets. 64 | while ( response = conn.receive_tns_packet() ) 65 | break unless response.is_a?(DataPacket) 66 | break if response.flags == 0x0040 67 | status_response_raw += response.data 68 | end 69 | return status_response_raw 70 | ensure 71 | conn.close_socket() unless conn.nil? 72 | end 73 | end 74 | 75 | # Parse the "VSNNUM" component of certain TNS listener responses, which 76 | # contains an encoded form of the version number. 77 | def self.parse_vsnnum(vsnnum_string) 78 | # The VSNNUM is a really insane way of encoding the version number. It 79 | # is a decimal number (e.g. 169869568) that, in hex (e.g. A200100), 80 | # is a weird representation of the dotted version 81 | # (e.g. 169869568 -> A200100 -> A.2.0.01.00 -> A.2.0.1.0 -> 10.2.0.1.0). 82 | raise ArgumentError unless vsnnum_string.is_a?(String) 83 | vsnnum_decimal = vsnnum_string.to_i 84 | 85 | version_components = [] 86 | version_components << (( vsnnum_decimal >> 24 ) & 0xFF) 87 | version_components << (( vsnnum_decimal >> 20 ) & 0xF) 88 | version_components << (( vsnnum_decimal >> 16 ) & 0xF) 89 | version_components << (( vsnnum_decimal >> 8 ) & 0xFF) 90 | version_components << (( vsnnum_decimal >> 0 ) & 0xFF) 91 | 92 | return version_components.join('.') 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/net/tns/connection.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet" 2 | require "net/tns/exceptions" 3 | 4 | module Net 5 | module TNS 6 | class Connection 7 | attr_reader :tns_protocol_version 8 | attr_reader :tns_sdu 9 | 10 | def initialize(opts={}) 11 | @socket = nil 12 | 13 | @host = opts.delete(:host) 14 | @port = opts.delete(:port) || 1521 15 | @new_socket_proc = opts.delete(:new_socket_proc) 16 | 17 | raise ArgumentError.new("Unrecognized options: #{opts.keys}") unless opts.empty? 18 | 19 | if @host.nil? == @new_socket_proc.nil? 20 | raise ArgumentError.new("Invalid socket options. Need :host and :port, OR :new_socket_proc") 21 | end 22 | end 23 | 24 | # This is a low-level function to directly open a socket for this connection. 25 | # Most callers should use #connect instead, which will open a socket and 26 | # negotiate a TNS connection. 27 | def open_socket 28 | Net::TNS.logger.debug("Connection#open_socket called") 29 | close_socket() 30 | 31 | if @host 32 | require "socket" 33 | Net::TNS.logger.info("Creating new TCPSocket for #{@host}:#{@port}") 34 | @socket = TCPSocket.new(@host, @port) 35 | elsif @new_socket_proc 36 | Net::TNS.logger.info("Calling new-socket proc for new socket") 37 | @socket = @new_socket_proc.call() 38 | else 39 | raise ArgumentError.new("Invalid socket options") 40 | end 41 | 42 | return 43 | end 44 | 45 | # This is a low-level function to directly close the socket for this connection. 46 | # Most callers should use #disconnect instead, which will disconnect the 47 | # TNS connection before closing the socket. 48 | def close_socket 49 | Net::TNS.logger.debug("Connection#close_socket called") 50 | begin 51 | unless @socket.nil? or @socket.closed? 52 | Net::TNS.logger.info("Closing socket") 53 | @socket.close 54 | end 55 | ensure 56 | @socket = nil 57 | end 58 | end 59 | 60 | def connect(opts={}) 61 | sid = opts.delete(:sid) if opts.has_key?(:sid) 62 | service_name = opts.delete(:service_name) if opts.has_key?(:service_name) 63 | raise ArgumentError.new("Unrecognized opts: #{opts.keys}") unless opts.empty? 64 | raise ArgumentError.new("Must specify :sid or :service_name") unless sid.nil? != service_name.nil? 65 | 66 | open_socket() if @socket.nil? 67 | dst_host = @socket.peeraddr[3] 68 | dst_port = @socket.peeraddr[1] 69 | 70 | if sid 71 | Net::TNS.logger.debug("Connecting to target by SID (""#{sid}"")") 72 | connect_packet = ConnectPacket.make_connection_by_sid( dst_host, dst_port, sid ) 73 | elsif service_name 74 | Net::TNS.logger.debug("Connecting to target by service name (""#{service_name}"")") 75 | connect_packet = ConnectPacket.make_connection_by_service_name( dst_host, dst_port, service_name ) 76 | end 77 | 78 | response = send_and_receive(connect_packet) 79 | unless response.is_a?(AcceptPacket) || response.is_a?(RedirectPacket) 80 | raise Exceptions::ProtocolException.new("Unexpected response to Connect packet: #{response.class}") 81 | end 82 | if response.is_a?(RedirectPacket) 83 | # CLR extproc on 12c will end up here 84 | return 85 | end 86 | @tns_protocol_version = response.version.to_i 87 | @tns_sdu = response.sdu_size.to_i 88 | negotiate_ano() 89 | end 90 | 91 | def disconnect 92 | begin 93 | packet = DataPacket.make_disconnect_request 94 | send_tns_packet( packet ) 95 | ensure 96 | close_socket() 97 | end 98 | end 99 | 100 | # Perform negotiation of Additional Network Options ("SNS" in Wireshark). 101 | # This is actually a different layer from TNS, but we're basically 102 | # ignoring it anyway, so there's no sense factoring it out of Net::TNS (yet). 103 | def negotiate_ano 104 | # create request for no ANO 105 | request = DataPacket.new() 106 | request.data = ("deadbeef00920a2001000004000004000300000000000400050a200100000800" + 107 | "0100000b58884d7db000120001deadbeef000300000004000400010001000200" + 108 | "01000300000000000400050a20010000020003e0e100020006fcff0002000200" + 109 | "000000000400050a200100000c0001001106100c0f0a0b080201030003000200" + 110 | "000000000400050a20010000030001000301").tns_unhexify 111 | 112 | Net::TNS.logger.debug("Sending ANO negotiation request") 113 | response = send_and_receive(request) 114 | unless response.is_a?(DataPacket) && response.data.start_with?("deadbeef".tns_unhexify) 115 | raise Exceptions::ProtocolException.new("Unexpected response to ANO request") 116 | end 117 | return response.data 118 | end 119 | private :negotiate_ano 120 | 121 | 122 | 123 | def send_and_receive( packet ) 124 | send_tns_packet( packet ) 125 | receive_tns_packet() 126 | end 127 | 128 | # @param packet [Net::TNS::Packet] 129 | def send_tns_packet( packet ) 130 | if @socket.nil? || @socket.closed? 131 | Net::TNS.logger.warn( "Can't send packet to a closed or nil socket!" ) 132 | return 133 | end 134 | # Store this in case we get a Resend 135 | @tns_last_sent_packet = packet 136 | Net::TNS.logger.debug( "Sending packet #{packet.class} (#{packet.num_bytes} bytes)" ) 137 | @socket.write( packet.to_binary_s ) 138 | end 139 | 140 | def resend_last_tns_packet 141 | if @tns_last_sent_packet.nil? 142 | raise Exceptions::TNSException.new( "Resend received without a packet to resend" ) 143 | end 144 | send_tns_packet( @tns_last_sent_packet ) 145 | end 146 | private :resend_last_tns_packet 147 | 148 | # Attempts to receive a TNS message. 149 | # 150 | # @param [Boolean] (Optional) Indicates a special state in which an error 151 | # notification has been received, and we expect to be receiving a response 152 | # to a request for the error message. 153 | # @return [Net::TNS::Packet] 154 | # @raise [Net::TNS::Exceptions::RefuseMessageReceived] If the other side 155 | # sent TNS refuse message. 156 | # @raise [Net::TNS::Exceptions::TNSException] If another unexpected 157 | # state or action occurs. 158 | def receive_tns_packet( waiting_for_error_message = false ) 159 | # This is structured as a loop in order to handle messages (e.g. Resends) 160 | # that need to be handled without returning to the caller. To keep a malicious 161 | # server from making us loop continuously, we set an arbitrary limit of 162 | # 10 loops without a "real" message before we throw an exception. 163 | receive_count = 0 164 | while ( true ) 165 | raise Exceptions::ConnectionClosed if @socket.closed? 166 | 167 | receive_count += 1 168 | if ( receive_count >= 3 ) 169 | raise Exceptions::TNSException.new( "Maximum receive attempts exceeded - too many Resends received." ) 170 | end 171 | 172 | # Try to receive a TNS packet 173 | Net::TNS.logger.debug("Attempting to receive packet (try ##{receive_count})") 174 | packet = Net::TNS::Packet.from_socket(@socket) 175 | 176 | case packet 177 | when Net::TNS::RefusePacket 178 | Net::TNS.logger.warn("Received RefusePacket") 179 | raise Exceptions::RefuseMessageReceived.new( packet.data ) 180 | 181 | # # We received a redirect request (typical of Oracle 9 and possibly previous versions) 182 | # when Net::TNS::RedirectPacket 183 | # raise Exceptions::RedirectMessageReceived.new( packet.data ) 184 | 185 | # We received a request to resend the last packet 186 | when Net::TNS::ResendPacket 187 | Net::TNS.logger.debug("Received ResendPacket") 188 | # Re-send the last packet and then loop again 189 | resend_last_tns_packet() 190 | 191 | # We received a normal response 192 | else 193 | Net::TNS.logger.debug("Received #{packet.class} (#{packet.num_bytes} bytes)") 194 | return packet 195 | end 196 | end 197 | end 198 | end 199 | end 200 | end 201 | -------------------------------------------------------------------------------- /lib/net/tns/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Net::TNS 2 | module Exceptions 3 | class TNSException < StandardError 4 | end 5 | 6 | class ProtocolException < TNSException 7 | end 8 | 9 | 10 | class ReceiveTimeoutExceeded < TNSException 11 | end 12 | 13 | class ConnectionClosed < TNSException 14 | end 15 | 16 | class RefuseMessageReceived < TNSException 17 | end 18 | 19 | class RedirectMessageReceived < TNSException 20 | attr_reader :new_port 21 | attr_reader :new_host 22 | 23 | def initialize( message ) 24 | super( message ) 25 | 26 | host_matches = /\(HOST=([^\)]+)\)/.match( self.message ) 27 | @new_host = host_matches[1] unless host_matches.nil? 28 | 29 | port_matches = /\(PORT=(\d{1,5})\)/.match( self.message ) 30 | @new_port = port_matches[1] unless port_matches.nil? 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/net/tns/gem_version.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | VERSION = "1.1.4" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/net/tns/helpers/string_helpers.rb: -------------------------------------------------------------------------------- 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 | # Adapted from the Ruby Black Bag (http://github.com/emonti/rbkb/) 8 | # Convert a string to ASCII hex string 9 | def tns_hexify 10 | self.each_byte.map do |byte| 11 | (HEXCHARS[(byte >> 4)] + HEXCHARS[(byte & 0xf )]) 12 | end.join() 13 | end 14 | 15 | # Convert ASCII hex string to raw. 16 | # 17 | # Parameters: 18 | # 19 | # d = optional 'delimiter' between hex bytes (zero+ spaces by default) 20 | def tns_unhexify(d=/\s*/) 21 | self.strip.gsub(/([A-Fa-f0-9]{1,2})#{d}?/) { $1.hex.chr } 22 | end 23 | end 24 | end 25 | 26 | class String 27 | include Net::TNS::StringHelpers 28 | end 29 | -------------------------------------------------------------------------------- /lib/net/tns/packet.rb: -------------------------------------------------------------------------------- 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 = 8192 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 ArgumentError.new("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 | header_length = header_raw.length unless header_raw.nil? 50 | raise Exceptions::ProtocolException.new("Failed to read complete header. Read #{header_length.to_i} bytes.") 51 | end 52 | 53 | header = Header.new() 54 | header.read( header_raw ) 55 | Net::TNS.logger.debug("Read header. Reported packet length is #{header.packet_length} bytes") 56 | if header.packet_length > SESSION_DATA_UNIT_SIZE 57 | raise Exceptions::ProtocolException.new("Packet length in header (#{header.packet_length}) is longer than SDU size.") 58 | end 59 | 60 | payload_raw = socket.read( header.packet_length - Header::LENGTH ) 61 | packet_raw = header_raw + payload_raw 62 | 63 | unless payload_class = @@tns_packet_classes[ header.packet_type ] 64 | raise Net::TNS::Exceptions::TNSException.new( "Unknown TNS packet type: #{header.packet_type}" ) 65 | end 66 | 67 | unless packet_raw.length == header.packet_length 68 | raise Net::TNS::Exceptions::ProtocolException.new("Failed to read entire packet (read #{packet_raw.length} of #{header.packet_length} bytes).") 69 | end 70 | 71 | new_packet = payload_class.read( packet_raw ) 72 | return new_packet 73 | end 74 | 75 | def update_header() 76 | self.header.packet_type = @@tns_packet_types[self.class] 77 | self.header.packet_length = self.num_bytes 78 | end 79 | 80 | def to_binary_s() 81 | update_header() 82 | return super 83 | end 84 | end 85 | end 86 | end 87 | 88 | require "pathname" 89 | Dir.glob("#{Pathname.new(__FILE__).dirname}/packets/*.rb") { |file| require file } 90 | -------------------------------------------------------------------------------- /lib/net/tns/packets/abort_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class AbortPacket < Packet 4 | register_tns_type 9 5 | 6 | uint8 :user_reason 7 | uint8 :system_reason 8 | # not sure if this is correct, it's just what wireshark seems to do 9 | rest :data 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/net/tns/packets/accept_packet.rb: -------------------------------------------------------------------------------- 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 20 | -------------------------------------------------------------------------------- /lib/net/tns/packets/ack_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class AckPacket < Packet 4 | register_tns_type 3 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/net/tns/packets/attention_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class AttentionPacket < Packet 4 | register_tns_type 13 5 | 6 | rest :data 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/net/tns/packets/connect_packet.rb: -------------------------------------------------------------------------------- 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 => Net::TNS::Packet::SESSION_DATA_UNIT_SIZE 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 56 | -------------------------------------------------------------------------------- /lib/net/tns/packets/control_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class ControlPacket < Packet 4 | register_tns_type 14 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/net/tns/packets/data_packet.rb: -------------------------------------------------------------------------------- 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 21 | -------------------------------------------------------------------------------- /lib/net/tns/packets/marker_packet.rb: -------------------------------------------------------------------------------- 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 19 | -------------------------------------------------------------------------------- /lib/net/tns/packets/null_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class NullPacket < Packet 4 | register_tns_type 7 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/net/tns/packets/redirect_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class RedirectPacket < Packet 4 | register_tns_type 5 5 | 6 | uint16be :data_length 7 | string :data, :read_length => :data_length 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/net/tns/packets/refuse_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class RefusePacket < Packet 4 | register_tns_type 4 5 | 6 | uint8 :user_reason 7 | uint8 :system_reason 8 | uint16be :data_length 9 | string :data, :read_length => :data_length 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/net/tns/packets/resend_packet.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TNS 3 | class ResendPacket < Packet 4 | register_tns_type 11 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/net/tns/version.rb: -------------------------------------------------------------------------------- 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 16 | -------------------------------------------------------------------------------- /lib/net/tti.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/helpers/string_helpers" 2 | 3 | module Net 4 | module TTI 5 | require "pathname" 6 | Dir.glob("#{Pathname.new(__FILE__).dirname}/tti/*.rb") { |file| require file } 7 | 8 | def self.logger 9 | unless defined?(@@logger) 10 | require "logger" 11 | @@logger = Logger.new(STDERR) 12 | @@logger.progname = "Net::TTI" 13 | @@logger.sev_threshold = 6 unless $DEBUG 14 | end 15 | return @@logger 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/net/tti/capabilities.rb: -------------------------------------------------------------------------------- 1 | 2 | module Net 3 | module TTI 4 | class Capabilities 5 | def initialize(caps_bytes=[]) 6 | @caps_bytes = caps_bytes 7 | end 8 | 9 | def self.from_byte_array(bytes) 10 | Capabilities.new(bytes) 11 | end 12 | 13 | def self.from_binary_string(string) 14 | Capabilities.new( string.unpack("C*") ) 15 | end 16 | 17 | def [](index) 18 | @caps_bytes[index] 19 | end 20 | 21 | def []=(index, value) 22 | @caps_bytes[index] = value 23 | end 24 | 25 | def length 26 | @caps_bytes.length 27 | end 28 | 29 | def to_binary_s 30 | @caps_bytes.pack("C*") 31 | end 32 | 33 | # Returns hexified bytes, delimited by spaces 34 | # e.g. [0x01,0x41,0x81,0xa1] -> "01 41 81 a1" 35 | def to_hexified_s 36 | to_binary_s.scan(/../).join(" ") 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/net/tti/client.rb: -------------------------------------------------------------------------------- 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, :new_socket_proc] 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 | begin 32 | pre_auth_request = get_pre_auth_request( username ) 33 | 34 | pre_auth_response_raw = @tti_conn.send_and_receive(pre_auth_request) 35 | pre_auth_response = PreAuthenticationResponse.read(pre_auth_response_raw) 36 | 37 | auth_request = get_auth_request(username, password, pre_auth_response) 38 | 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() 60 | pre_auth_request.username = username 61 | pre_auth_request.add_parameter("AUTH_TERMINAL", "unknown") 62 | return pre_auth_request 63 | end 64 | 65 | def get_auth_request(username, password, pre_auth_response) 66 | auth_sesskey = pre_auth_response.auth_sesskey 67 | 68 | case @tti_conn.conn_params.tns_version 69 | when Net::TNS::Version::VERSION_10G 70 | enc_password, enc_client_session_key = Net::TTI::Crypto.get_10g_auth_values( username, password, auth_sesskey ) 71 | when Net::TNS::Version::VERSION_11G 72 | case auth_sesskey.length 73 | when 32 74 | enc_password, enc_client_session_key = Net::TTI::Crypto.get_10g_auth_values( username, password, auth_sesskey ) 75 | when 48 76 | auth_vfr_data = pre_auth_response.auth_vfr_data 77 | enc_password, enc_client_session_key = Net::TTI::Crypto.get_11g_auth_values( password, auth_sesskey, auth_vfr_data ) 78 | else 79 | raise Exceptions::ProtocolException.new("Unexpected AUTH_SESSKEY length #{auth_sesskey.length}") 80 | end 81 | else 82 | raise Exceptions::UnsupportedTNSVersion.new( @tti_conn.conn_params.tns_version ) 83 | end 84 | 85 | auth_request = Authentication.create_auth_request() 86 | auth_request.username = username 87 | auth_request.enc_password = enc_password 88 | auth_request.enc_client_session_key = enc_client_session_key 89 | auth_request.add_parameter("AUTH_TERMINAL", "unknown") 90 | return auth_request 91 | end 92 | 93 | def disconnect 94 | @tti_conn.disconnect() unless @tti_conn.nil? 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/net/tti/connection.rb: -------------------------------------------------------------------------------- 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 :ttc_server 11 | attr_accessor :tns_version 12 | attr_accessor :character_set 13 | attr_accessor :server_flags 14 | attr_accessor :server_compiletime_capabilities 15 | attr_accessor :server_runtime_capabilities 16 | 17 | def to_s 18 | return "Platform: #{@platform||nil}" + 19 | "; Architecture: #{@architecture||nil}" + 20 | "; TNS Version: #{@tns_version||nil}" + 21 | "; TTC server version: #{@ttc_version||nil}" + 22 | "; TTC server string: #{@ttc_server||nil}" + 23 | "; Server character set: #{@character_set||nil}" + 24 | "; Server flags: #{@server_flags||nil}" + 25 | "; Server Compiletime Capabilities: #{@server_compiletime_capabilities.to_hexified_s}" + 26 | "; Server Runtime Capabilities: #{@server_runtime_capabilities.to_hexified_s}" 27 | end 28 | end 29 | 30 | class Connection 31 | attr_reader :conn_params 32 | 33 | def initialize(opts={}) 34 | Net::TTI.logger.debug("Creating new TNS Connection") 35 | @tns_connection = Net::TNS::Connection.new(opts) 36 | @conn_params = ConnectionParameters.new() 37 | end 38 | 39 | def connect(opts={}) 40 | Net::TTI.logger.debug("Connection#connect called") 41 | @tns_connection.connect(opts) 42 | @conn_params.tns_version = @tns_connection.tns_protocol_version 43 | 44 | Net::TTI.logger.debug("Sending protocol negotiation request") 45 | proto_nego_request = ProtocolNegotiationRequest.create_request() 46 | proto_nego_response_raw = send_and_receive( proto_nego_request ) 47 | 48 | proto_nego_response = ProtocolNegotiationResponse.read( proto_nego_response_raw ) 49 | proto_nego_response.populate_connection_parameters( @conn_params ) 50 | 51 | Net::TTI.logger.debug("Sending data type negotiation request") 52 | dt_nego_request = DataTypeNegotiationRequest.create_request( @conn_params ) 53 | dt_nego_response_raw = send_and_receive( dt_nego_request ) 54 | 55 | return nil 56 | end 57 | 58 | def disconnect() 59 | @tns_connection.disconnect 60 | end 61 | 62 | def send_and_receive( tti_message ) 63 | send_tti_message(tti_message) 64 | receive_tti_message() 65 | end 66 | 67 | # Sends a TTI message. This function takes a TTI payload, embeds it in one 68 | # or more TNS Data packets and sends those packets. 69 | def send_tti_message( tti_message ) 70 | raw_message = tti_message.to_binary_s 71 | Net::TTI.logger.debug( "Connection#send_tti_message called with #{raw_message.length}-byte #{tti_message.class} message" ) 72 | 73 | # Split the message into multiple packets if necessary 74 | max_data = @tns_connection.tns_sdu - 12 # 12 = 10 (HEADER) + 2 (FLAGS) 75 | raw_message.scan(/.{1,#{max_data}}/m).each do |raw_message_part| 76 | tns_packet = Net::TNS::DataPacket.new() 77 | tns_packet.data = raw_message_part 78 | Net::TTI.logger.debug( "Sending data packet (#{tns_packet.num_bytes} bytes total)" ) 79 | @tns_connection.send_tns_packet( tns_packet ) 80 | end 81 | end 82 | 83 | def receive_tti_message( waiting_for_error_message = false, max_message_length=10_000 ) 84 | # This is structured as a loop in order to handle messages (e.g. Markers) 85 | # that need to be handled without returning to the caller. To keep a malicious 86 | # server from making us loop continuously, we set an arbitrary limit of 87 | # 10 loops without a "real" message before we throw an exception. 88 | receive_count = 0 89 | while ( true ) 90 | receive_count += 1 91 | if ( receive_count >= 3 ) 92 | raise Exceptions::ProtocolException.new( "Maximum receive attempts exceeded - too many Markers received." ) 93 | end 94 | 95 | Net::TTI.logger.debug("Attempting to receive packet (try ##{receive_count})") 96 | case tns_packet = @tns_connection.receive_tns_packet() 97 | when Net::TNS::DataPacket 98 | message_data = tns_packet.data 99 | # If this is a long packet, the data may have hit the max length and 100 | # carried into an additional packet. We wouldn't need to do this if 101 | # we could fully parse every message (or at least know lengths to 102 | # read). I'm looking at you, DataTypeNegotiationResponse. 103 | if @tns_connection.tns_protocol_version <= 313 && tns_packet.num_bytes > 1900 104 | begin 105 | max_message_length -= message_data.length 106 | message_data += receive_tti_message(waiting_for_error_message, max_message_length) 107 | rescue Net::TNS::Exceptions::ReceiveTimeoutExceeded 108 | Net::TTI.logger.debug("Hit receive timeout trying to read another Data packet") 109 | end 110 | end 111 | 112 | return message_data 113 | 114 | # We received an error notification 115 | when Net::TNS::MarkerPacket 116 | Net::TTI.logger.info("Received MarkerPacket") 117 | # TNS Markers seem to come in pairs. If we've already got one and sent 118 | # the request for the error message, we'll ignore subsequent markers 119 | # until we get the error message. 120 | unless waiting_for_error_message 121 | error_message = get_error_message() 122 | raise Exceptions::ErrorMessageReceived.new( error_message ) 123 | end 124 | 125 | # We received something else 126 | else 127 | Net::TTI.logger.warn("Received #{tns_packet.class} instead of Data") 128 | if waiting_for_error_message 129 | raise Exceptions::ProtocolException.new( "Invalid response while waiting for error message - got #{tns_packet.class}" ) 130 | else 131 | raise Exceptions::ProtocolException.new( "Received #{tns_packet.class} instead of TNS data packet (for TTI)" ) 132 | end 133 | end 134 | end 135 | end 136 | 137 | def get_error_message 138 | error_request = Net::TNS::MarkerPacket.create_request() 139 | @tns_connection.send_tns_packet( error_request ) 140 | 141 | raw_response = receive_tti_message(true) 142 | response = Message.from_data_string(raw_response) 143 | 144 | unless response.is_a?(ErrorMessage) 145 | raise Exceptions::ProtocolException.new( "Received #{response.class} instead of error message" ) 146 | end 147 | 148 | return response.message 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/net/tti/crypto.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | 3 | module Net::TTI 4 | class Crypto 5 | # Generates the encrypted password and encrypted client session key for 6 | # authentication with a 10g server. 7 | # 8 | # @param username [String] The username for authentication. 9 | # @param password [String] The password for authentication. 10 | # @param enc_server_session_key [String] The encrypted server session key. 11 | # provided by the server. This should be a 32-byte binary packed string. 12 | # @return [Array] The encrypted password and the encrypted client 13 | # session key to use to authenticate with the server. These are returned 14 | # as binary packed strings. 15 | def self.get_10g_auth_values( username, password, enc_server_session_key ) 16 | # Hash the password and pad it to 16 bytes. This will be used as the key 17 | # for encrypting the client session key and decrypting the server session key. 18 | password_hash = hash_password_10g( username, password ) 19 | password_hash += "\0" * 8 20 | 21 | # TODO: make random client session key 22 | client_session_key = "FAF5034314546426F329B1DAB1CDC5B8FF94349E0875623160350B0E13A0DA36".tns_unhexify 23 | 24 | # Encrypt client session key and decrypt the server session key, using the 25 | # password hash as a key. 26 | enc_client_session_key = openssl_encrypt( "AES-128-CBC", password_hash, nil, client_session_key ) 27 | server_session_key = openssl_decrypt( "AES-128-CBC", password_hash, nil, enc_server_session_key ) 28 | 29 | # Make the combined session key hash. This is used as the key to encrypt 30 | # the password. 31 | combo_session_key = create_combined_session_key_hash_10g( server_session_key, client_session_key ) 32 | 33 | # TODO: make random salt 34 | salt = "4C31AFE05F3B012C0AE9AB0CDFF0C508".tns_unhexify 35 | # Encrypt the salted password 36 | enc_password = openssl_encrypt( "AES-128-CBC", combo_session_key, nil, salt + password, true ) 37 | 38 | return enc_password, enc_client_session_key 39 | end 40 | 41 | # Generates the encrypted password and encrypted client session key for 42 | # authentication with an 11g server. 43 | # 44 | # @param password [String] The password for authentication. 45 | # @param enc_server_session_key [String] The encrypted server session key. 46 | # provided by the server. This should be a 48-byte binary packed string. 47 | # @param auth_vfr_data [String] The value from the AUTH_VFR_DATA key-value 48 | # pair provided by the server. 49 | # @return [Array] The encrypted password and the encrypted client 50 | # session key to use to authenticate with the server. These are returned 51 | # as binary packed strings. 52 | def self.get_11g_auth_values( password, enc_server_session_key, auth_vfr_data ) 53 | # Hash the password and auth_vfr_data and pad it to 24 bytes. This will be 54 | # used as the key for encrypting the client session key and decrypting the 55 | # server session key. 56 | password_hash = sha1_digest( password + auth_vfr_data ) + ("\0" * 4) 57 | 58 | # TODO: make random client session key 59 | client_session_key = "080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808".tns_unhexify 60 | 61 | # Encrypt client session key and decrypt the server session key, using the 62 | # password hash as a key. 63 | enc_client_session_key = openssl_encrypt( "AES-192-CBC", password_hash, nil, client_session_key ) 64 | server_session_key = openssl_decrypt( "AES-192-CBC", password_hash, nil, enc_server_session_key ) 65 | 66 | # Make the combined session key hash. This is used as the key to encrypt 67 | # the password. 68 | combo_session_key = create_combined_session_key_hash_11g( server_session_key, client_session_key ) 69 | 70 | # TODO: make random salt 71 | salt = "4C31AFE05F3B012C0AE9AB0CDFF0C508".tns_unhexify 72 | # Encrypt the salted password 73 | enc_password = openssl_encrypt( "AES-192-CBC", combo_session_key, nil, salt + password, true ) 74 | 75 | return enc_password, enc_client_session_key 76 | end 77 | 78 | 79 | 80 | # Generates the password hash for use in 10g authentication. 81 | # 82 | # @param username [String] The username for authentication. 83 | # @param password [String] The password for authentication. 84 | # @return [String] The password hash, as a binary packed string. 85 | def self.hash_password_10g( username, password ) 86 | uspw = (username + password).upcase().encode( Encoding::UTF_16BE ) 87 | key = "0123456789abcdef".tns_unhexify # fixed key used for 10g hashing 88 | 89 | # Pad the username-password to an 8-byte boundary 90 | if ( uspw.length % 4 > 0 ) 91 | padding_length = 4 - ( uspw.length % 4 ) 92 | uspw += ("\0".encode( Encoding::UTF_16BE )) * padding_length 93 | end 94 | 95 | key2 = openssl_encrypt( "DES-CBC", key, nil, uspw, false ) 96 | key2 = key2[-8,8] 97 | 98 | pwhash = openssl_encrypt( "DES-CBC", key2, nil, uspw, false ) 99 | pwhash = pwhash[-8,8] 100 | end 101 | 102 | # Generates the combined session key hash, for use in encrypting the 103 | # password in authentication. 104 | # 105 | # @param server_session_key [String] The unencrypted server session key, as 106 | # a binary packed string. 107 | # @param client_session_key [String] The unencrypted client session key, as 108 | # a binary packed string. 109 | # @return [String] The hash of the combined session key, as a binary packed 110 | # string. 111 | def self.create_combined_session_key_hash_10g( server_session_key, client_session_key ) 112 | # Unpack the session keys into byte arrays 113 | server_key_bytes = server_session_key.unpack( "C*" ) 114 | client_key_bytes = client_session_key.unpack( "C*" ) 115 | combo_session_key = "" 116 | 117 | # XOR bytes 17-32 of the session keys to make the combined session key 118 | for byte_itr in (16..31) 119 | combo_session_key += (server_key_bytes[ byte_itr ] ^ client_key_bytes[ byte_itr ]).chr 120 | end 121 | 122 | # Hash the combined session key 123 | return md5_digest( combo_session_key ) 124 | end 125 | 126 | # Generates the combined session key hash, for use in encrypting the 127 | # password in authentication. 128 | # 129 | # @param server_session_key [String] The unencrypted server session key, as 130 | # a binary packed string. 131 | # @param client_session_key [String] The unencrypted client session key, as 132 | # a binary packed string. 133 | # @return [String] The hash of the combined session key, as a binary packed 134 | # string. 135 | def self.create_combined_session_key_hash_11g( server_session_key, client_session_key ) 136 | # make combined session key 137 | server_key_bytes = server_session_key.unpack( "C*" ) 138 | client_key_bytes = client_session_key.unpack( "C*" ) 139 | combo_session_key = "" 140 | for byte_itr in (16..39) 141 | combo_session_key += (server_key_bytes[ byte_itr ] ^ client_key_bytes[ byte_itr ]).chr 142 | end 143 | 144 | # hash combined session key 145 | combo_session_key = ( md5_digest(combo_session_key[0,16]) + 146 | md5_digest(combo_session_key[16,combo_session_key.length]) ) 147 | combo_session_key = combo_session_key[0,24] 148 | 149 | return combo_session_key 150 | end 151 | 152 | 153 | # Helper function for encryption. 154 | def self.openssl_encrypt( cipher, key, iv, data, padding=false ) 155 | cipher = OpenSSL::Cipher.new( cipher ) 156 | cipher.encrypt 157 | cipher.key = key 158 | cipher.iv = iv unless iv.nil? 159 | cipher.padding = 0 unless padding 160 | 161 | ciphertext = cipher.update( data ) + cipher.final 162 | end 163 | 164 | # Helper function for decryption. 165 | def self.openssl_decrypt( cipher, key, iv, data, padding=false ) 166 | cipher = OpenSSL::Cipher.new( cipher ) 167 | cipher.decrypt 168 | cipher.key = key 169 | cipher.iv = iv unless iv.nil? 170 | cipher.padding = 0 unless padding 171 | 172 | ciphertext = cipher.update( data ) + cipher.final 173 | end 174 | 175 | # @return the MD5 digest (as a binary string) for the given input string 176 | def self.md5_digest(input_str) 177 | digester=Digest::MD5.new() 178 | digester.update(input_str) 179 | return digester.digest 180 | end 181 | 182 | # @return the SHA1 digest (as a binary string) for the given input string 183 | def self.sha1_digest(input_str) 184 | digester=Digest::SHA1.new() 185 | digester.update(input_str) 186 | return digester.digest 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /lib/net/tti/data_types.rb: -------------------------------------------------------------------------------- 1 | require "net/tti/data_types/chunked_string" 2 | require "net/tti/data_types/key_value_pair" 3 | -------------------------------------------------------------------------------- /lib/net/tti/data_types/chunked_string.rb: -------------------------------------------------------------------------------- 1 | require "bindata" 2 | 3 | module Net 4 | module TTI 5 | module DataTypes 6 | class ChunkedString < BinData::BasePrimitive 7 | # The marker to indicate that a string is being divided into multiple chunks 8 | MULTI_CHUNK_MARKER = 0xFE 9 | MULTI_CHUNK_TERMINATOR = 0x00 10 | # The apparent maximum chunk length used by Oracle TNS implementations 11 | MAX_CHUNK_LENGTH = 0x40 12 | 13 | def sensible_default 14 | return "" 15 | end 16 | 17 | def read_and_return_value(io) 18 | begin 19 | length = unmarshal_uint8(io) 20 | rescue EOFError 21 | return "" 22 | end 23 | 24 | if length == MULTI_CHUNK_MARKER 25 | data = "" 26 | while (length = unmarshal_uint8(io)) != MULTI_CHUNK_TERMINATOR 27 | data += io.readbytes(length) 28 | end 29 | 30 | return data 31 | else 32 | return io.readbytes(length) 33 | end 34 | end 35 | 36 | def value_to_binary_string(value) 37 | return "" if value.empty? 38 | 39 | if value.length > MAX_CHUNK_LENGTH 40 | value_index = 0 41 | 42 | binary_string = "" 43 | binary_string << [MULTI_CHUNK_MARKER].pack("C") 44 | while value_index < value.length 45 | chunk = value[value_index, MAX_CHUNK_LENGTH] 46 | binary_string << [chunk.length, chunk].pack("Ca*") 47 | value_index += MAX_CHUNK_LENGTH 48 | end 49 | binary_string << [MULTI_CHUNK_TERMINATOR].pack("C") 50 | return binary_string 51 | else 52 | return [value.length, value].pack("Ca*") 53 | end 54 | end 55 | 56 | private 57 | def unmarshal_uint8(io) 58 | int_string = io.readbytes(1) 59 | return int_string.unpack("C").first 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/net/tti/data_types/flags.rb: -------------------------------------------------------------------------------- 1 | require "bindata" 2 | 3 | module Net 4 | module TTI 5 | module DataTypes 6 | class Flags < BinData::Primitive 7 | uint8 :value_length 8 | choice :flag_value, :selection => :value_length do 9 | virtual 0, :value => 0 10 | uint8 1 11 | uint16be 2 12 | uint32be 4 13 | end 14 | 15 | def get 16 | self.flag_value.to_i 17 | end 18 | 19 | def set(new_value) 20 | if new_value > 0xffff 21 | self.value_length = 4 22 | self.flag_value = new_value 23 | elsif new_value > 0xff 24 | self.value_length = 2 25 | self.flag_value = new_value 26 | elsif new_value > 0 27 | self.value_length = 1 28 | self.flag_value = new_value 29 | else 30 | self.value_length = 0 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/net/tti/data_types/key_value_pair.rb: -------------------------------------------------------------------------------- 1 | require "bindata" 2 | require "net/tti/data_types/chunked_string" 3 | require "net/tti/data_types/flags" 4 | 5 | module Net 6 | module TTI 7 | module DataTypes 8 | class KeyValuePair < BinData::Record 9 | # Follows (simplified) format used by JDBC driver; other clients send 10 | # KVPs with a longer, more opaque structure (e.g. each chunked string 11 | # was preceded by a 4-byte value that, in earlier dialects appeared to 12 | # contain the total length of the string, but in later dialects did not 13 | # seem related to string length at all). 14 | uint8 :unknown1, :initial_value => 0x01 # size in bytes of kvp_key_length OR boolean 15 | uint8 :kvp_key_length, :onlyif => lambda {unknown1 != 0x00}, :value => lambda {kvp_key.length} 16 | chunked_string :kvp_key, :onlyif => lambda {unknown1 != 0x00 && kvp_key_length != 0x00} 17 | uint8 :unknown2, :initial_value => 0x01 # size in bytes of kvp_value_length 18 | uint8 :kvp_value_length, :onlyif => lambda {unknown2 != 0x00}, :value => lambda {kvp_value.length} 19 | chunked_string :kvp_value, :onlyif => lambda {unknown2 != 0x00 && kvp_value_length != 0x00} 20 | # flags are used when AUTH_SESSKEY and AUTH_ALTER_SESSION are sent to the server 21 | flags :flags 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/net/tti/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Net::TTI 2 | module Exceptions 3 | class TTIException < StandardError 4 | end 5 | 6 | class ProtocolException < TTIException 7 | end 8 | 9 | class UnsupportedTarget < TTIException 10 | end 11 | 12 | class UnsupportedArchitecture < TTIException 13 | def initialize( architecture ) 14 | super( "Unsupported architecture: #{architecture}" ) 15 | end 16 | end 17 | 18 | class UnsupportedPlatform < UnsupportedTarget 19 | def initialize( platform ) 20 | super( "Unsupported platform: #{platform}" ) 21 | end 22 | end 23 | 24 | class UnsupportedTNSVersion < UnsupportedTarget 25 | def initialize( version ) 26 | super( "Unsupported version: #{version}" ) 27 | end 28 | end 29 | 30 | 31 | 32 | class ErrorMessageReceived < TTIException 33 | ERROR_REGEX = /ORA\-(\d+)(?:\:\ (.*))?/ 34 | ERROR_REGEX_INDEX_CODE = 1 35 | ERROR_REGEX_INDEX_DESCRIPTION = 2 36 | 37 | # Attempts to parse and return the "ORA-xxxxx" error code from the error message 38 | # @return [Integer] A numeric error code, or nil if the code could not be determined 39 | def error_code() 40 | matches = ERROR_REGEX.match( self.message ) 41 | error_code = matches[ERROR_REGEX_INDEX_CODE] unless( matches.nil? or matches[ERROR_REGEX_INDEX_CODE].nil? ) 42 | error_code = error_code.to_i unless error_code.nil? 43 | return error_code 44 | end 45 | 46 | 47 | # Attempts to parse and return the error description after the "ORA-xxxxx" 48 | # error code in the error message 49 | # @return [String] A string containing the error description, or nil if the description could not be determined 50 | def error_description() 51 | matches = ERROR_REGEX.match( self.message ) 52 | return matches[ERROR_REGEX_INDEX_DESCRIPTION] unless( matches.nil? or matches[ERROR_REGEX_INDEX_DESCRIPTION].nil? ) 53 | end 54 | end 55 | 56 | class AuthenticationError < ErrorMessageReceived 57 | end 58 | 59 | class InvalidCredentialsError < AuthenticationError 60 | end 61 | 62 | class AccountLockedOutError < AuthenticationError 63 | end 64 | 65 | class PasswordExpiredError < AuthenticationError 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/net/tti/message.rb: -------------------------------------------------------------------------------- 1 | require "bindata" 2 | 3 | module Net 4 | module TTI 5 | class Message < BinData::Record 6 | TTC_CODE_PROTOCOL_NEGOTIATION = 0x1 7 | TTC_CODE_DATA_TYPE_NEGOTIATION = 0x2 8 | TTC_CODE_FUNCTION_CALL = 0x3 9 | TTC_CODE_ERROR = 0x4 10 | TTC_CODE_OK = 0x8 11 | 12 | # BinData fields 13 | uint8 :ttc_code, :initial_value => :_ttc_code 14 | 15 | def _ttc_code 16 | raise NotImplementedError 17 | end 18 | private :_ttc_code 19 | 20 | def self.handles_response_for_ttc_code(ttc_code) 21 | @@ttc_classes ||= {} 22 | @@ttc_codes ||= {} 23 | if @@ttc_classes.has_key?(ttc_code) 24 | existing_class = @@ttc_classes[ttc_code] 25 | raise ArgumentError.new("Duplicate TTC response handlers defined: #{existing_class} and #{self} both have TTC code of #{ttc_code}") 26 | end 27 | 28 | @@ttc_classes[ttc_code] = self 29 | @@ttc_codes[self] = ttc_code 30 | return nil 31 | end 32 | 33 | def self.from_data_string( raw_message ) 34 | ttc_code = raw_message[0].unpack("C").first 35 | 36 | unless message_class = @@ttc_classes[ttc_code] 37 | raise Net::TNS::Exceptions::TNSException.new( "Unknown TTC code: #{ttc_code}" ) 38 | end 39 | 40 | new_message = message_class.new 41 | new_message.read( raw_message ) 42 | 43 | return new_message 44 | end 45 | end 46 | end 47 | end 48 | 49 | require "pathname" 50 | Dir.glob("#{Pathname.new(__FILE__).dirname}/messages/*.rb") { |file| require file } 51 | -------------------------------------------------------------------------------- /lib/net/tti/messages/data_type_negotiation_request.rb: -------------------------------------------------------------------------------- 1 | require "net/tti/capabilities" 2 | 3 | module Net 4 | module TTI 5 | class DataTypeNegotiationRequest < Message 6 | # BinData fields 7 | 8 | # Not sure why this is duplicated, but clients always send the same 9 | # character set in both fields. 10 | # Character sets: 11 | # 0x00b2 (178) - US-ASCII 12 | # 0x0369 (873) - UTF-8 13 | uint16le :charset1 14 | uint16le :charset2 15 | uint8 :client_flags 16 | uint8 :client_compiletime_capabilities_length, :value => lambda { client_compiletime_capabilities.length } 17 | string :client_compiletime_capabilities 18 | uint8 :client_runtime_capabilities_length, :value => lambda { client_runtime_capabilities.length } 19 | string :client_runtime_capabilities 20 | string :unknown1 21 | string :unknown2 22 | string :dty_body 23 | string :fin 24 | 25 | def _ttc_code() 26 | TTC_CODE_DATA_TYPE_NEGOTIATION 27 | end 28 | private :_ttc_code 29 | 30 | def self.create_request(conn_params) 31 | request = self.new 32 | request.character_set = conn_params.character_set 33 | request.client_flags = conn_params.server_flags 34 | 35 | client_ct_caps = Capabilities.from_binary_string( "060100000a0101060101010101010029900307030001004f013704010000000c000006000383".tns_unhexify ) 36 | client_rt_caps = Capabilities.from_binary_string( "02010000000001".tns_unhexify ) 37 | 38 | if(conn_params.server_runtime_capabilities.length <=1 or conn_params.server_runtime_capabilities[1] & 1 != 1) 39 | client_rt_caps[1] &= 254 40 | end 41 | 42 | if(conn_params.server_compiletime_capabilities.length <= 37 or conn_params.server_compiletime_capabilities[37] & 2 != 2) 43 | client_rt_caps[1] &= 254 44 | client_ct_caps[37] &= 253 45 | end 46 | 47 | if(conn_params.server_compiletime_capabilities.length <= 27 or conn_params.server_compiletime_capabilities[27] == 0) 48 | client_ct_caps[27] = 0 49 | end 50 | 51 | request.client_compiletime_capabilities = client_ct_caps.to_binary_s 52 | request.client_runtime_capabilities = client_rt_caps.to_binary_s 53 | 54 | if (client_rt_caps[1] & 1 == 1) 55 | request.unknown1 = "800000003c3c8000000000".tns_unhexify 56 | if(client_ct_caps[37] & 2 == 2) 57 | request.unknown2 = "00000000".tns_unhexify 58 | end 59 | end 60 | 61 | if(conn_params.server_compiletime_capabilities.length <= 7 or conn_params.server_compiletime_capabilities[7] != 5) 62 | dty_body_bytes = [0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x0a, 0x00, 0x08, 0x08, 0x01, 0x00, 0x0c, 0x0c, 0x0a, 0x00, 0x17, 0x17, 0x01, 0x00, 0x18, 0x18, 0x01, 0x00, 0x19, 0x19, 0x01, 0x00, 0x1a, 0x1a, 0x01, 0x00, 0x1b, 0x1b, 0x01, 0x00, 0x1c, 0x1c, 0x01, 0x00, 0x1d, 0x1d, 0x01, 0x00, 0x1e, 0x1e, 0x01, 0x00, 0x1f, 0x1f, 0x01, 0x00, 0x20, 0x20, 0x01, 0x00, 0x21, 0x21, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x00, 0xb, 0xb, 0x01, 0x00, 0x28, 0x28, 0x01, 0x00, 0x29, 0x29, 0x01, 0x00, 0x75, 0x75, 0x01, 0x00, 0x78, 0x78, 0x01, 0x00, 0x122, 0x122, 0x01, 0x00, 0x123, 0x123, 0x01, 0x00, 0x124, 0x124, 0x01, 0x00, 0x125, 0x125, 0x01, 0x00, 0x126, 0x126, 0x01, 0x00, 0x12a, 0x12a, 0x01, 0x00, 0x12b, 0x12b, 0x01, 0x00, 0x12c, 0x12c, 0x01, 0x00, 0x12d, 0x12d, 0x01, 0x00, 0x12e, 0x12e, 0x01, 0x00, 0x12f, 0x12f, 0x01, 0x00, 0x130, 0x130, 0x01, 0x00, 0x131, 0x131, 0x01, 0x00, 0x132, 0x132, 0x01, 0x00, 0x133, 0x133, 0x01, 0x00, 0x134, 0x134, 0x01, 0x00, 0x135, 0x135, 0x01, 0x00, 0x136, 0x136, 0x01, 0x00, 0x137, 0x137, 0x01, 0x00, 0x138, 0x138, 0x01, 0x00, 0x139, 0x139, 0x01, 0x00, 0x13b, 0x13b, 0x01, 0x00, 0x13c, 0x13c, 0x01, 0x00, 0x13d, 0x13d, 0x01, 0x00, 0x13e, 0x13e, 0x01, 0x00, 0x13f, 0x13f, 0x01, 0x00, 0x140, 0x140, 0x01, 0x00, 0x141, 0x141, 0x01, 0x00, 0x142, 0x142, 0x01, 0x00, 0x143, 0x143, 0x01, 0x00, 0x147, 0x147, 0x01, 0x00, 0x148, 0x148, 0x01, 0x00, 0x149, 0x149, 0x01, 0x00, 0x14b, 0x14b, 0x01, 0x00, 0x14d, 0x14d, 0x01, 0x00, 0x14e, 0x14e, 0x01, 0x00, 0x14f, 0x14f, 0x01, 0x00, 0x150, 0x150, 0x01, 0x00, 0x151, 0x151, 0x01, 0x00, 0x152, 0x152, 0x01, 0x00, 0x153, 0x153, 0x01, 0x00, 0x154, 0x154, 0x01, 0x00, 0x155, 0x155, 0x01, 0x00, 0x156, 0x156, 0x01, 0x00, 0x157, 0x157, 0x01, 0x00, 0x158, 0x158, 0x01, 0x00, 0x159, 0x159, 0x01, 0x00, 0x15a, 0x15a, 0x01, 0x00, 0x15c, 0x15c, 0x01, 0x00, 0x15d, 0x15d, 0x01, 0x00, 0x162, 0x162, 0x01, 0x00, 0x163, 0x163, 0x01, 0x00, 0x167, 0x167, 0x01, 0x00, 0x16b, 0x16b, 0x01, 0x00, 0x17c, 0x17c, 0x01, 0x00, 0x17d, 0x17d, 0x01, 0x00, 0x17e, 0x17e, 0x01, 0x00, 0x17f, 0x17f, 0x01, 0x00, 0x180, 0x180, 0x01, 0x00, 0x181, 0x181, 0x01, 0x00, 0x182, 0x182, 0x01, 0x00, 0x183, 0x183, 0x01, 0x00, 0x184, 0x184, 0x01, 0x00, 0x185, 0x185, 0x01, 0x00, 0x186, 0x186, 0x01, 0x00, 0x187, 0x187, 0x01, 0x00, 0x189, 0x189, 0x01, 0x00, 0x18a, 0x18a, 0x01, 0x00, 0x18b, 0x18b, 0x01, 0x00, 0x18c, 0x18c, 0x01, 0x00, 0x18d, 0x18d, 0x01, 0x00, 0x18e, 0x18e, 0x01, 0x00, 0x18f, 0x18f, 0x01, 0x00, 0x190, 0x190, 0x01, 0x00, 0x191, 0x191, 0x01, 0x00, 0x194, 0x194, 0x01, 0x00, 0x195, 0x195, 0x01, 0x00, 0x196, 0x196, 0x01, 0x00, 0x197, 0x197, 0x01, 0x00, 0x19d, 0x19d, 0x01, 0x00, 0x19e, 0x19e, 0x01, 0x00, 0x19f, 0x19f, 0x01, 0x00, 0x1a0, 0x1a0, 0x01, 0x00, 0x1a1, 0x1a1, 0x01, 0x00, 0x1a2, 0x1a2, 0x01, 0x00, 0x1a3, 0x1a3, 0x01, 0x00, 0x1a4, 0x1a4, 0x01, 0x00, 0x1a5, 0x1a5, 0x01, 0x00, 0x1a6, 0x1a6, 0x01, 0x00, 0x1a7, 0x1a7, 0x01, 0x00, 0x1a8, 0x1a8, 0x01, 0x00, 0x1a9, 0x1a9, 0x01, 0x00, 0x1aa, 0x1aa, 0x01, 0x00, 0x1ab, 0x1ab, 0x01, 0x00, 0x1ad, 0x1ad, 0x01, 0x00, 0x1ae, 0x1ae, 0x01, 0x00, 0x1af, 0x1af, 0x01, 0x00, 0x1b0, 0x1b0, 0x01, 0x00, 0x1b1, 0x1b1, 0x01, 0x00, 0x1c1, 0x1c1, 0x01, 0x00, 0x1c2, 0x1c2, 0x01, 0x00, 0x1c6, 0x1c6, 0x01, 0x00, 0x1c7, 0x1c7, 0x01, 0x00, 0x1c8, 0x1c8, 0x01, 0x00, 0x1c9, 0x1c9, 0x01, 0x00, 0x1ca, 0x1ca, 0x01, 0x00, 0x1cb, 0x1cb, 0x01, 0x00, 0x1cc, 0x1cc, 0x01, 0x00, 0x1cd, 0x1cd, 0x01, 0x00, 0x1ce, 0x1ce, 0x01, 0x00, 0x1cf, 0x1cf, 0x01, 0x00, 0x1d2, 0x1d2, 0x01, 0x00, 0x1d3, 0x1d3, 0x01, 0x00, 0x1d4, 0x1d4, 0x01, 0x00, 0x1d5, 0x1d5, 0x01, 0x00, 0x1d6, 0x1d6, 0x01, 0x00, 0x1d7, 0x1d7, 0x01, 0x00, 0x1d8, 0x1d8, 0x01, 0x00, 0x1d9, 0x1d9, 0x01, 0x00, 0x1da, 0x1da, 0x01, 0x00, 0x1db, 0x1db, 0x01, 0x00, 0x1dc, 0x1dc, 0x01, 0x00, 0x1dd, 0x1dd, 0x01, 0x00, 0x1de, 0x1de, 0x01, 0x00, 0x1df, 0x1df, 0x01, 0x00, 0x1e0, 0x1e0, 0x01, 0x00, 0x1e1, 0x1e1, 0x01, 0x00, 0x1e2, 0x1e2, 0x01, 0x00, 0x1e3, 0x1e3, 0x01, 0x00, 0x1e4, 0x1e4, 0x01, 0x00, 0x1e5, 0x1e5, 0x01, 0x00, 0x1e6, 0x1e6, 0x01, 0x00, 0x1ea, 0x1ea, 0x01, 0x00, 0x1eb, 0x1eb, 0x01, 0x00, 0x1ec, 0x1ec, 0x01, 0x00, 0x1ed, 0x1ed, 0x01, 0x00, 0x1ee, 0x1ee, 0x01, 0x00, 0x1ef, 0x1ef, 0x01, 0x00, 0x1f0, 0x1f0, 0x01, 0x00, 0x1f2, 0x1f2, 0x01, 0x00, 0x1f3, 0x1f3, 0x01, 0x00, 0x1f4, 0x1f4, 0x01, 0x00, 0x1f5, 0x1f5, 0x01, 0x00, 0x1f6, 0x1f6, 0x01, 0x00, 0x1fd, 0x1fd, 0x01, 0x00, 0x1fe, 0x1fe, 0x01, 0x00, 0x201, 0x201, 0x01, 0x00, 0x202, 0x202, 0x01, 0x00, 0x204, 0x204, 0x01, 0x00, 0x205, 0x205, 0x01, 0x00, 0x206, 0x206, 0x01, 0x00, 0x207, 0x207, 0x01, 0x00, 0x208, 0x208, 0x01, 0x00, 0x209, 0x209, 0x01, 0x00, 0x20a, 0x20a, 0x01, 0x00, 0x20b, 0x20b, 0x01, 0x00, 0x20c, 0x20c, 0x01, 0x00, 0x20d, 0x20d, 0x01, 0x00, 0x20e, 0x20e, 0x01, 0x00, 0x20f, 0x20f, 0x01, 0x00, 0x210, 0x210, 0x01, 0x00, 0x211, 0x211, 0x01, 0x00, 0x212, 0x212, 0x01, 0x00, 0x213, 0x213, 0x01, 0x00, 0x214, 0x214, 0x01, 0x00, 0x215, 0x215, 0x01, 0x00, 0x216, 0x216, 0x01, 0x00, 0x217, 0x217, 0x01, 0x00, 0x218, 0x218, 0x01, 0x00, 0x219, 0x219, 0x01, 0x00, 0x21a, 0x21a, 0x01, 0x00, 0x21b, 0x21b, 0x01, 0x00, 0x21c, 0x21c, 0x01, 0x00, 0x21d, 0x21d, 0x01, 0x00, 0x21e, 0x21e, 0x01, 0x00, 0x21f, 0x21f, 0x01, 0x00, 0x230, 0x230, 0x01, 0x00, 0x235, 0x235, 0x01, 0x00, 0x23c, 0x23c, 0x01, 0x00, 0x23d, 0x23d, 0x01, 0x00, 0x23e, 0x23e, 0x01, 0x00, 0x23f, 0x23f, 0x01, 0x00, 0x240, 0x240, 0x01, 0x00, 0x242, 0x242, 0x01, 0x00, 0x244, 0x244, 0x01, 0x00, 0x245, 0x245, 0x01, 0x00, 0x246, 0x246, 0x01, 0x00, 0x247, 0x247, 0x01, 0x00, 0x248, 0x248, 0x01, 0x00, 0x249, 0x249, 0x01, 0x00, 0x3, 0x02, 0x0a, 0x00, 0x4, 0x02, 0x0a, 0x00, 0x5, 0x01, 0x01, 0x00, 0x6, 0x02, 0x0a, 0x00, 0x7, 0x02, 0x0a, 0x00, 0x9, 0x01, 0x01, 0x00, 0xd, 0x00, 0xe, 0x00, 0xf, 0x17, 0x01, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x27, 0x78, 0x01, 0x00, 0x3a, 0x00, 0x44, 0x02, 0x0a, 0x00, 0x45, 0x00, 0x46, 0x00, 0x4a, 0x00, 0x4c, 0x00, 0x5b, 0x02, 0x0a, 0x00, 0x5e, 0x01, 0x01, 0x00, 0x5f, 0x17, 0x01, 0x00, 0x60, 0x60, 0x01, 0x00, 0x61, 0x60, 0x01, 0x00, 0x64, 0x64, 0x01, 0x00, 0x65, 0x65, 0x01, 0x00, 0x66, 0x66, 0x01, 0x00, 0x68, 0xb, 0x01, 0x00, 0x69, 0x00, 0x6a, 0x6a, 0x01, 0x00, 0x6c, 0x6d, 0x01, 0x00, 0x6d, 0x6d, 0x01, 0x00, 0x6e, 0x6f, 0x01, 0x00, 0x6f, 0x6f, 0x01, 0x00, 0x70, 0x70, 0x01, 0x00, 0x71, 0x71, 0x01, 0x00, 0x72, 0x72, 0x01, 0x00, 0x73, 0x73, 0x01, 0x00, 0x74, 0x66, 0x01, 0x00, 0x76, 0x00, 0x77, 0x00, 0x79, 0x00, 0x7a, 0x00, 0x7b, 0x00, 0x88, 0x00, 0x92, 0x92, 0x01, 0x00, 0x93, 0x00, 0x98, 0x02, 0x0a, 0x00, 0x99, 0x02, 0x0a, 0x00, 0x9a, 0x02, 0x0a, 0x00, 0x9b, 0x01, 0x01, 0x00, 0x9c, 0x0c, 0x0a, 0x00, 0xac, 0x02, 0x0a, 0x00, 0xb2, 0xb2, 0x01, 0x00, 0xb3, 0xb3, 0x01, 0x00, 0xb4, 0xb4, 0x01, 0x00, 0xb5, 0xb5, 0x01, 0x00, 0xb6, 0xb6, 0x01, 0x00, 0xb7, 0xb7, 0x01, 0x00, 0xb8, 0x0c, 0x0a, 0x00, 0xb9, 0xb9, 0x01, 0x00, 0xba, 0xba, 0x01, 0x00, 0xbb, 0xbb, 0x01, 0x00, 0xbc, 0xbc, 0x01, 0x00, 0xbd, 0xbd, 0x01, 0x00, 0xbe, 0xbe, 0x01, 0x00, 0xbf, 0x00, 0xc0, 0x00, 0xc3, 0x70, 0x01, 0x00, 0xc4, 0x71, 0x01, 0x00, 0xc5, 0x72, 0x01, 0x00, 0xd0, 0xd0, 0x01, 0x00, 0xd1, 0x00, 0xe7, 0xe7, 0x01, 0x00, 0xe8, 0xe7, 0x01, 0x00, 0xe9, 0xe9, 0x01, 0x00, 0xf1, 0x6d, 0x01, 0x00, 0x203, 0x00, 0x24e, 0x24e, 0x01, 0x00, 0x24f, 0x24f, 0x01, 0x00, 0x250, 0x250, 0x01, 0x00] 63 | else 64 | dty_body_bytes = [0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x0a, 0x00, 0x08, 0x08, 0x01, 0x00, 0x0c, 0x0c, 0x0a, 0x00, 0x17, 0x17, 0x01, 0x00, 0x18, 0x18, 0x01, 0x00, 0x19, 0x19, 0x01, 0x00, 0x1a, 0x1a, 0x01, 0x00, 0x1b, 0x1b, 0x01, 0x00, 0x1c, 0x1c, 0x01, 0x00, 0x1d, 0x1d, 0x01, 0x00, 0x1e, 0x1e, 0x01, 0x00, 0x1f, 0x1f, 0x01, 0x00, 0x20, 0x20, 0x01, 0x00, 0x21, 0x21, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x00, 0xb, 0xb, 0x01, 0x00, 0x28, 0x28, 0x01, 0x00, 0x29, 0x29, 0x01, 0x00, 0x75, 0x75, 0x01, 0x00, 0x78, 0x78, 0x01, 0x00, 0x122, 0x122, 0x01, 0x00, 0x123, 0x123, 0x01, 0x00, 0x124, 0x124, 0x01, 0x00, 0x125, 0x125, 0x01, 0x00, 0x126, 0x126, 0x01, 0x00, 0x12a, 0x12a, 0x01, 0x00, 0x12b, 0x12b, 0x01, 0x00, 0x12c, 0x12c, 0x01, 0x00, 0x12d, 0x12d, 0x01, 0x00, 0x12e, 0x12e, 0x01, 0x00, 0x12f, 0x12f, 0x01, 0x00, 0x130, 0x130, 0x01, 0x00, 0x131, 0x131, 0x01, 0x00, 0x132, 0x132, 0x01, 0x00, 0x133, 0x133, 0x01, 0x00, 0x134, 0x134, 0x01, 0x00, 0x135, 0x135, 0x01, 0x00, 0x136, 0x136, 0x01, 0x00, 0x137, 0x137, 0x01, 0x00, 0x138, 0x138, 0x01, 0x00, 0x139, 0x139, 0x01, 0x00, 0x13b, 0x13b, 0x01, 0x00, 0x13c, 0x13c, 0x01, 0x00, 0x13d, 0x13d, 0x01, 0x00, 0x13e, 0x13e, 0x01, 0x00, 0x13f, 0x13f, 0x01, 0x00, 0x140, 0x140, 0x01, 0x00, 0x141, 0x141, 0x01, 0x00, 0x142, 0x142, 0x01, 0x00, 0x143, 0x143, 0x01, 0x00, 0x147, 0x147, 0x01, 0x00, 0x148, 0x148, 0x01, 0x00, 0x149, 0x149, 0x01, 0x00, 0x14b, 0x14b, 0x01, 0x00, 0x14d, 0x14d, 0x01, 0x00, 0x14e, 0x14e, 0x01, 0x00, 0x14f, 0x14f, 0x01, 0x00, 0x150, 0x150, 0x01, 0x00, 0x151, 0x151, 0x01, 0x00, 0x152, 0x152, 0x01, 0x00, 0x153, 0x153, 0x01, 0x00, 0x154, 0x154, 0x01, 0x00, 0x155, 0x155, 0x01, 0x00, 0x156, 0x156, 0x01, 0x00, 0x157, 0x157, 0x01, 0x00, 0x158, 0x158, 0x01, 0x00, 0x159, 0x159, 0x01, 0x00, 0x15a, 0x15a, 0x01, 0x00, 0x15c, 0x15c, 0x01, 0x00, 0x15d, 0x15d, 0x01, 0x00, 0x162, 0x162, 0x01, 0x00, 0x163, 0x163, 0x01, 0x00, 0x167, 0x167, 0x01, 0x00, 0x16b, 0x16b, 0x01, 0x00, 0x17c, 0x17c, 0x01, 0x00, 0x17d, 0x17d, 0x01, 0x00, 0x17e, 0x17e, 0x01, 0x00, 0x17f, 0x17f, 0x01, 0x00, 0x180, 0x180, 0x01, 0x00, 0x181, 0x181, 0x01, 0x00, 0x182, 0x182, 0x01, 0x00, 0x183, 0x183, 0x01, 0x00, 0x184, 0x184, 0x01, 0x00, 0x185, 0x185, 0x01, 0x00, 0x186, 0x186, 0x01, 0x00, 0x187, 0x187, 0x01, 0x00, 0x189, 0x189, 0x01, 0x00, 0x18a, 0x18a, 0x01, 0x00, 0x18b, 0x18b, 0x01, 0x00, 0x18c, 0x18c, 0x01, 0x00, 0x18d, 0x18d, 0x01, 0x00, 0x18e, 0x18e, 0x01, 0x00, 0x18f, 0x18f, 0x01, 0x00, 0x190, 0x190, 0x01, 0x00, 0x191, 0x191, 0x01, 0x00, 0x194, 0x194, 0x01, 0x00, 0x195, 0x195, 0x01, 0x00, 0x196, 0x196, 0x01, 0x00, 0x197, 0x197, 0x01, 0x00, 0x19d, 0x19d, 0x01, 0x00, 0x19e, 0x19e, 0x01, 0x00, 0x19f, 0x19f, 0x01, 0x00, 0x1a0, 0x1a0, 0x01, 0x00, 0x1a1, 0x1a1, 0x01, 0x00, 0x1a2, 0x1a2, 0x01, 0x00, 0x1a3, 0x1a3, 0x01, 0x00, 0x1a4, 0x1a4, 0x01, 0x00, 0x1a5, 0x1a5, 0x01, 0x00, 0x1a6, 0x1a6, 0x01, 0x00, 0x1a7, 0x1a7, 0x01, 0x00, 0x1a8, 0x1a8, 0x01, 0x00, 0x1a9, 0x1a9, 0x01, 0x00, 0x1aa, 0x1aa, 0x01, 0x00, 0x1ab, 0x1ab, 0x01, 0x00, 0x1ad, 0x1ad, 0x01, 0x00, 0x1ae, 0x1ae, 0x01, 0x00, 0x1af, 0x1af, 0x01, 0x00, 0x1b0, 0x1b0, 0x01, 0x00, 0x1b1, 0x1b1, 0x01, 0x00, 0x1c1, 0x1c1, 0x01, 0x00, 0x1c2, 0x1c2, 0x01, 0x00, 0x1c6, 0x1c6, 0x01, 0x00, 0x1c7, 0x1c7, 0x01, 0x00, 0x1c8, 0x1c8, 0x01, 0x00, 0x1c9, 0x1c9, 0x01, 0x00, 0x1ca, 0x1ca, 0x01, 0x00, 0x1cb, 0x1cb, 0x01, 0x00, 0x1cc, 0x1cc, 0x01, 0x00, 0x1cd, 0x1cd, 0x01, 0x00, 0x1ce, 0x1ce, 0x01, 0x00, 0x1cf, 0x1cf, 0x01, 0x00, 0x1d2, 0x1d2, 0x01, 0x00, 0x1d3, 0x1d3, 0x01, 0x00, 0x1d4, 0x1d4, 0x01, 0x00, 0x1d5, 0x1d5, 0x01, 0x00, 0x1d6, 0x1d6, 0x01, 0x00, 0x1d7, 0x1d7, 0x01, 0x00, 0x1d8, 0x1d8, 0x01, 0x00, 0x1d9, 0x1d9, 0x01, 0x00, 0x1da, 0x1da, 0x01, 0x00, 0x1db, 0x1db, 0x01, 0x00, 0x1dc, 0x1dc, 0x01, 0x00, 0x1dd, 0x1dd, 0x01, 0x00, 0x1de, 0x1de, 0x01, 0x00, 0x1df, 0x1df, 0x01, 0x00, 0x1e0, 0x1e0, 0x01, 0x00, 0x1e1, 0x1e1, 0x01, 0x00, 0x1e2, 0x1e2, 0x01, 0x00, 0x1e3, 0x1e3, 0x01, 0x00, 0x1e4, 0x1e4, 0x01, 0x00, 0x1e5, 0x1e5, 0x01, 0x00, 0x1e6, 0x1e6, 0x01, 0x00, 0x1ea, 0x1ea, 0x01, 0x00, 0x1eb, 0x1eb, 0x01, 0x00, 0x1ec, 0x1ec, 0x01, 0x00, 0x1ed, 0x1ed, 0x01, 0x00, 0x1ee, 0x1ee, 0x01, 0x00, 0x1ef, 0x1ef, 0x01, 0x00, 0x1f0, 0x1f0, 0x01, 0x00, 0x1f2, 0x1f2, 0x01, 0x00, 0x1f3, 0x1f3, 0x01, 0x00, 0x1f4, 0x1f4, 0x01, 0x00, 0x1f5, 0x1f5, 0x01, 0x00, 0x1f6, 0x1f6, 0x01, 0x00, 0x1fd, 0x1fd, 0x01, 0x00, 0x1fe, 0x1fe, 0x01, 0x00, 0x201, 0x201, 0x01, 0x00, 0x202, 0x202, 0x01, 0x00, 0x204, 0x204, 0x01, 0x00, 0x205, 0x205, 0x01, 0x00, 0x206, 0x206, 0x01, 0x00, 0x207, 0x207, 0x01, 0x00, 0x208, 0x208, 0x01, 0x00, 0x209, 0x209, 0x01, 0x00, 0x20a, 0x20a, 0x01, 0x00, 0x20b, 0x20b, 0x01, 0x00, 0x20c, 0x20c, 0x01, 0x00, 0x20d, 0x20d, 0x01, 0x00, 0x20e, 0x20e, 0x01, 0x00, 0x20f, 0x20f, 0x01, 0x00, 0x210, 0x210, 0x01, 0x00, 0x211, 0x211, 0x01, 0x00, 0x212, 0x212, 0x01, 0x00, 0x213, 0x213, 0x01, 0x00, 0x214, 0x214, 0x01, 0x00, 0x215, 0x215, 0x01, 0x00, 0x216, 0x216, 0x01, 0x00, 0x217, 0x217, 0x01, 0x00, 0x218, 0x218, 0x01, 0x00, 0x219, 0x219, 0x01, 0x00, 0x21a, 0x21a, 0x01, 0x00, 0x21b, 0x21b, 0x01, 0x00, 0x21c, 0x21c, 0x01, 0x00, 0x21d, 0x21d, 0x01, 0x00, 0x21e, 0x21e, 0x01, 0x00, 0x21f, 0x21f, 0x01, 0x00, 0x230, 0x230, 0x01, 0x00, 0x235, 0x235, 0x01, 0x00, 0x23c, 0x23c, 0x01, 0x00, 0x23d, 0x23d, 0x01, 0x00, 0x23e, 0x23e, 0x01, 0x00, 0x23f, 0x23f, 0x01, 0x00, 0x240, 0x240, 0x01, 0x00, 0x242, 0x242, 0x01, 0x00, 0x244, 0x244, 0x01, 0x00, 0x245, 0x245, 0x01, 0x00, 0x246, 0x246, 0x01, 0x00, 0x247, 0x247, 0x01, 0x00, 0x248, 0x248, 0x01, 0x00, 0x249, 0x249, 0x01, 0x00, 0x3, 0x02, 0x0a, 0x00, 0x4, 0x02, 0x0a, 0x00, 0x5, 0x01, 0x01, 0x00, 0x6, 0x02, 0x0a, 0x00, 0x7, 0x02, 0x0a, 0x00, 0x9, 0x01, 0x01, 0x00, 0xd, 0x00, 0xe, 0x00, 0xf, 0x17, 0x01, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x27, 0x78, 0x01, 0x00, 0x3a, 0x00, 0x44, 0x02, 0x0a, 0x00, 0x45, 0x00, 0x46, 0x00, 0x4a, 0x00, 0x4c, 0x00, 0x5b, 0x02, 0x0a, 0x00, 0x5e, 0x01, 0x01, 0x00, 0x5f, 0x17, 0x01, 0x00, 0x60, 0x60, 0x01, 0x00, 0x61, 0x60, 0x01, 0x00, 0x64, 0x64, 0x01, 0x00, 0x65, 0x65, 0x01, 0x00, 0x66, 0x66, 0x01, 0x00, 0x68, 0xb, 0x01, 0x00, 0x69, 0x00, 0x6a, 0x6a, 0x01, 0x00, 0x6c, 0x6d, 0x01, 0x00, 0x6d, 0x6d, 0x01, 0x00, 0x6e, 0x6f, 0x01, 0x00, 0x6f, 0x6f, 0x01, 0x00, 0x70, 0x70, 0x01, 0x00, 0x71, 0x71, 0x01, 0x00, 0x72, 0x72, 0x01, 0x00, 0x73, 0x73, 0x01, 0x00, 0x74, 0x66, 0x01, 0x00, 0x76, 0x00, 0x77, 0x00, 0x79, 0x00, 0x7a, 0x00, 0x7b, 0x00, 0x88, 0x00, 0x92, 0x92, 0x01, 0x00, 0x93, 0x00, 0x98, 0x02, 0x0a, 0x00, 0x99, 0x02, 0x0a, 0x00, 0x9a, 0x02, 0x0a, 0x00, 0x9b, 0x01, 0x01, 0x00, 0x9c, 0x0c, 0x0a, 0x00, 0xac, 0x02, 0x0a, 0x00, 0xb2, 0xb2, 0x01, 0x00, 0xb3, 0xb3, 0x01, 0x00, 0xb4, 0xb4, 0x01, 0x00, 0xb5, 0xb5, 0x01, 0x00, 0xb6, 0xb6, 0x01, 0x00, 0xb7, 0xb7, 0x01, 0x00, 0xb8, 0x0c, 0x0a, 0x00, 0xb9, 0xb9, 0x01, 0x00, 0xba, 0xba, 0x01, 0x00, 0xbb, 0xbb, 0x01, 0x00, 0xbc, 0xbc, 0x01, 0x00, 0xbd, 0xbd, 0x01, 0x00, 0xbe, 0xbe, 0x01, 0x00, 0xbf, 0x00, 0xc0, 0x00, 0xc3, 0x70, 0x01, 0x00, 0xc4, 0x71, 0x01, 0x00, 0xc5, 0x72, 0x01, 0x00, 0xd0, 0xd0, 0x01, 0x00, 0xd1, 0x00, 0xe7, 0xe7, 0x01, 0x00, 0xe8, 0xe7, 0x01, 0x00, 0xe9, 0xe9, 0x01, 0x00, 0xf1, 0x6d, 0x01, 0x00, 0x203, 0x00] 65 | end 66 | 67 | if(client_ct_caps[27] == 0) 68 | request.dty_body = dty_body_bytes.map { |m| m & 0xff }.pack('C*') 69 | request.fin = [0x00].pack('C') 70 | else 71 | request.dty_body = dty_body_bytes.pack('n*') 72 | request.fin = [0x00].pack('n') 73 | end 74 | 75 | return request 76 | end 77 | 78 | def character_set=(charset) 79 | self.charset1 = charset 80 | self.charset2 = charset 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/net/tti/messages/data_type_negotiation_response.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TTI 3 | class DataTypeNegotiationResponse < Message 4 | handles_response_for_ttc_code TTC_CODE_DATA_TYPE_NEGOTIATION 5 | # BinData fields 6 | rest :data 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/net/tti/messages/error_message.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TTI 3 | class ErrorMessage < Message 4 | handles_response_for_ttc_code TTC_CODE_ERROR 5 | 6 | # BinData fields 7 | 8 | uint8 :ucaeocs_length 9 | string :ucaeocs, :read_length => lambda { ucaeocs_length } 10 | uint8 :oerrdd_length 11 | string :oerrdd, :read_length => lambda { oerrdd_length } 12 | uint8 :current_row_number_length 13 | string :current_row_number, :read_length => lambda { current_row_number_length } 14 | uint8 :retcode_length 15 | string :retcode, :read_length => lambda { retcode_length } 16 | string :unknown1, :read_length => 24 17 | uint8 :message_length 18 | string :message, :read_length => lambda { message_length } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/net/tti/messages/function_call.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TTI 3 | class FunctionCall < Message 4 | handles_response_for_ttc_code TTC_CODE_FUNCTION_CALL 5 | 6 | FUNCTION_CODE_PRE_AUTH = 0x76 7 | FUNCTION_CODE_AUTH = 0x73 8 | 9 | # BinData fields 10 | # The function code: 0x76 => preauth, 0x73 => auth 11 | uint8 :function_code, :initial_value => :_function_code 12 | uint8 :sequence_number, :initial_value => :_sequence_number 13 | 14 | def _ttc_code 15 | return TTC_CODE_FUNCTION_CALL 16 | end 17 | private :_ttc_code 18 | 19 | def _function_code 20 | raise NotImplementedError 21 | end 22 | private :_function_code 23 | 24 | def _sequence_number 25 | @@seq_num ||= 0 26 | @@seq_num += 1 27 | return @@seq_num 28 | end 29 | private :_sequence_number 30 | 31 | def to_binary_s 32 | if sequence_number == 0 33 | sequence_number = FunctionCall.next_sequence_number 34 | end 35 | 36 | super 37 | end 38 | 39 | def self.next_sequence_number 40 | @@last_sequence_number ||= 0 41 | @@last_sequence_number = (@@last_sequence_number + 1) % 256 42 | return @@last_sequence_number 43 | end 44 | end 45 | end 46 | end 47 | 48 | require "pathname" 49 | Dir.glob("#{Pathname.new(__FILE__).dirname}/function_calls/*.rb") { |file| require file } 50 | -------------------------------------------------------------------------------- /lib/net/tti/messages/function_calls/authentication.rb: -------------------------------------------------------------------------------- 1 | require "net/tti/data_types" 2 | 3 | module Net 4 | module TTI 5 | class Authentication < FunctionCall 6 | LOGON_MODE_PRE_AUTH = 0x01 7 | LOGON_MODE_AUTH = 0x0101 8 | uint8 :unknown1, :initial_value => 0x01 9 | uint8 :username_length_length, :initial_value => 0x01 10 | uint8 :username_length, :value => lambda { username.length } 11 | uint8 :logon_mode_length, :initial_value => lambda { _logon_mode_length } 12 | choice :logon_mode, :selection => :_logon_mode do 13 | uint8 LOGON_MODE_PRE_AUTH, :initial_value => lambda { _logon_mode } 14 | uint16le LOGON_MODE_AUTH, :initial_value => lambda { _logon_mode } 15 | end 16 | uint8 :unknown2, :initial_value => 0x01 17 | uint8 :parameters_count_length, :initial_value => 0x01 18 | uint8 :parameters_count, :value => lambda {parameters.count} 19 | uint8 :unknown3, :initial_value => 0x01 20 | uint8 :unknown4, :initial_value => 0x01 21 | string :username 22 | array :parameters, :type => :key_value_pair, :read_until => lambda {index == parameters_count - 1} 23 | 24 | def _function_code 25 | return FUNCTION_CODE_AUTH 26 | end 27 | private :_function_code 28 | 29 | def _logon_mode 30 | return Authentication::LOGON_MODE_AUTH 31 | end 32 | private :_logon_mode 33 | 34 | def _logon_mode_length 35 | case _logon_mode 36 | when Authentication::LOGON_MODE_PRE_AUTH 37 | return 1 38 | when Authentication::LOGON_MODE_AUTH 39 | return 2 40 | end 41 | end 42 | private :_logon_mode 43 | 44 | def self.create_pre_auth_request() 45 | return PreAuthentication.new 46 | end 47 | 48 | def self.create_auth_request() 49 | return Authentication.new 50 | end 51 | 52 | def add_parameter( key, value, flags=0 ) 53 | kvp = DataTypes::KeyValuePair.new( :kvp_key => key, :kvp_value => value, :flags => flags ) 54 | self.parameters << kvp 55 | end 56 | 57 | def enc_client_session_key=(enc_client_session_key) 58 | add_parameter( "AUTH_SESSKEY", enc_client_session_key.tns_hexify.upcase, 1 ) 59 | end 60 | 61 | def enc_password=(enc_password) 62 | add_parameter( "AUTH_PASSWORD", enc_password.tns_hexify.upcase ) 63 | end 64 | end 65 | 66 | class PreAuthentication < Authentication 67 | def _function_code 68 | return FUNCTION_CODE_PRE_AUTH 69 | end 70 | private :_function_code 71 | 72 | def _logon_mode 73 | return Authentication::LOGON_MODE_PRE_AUTH 74 | end 75 | private :_logon_mode 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/net/tti/messages/function_calls/pre_authentication_response.rb: -------------------------------------------------------------------------------- 1 | require "net/tti/data_types" 2 | 3 | module Net 4 | module TTI 5 | class PreAuthenticationResponse < Message 6 | uint8 :unknown1 7 | uint8 :parameter_count 8 | array :parameters, :type => :key_value_pair, :read_until => lambda {index == parameter_count - 1} 9 | 10 | def _ttc_code 11 | return TTC_CODE_OK 12 | end 13 | private :_ttc_code 14 | 15 | def auth_sesskey 16 | param = find_param("AUTH_SESSKEY", true) 17 | return param.kvp_value.tns_unhexify 18 | end 19 | 20 | def auth_vfr_data 21 | param = find_param("AUTH_VFR_DATA", true) 22 | return param.kvp_value.tns_unhexify 23 | end 24 | 25 | def find_param(key, raise_if_not_found=false) 26 | param = self.parameters.find {|p| p.kvp_key == key} 27 | raise Net::TTI::Exceptions::TTIException.new("No #{key} parameter found") if param.nil? && raise_if_not_found 28 | return param 29 | end 30 | private :find_param 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/net/tti/messages/protocol_negotiation_request.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TTI 3 | class ProtocolNegotiationRequest < Message 4 | # BinData fields 5 | # In the request, this will be the version(s) the client supports; in the 6 | # response, this will be the version the server chooses. These are a 7 | # concatenated string of version numbers (e.g. 060504). 8 | stringz :client_versions_string 9 | stringz :client_string 10 | 11 | def _ttc_code 12 | TTC_CODE_PROTOCOL_NEGOTIATION 13 | end 14 | private :_ttc_code 15 | 16 | def self.create_request(client_versions=[6, 5, 4, 3, 2, 1, 0], client_string = "Java_TTC-8.2.0") 17 | request = self.new 18 | request.client_versions = client_versions 19 | request.client_string = client_string 20 | 21 | return request 22 | end 23 | 24 | def client_versions=(versions) 25 | self.client_versions_string = versions.pack("C*") 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/net/tti/messages/protocol_negotiation_response.rb: -------------------------------------------------------------------------------- 1 | module Net 2 | module TTI 3 | class ProtocolNegotiationResponse < Message 4 | handles_response_for_ttc_code TTC_CODE_PROTOCOL_NEGOTIATION 5 | 6 | # BinData fields 7 | uint8 :ttc_version 8 | # TODO throw if ttc_version is not in (4, 5, 6) 9 | uint8 :unknown1 10 | stringz :ttc_server 11 | uint16le :character_set 12 | uint8 :server_flags 13 | uint16le :character_set_elements_length 14 | string :character_set_elements, :read_length => lambda {character_set_elements_length * 5} 15 | # TODO stop parsing here if ttc_version = 4 16 | uint16be :fdo_length 17 | string :fdo, :read_length => :fdo_length 18 | # TODO stop parsing here is ttc_version < 6 19 | uint8 :server_compiletime_capabilities_length 20 | string :server_compiletime_capabilities, :read_length => :server_compiletime_capabilities_length 21 | uint8 :server_runtime_capabilities_length 22 | string :server_runtime_capabilities, :read_length => :server_runtime_capabilities_length 23 | 24 | def populate_connection_parameters( conn_params ) 25 | conn_params.ttc_version = self.ttc_version 26 | conn_params.ttc_server = self.ttc_server 27 | conn_params.character_set = self.character_set 28 | conn_params.server_flags = self.server_flags 29 | conn_params.server_compiletime_capabilities = Capabilities.from_binary_string( server_compiletime_capabilities ) 30 | conn_params.server_runtime_capabilities = Capabilities.from_binary_string( server_runtime_capabilities ) 31 | 32 | ttc_server_map = { 33 | # (start of) protocol handler string => {params} 34 | "IBMPC/WIN_NT-" => {:architecture => :x86, :platform => :windows}, 35 | "IBMPC/WIN_NT64" => {:architecture => :x64, :platform => :windows}, 36 | "Linuxi386/Linux" => {:architecture => :x86, :platform => :linux}, 37 | "x86_64/Linux" => {:architecture => :x64, :platform => :linux}, 38 | "Sun386i/SunOS" => {:architecture => :x86, :platform => :solaris}, 39 | "AMD64/SunOS" => {:architecture => :x64, :platform => :solaris}, 40 | } 41 | 42 | ph_match, match_params = ttc_server_map.find do |ph_start, params| 43 | ttc_server.start_with?(ph_start) 44 | end 45 | 46 | if ph_match 47 | conn_params.architecture = match_params[:architecture] 48 | conn_params.platform = match_params[:platform] 49 | else 50 | raise Net::TTI::Exceptions::UnsupportedPlatform.new( ttc_server ) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /net-tns.gemspec: -------------------------------------------------------------------------------- 1 | require "./lib/net/tns/gem_version" 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "net-tns" 5 | s.version = Net::TNS::VERSION 6 | s.summary = "Ruby implementation of the Oracle TNS protocol" 7 | s.description = "A pure Ruby (partial) implementation of the Oracle TNS protocol" 8 | 9 | s.authors = ["Chris Woodbury", "Eric Monti"] 10 | s.email = "woodbusy@gmail.com" 11 | s.homepage = "https://github.com/SpiderLabs/net-tns" 12 | s.license = "Apache-2.0" 13 | 14 | s.files = ["LICENSE"] + Dir.glob("{bin,lib}/**/*") 15 | s.require_paths = ["lib"] 16 | 17 | s.required_ruby_version = ">= 2.0.0" 18 | 19 | s.add_dependency("bindata", "~> 2.0") 20 | 21 | s.add_development_dependency("rspec", "~> 3.0") 22 | s.add_development_dependency("rspec-its", "~> 1.0") 23 | s.add_development_dependency("coveralls") 24 | end 25 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ## TNS Connection Sequence (<= 9i) 2 | C: TNS Connect 3 | S: TNS Accept 4 | C: TNS Data [ANO Negotiation Request] 5 | C: TNS Data [ANO Negotiation Response] 6 | 7 | ## TNS Connection Sequence (> 9i) 8 | C: TNS Connect 9 | S: TNS Resend 10 | C: TNS Connect 11 | S: TNS Accept 12 | C: TNS Data [ANO Negotiation Request] 13 | C: TNS Data [ANO Negotiation Response] 14 | 15 | ## TTI Connection Sequence 16 | [TNS connection first] 17 | [All TTI messages are passed via TNS Data packets] 18 | C: TTI Protocol Negotiation Request 19 | S: TTI Protocol Negotiation Response 20 | C: TTI Data Type Neogitation Request 21 | S: TTI Data Type Neogitation Response 22 | 23 | ## Authentication Sequence 24 | [TTI connection first] 25 | [All TTI messages are passed via TNS Data packets] 26 | C: TTI Function Call [Pre-Authentication Request] 27 | S: TTI OK [Pre-Authentication Response] 28 | C: TTI Function Call [Authentication Request] 29 | S: TTI OK [Authentication Response] 30 | -------------------------------------------------------------------------------- /spec/net/tns/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'net/tns/client' 2 | 3 | module Net 4 | module TNS 5 | describe Client do 6 | let(:vsnnum) { '301989888' } 7 | 8 | describe "::parse_vsnnum" do 9 | context "when provided a valid VSNNUM string" do 10 | it "should return the correct version" do 11 | expect(Client.parse_vsnnum(vsnnum)).to eql('18.0.0.0.0') 12 | end 13 | end 14 | context "when provided a non-string" do 15 | it "should raise an error" do 16 | expect { Client.parse_vsnnum(12345678) }.to raise_error(ArgumentError) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/net/tns/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require "tns_spec_helper" 2 | require 'net/tns/connection' 3 | 4 | module Net::TNS 5 | module ConnectionSpecHelper 6 | def self.get_fake_socket_proc 7 | return Proc.new {@last_socket = SpecHelpers::FakeSocket.new} 8 | end 9 | 10 | def self.current_socket 11 | @last_socket 12 | end 13 | 14 | def self.reset_current_socket 15 | @last_socket = nil 16 | end 17 | end 18 | 19 | describe Connection do 20 | context "when creating a socket" do 21 | it "should create a connection with a host and port" do 22 | expect {Connection.new(:host=>"127.0.0.1", :port=>12345)}.not_to raise_error 23 | end 24 | 25 | it "should create a connection with only a host" do 26 | expect {Connection.new(:host=>"127.0.0.1")}.not_to raise_error 27 | end 28 | 29 | it "should create a connection with only a new-socket proc" do 30 | proc = ConnectionSpecHelper.get_fake_socket_proc 31 | expect {Connection.new(:new_socket_proc => proc)}.not_to raise_error 32 | end 33 | end 34 | 35 | context "with a fake socket" do 36 | before :each do 37 | ConnectionSpecHelper.reset_current_socket 38 | end 39 | subject {Connection.new(:new_socket_proc => ConnectionSpecHelper.get_fake_socket_proc)} 40 | 41 | context "when calling #open_socket" do 42 | it "should call the new-socket proc" do 43 | expect(ConnectionSpecHelper.current_socket).to be_nil 44 | subject.open_socket() 45 | expect(ConnectionSpecHelper.current_socket).not_to be_nil 46 | expect(ConnectionSpecHelper.current_socket).not_to be_closed 47 | end 48 | end 49 | 50 | context "when calling #close_socket" do 51 | it "should close an open socket" do 52 | subject.open_socket() 53 | expect(ConnectionSpecHelper.current_socket).not_to be_closed 54 | subject.close_socket() 55 | expect(ConnectionSpecHelper.current_socket).to be_closed 56 | end 57 | 58 | it "should not error if the socket has already been closed" do 59 | subject.open_socket() 60 | subject.close_socket() 61 | expect(ConnectionSpecHelper.current_socket).to be_closed 62 | expect { subject.close_socket() }.not_to raise_error 63 | expect(ConnectionSpecHelper.current_socket).to be_closed 64 | end 65 | 66 | it "should not error if the socket has not been opened" do 67 | expect { subject.close_socket() }.not_to raise_error 68 | end 69 | end 70 | 71 | context "with an open socket" do 72 | before :each do 73 | subject.open_socket() 74 | end 75 | 76 | context "when calling #send_tns_packet" do 77 | it "should properly send the packet" do 78 | packet = Net::TNS::DataPacket.new(:data => "foo bar baz") 79 | subject.send_tns_packet(packet) 80 | expect( ConnectionSpecHelper.current_socket._written_data ).to eql_binary_string( packet ) 81 | end 82 | end 83 | 84 | context "when calling #receive_tns_packet" do 85 | it "should properly receive a packet" do 86 | packet1 = Net::TNS::DataPacket.new(:data => "foo bar") 87 | packet2 = Net::TNS::DataPacket.new(:data => "foo baz") 88 | ConnectionSpecHelper.current_socket._queue_response( packet1 ) 89 | ConnectionSpecHelper.current_socket._queue_response( packet2 ) 90 | 91 | expect( subject.receive_tns_packet() ).to eql_binary_string( packet1 ) 92 | expect( ConnectionSpecHelper.current_socket._has_unread_data? ).to eql(true) 93 | expect( subject.receive_tns_packet() ).to eql_binary_string( packet2 ) 94 | expect( ConnectionSpecHelper.current_socket._has_unread_data? ).to eql(false) 95 | end 96 | 97 | it "should properly handle a Resend" do 98 | fake_socket = ConnectionSpecHelper.current_socket 99 | # Send a packet so there's one to be re-sent 100 | request_packet = Net::TNS::DataPacket.new(:data => "foo bar") 101 | subject.send_tns_packet(request_packet) 102 | 103 | # Queue up the resend and the eventual real response 104 | response_packet = Net::TNS::DataPacket.new(:data => "foo baz") 105 | fake_socket._queue_response( Net::TNS::ResendPacket.new ) 106 | fake_socket._queue_response( response_packet ) 107 | 108 | fake_socket._clear_written_data! 109 | expect( fake_socket._has_unread_data? ).to eql(true) 110 | expect( fake_socket._written_data ).to be_empty 111 | 112 | expect( subject.receive_tns_packet() ).to eql_binary_string(response_packet) 113 | 114 | expect( fake_socket._has_unread_data? ).to eql(false) 115 | expect( fake_socket._written_data ).to eql_binary_string( request_packet ) 116 | end 117 | 118 | it "should properly handle a Refuse" do 119 | fake_socket = ConnectionSpecHelper.current_socket 120 | 121 | # Queue up the resend and the eventual real response 122 | fake_socket._queue_response( Net::TNS::RefusePacket.new ) 123 | 124 | expect {subject.receive_tns_packet()}.to raise_error(Exceptions::RefuseMessageReceived) 125 | 126 | expect( fake_socket._has_unread_data? ).to eql(false) 127 | expect( fake_socket._written_data ).to be_empty 128 | end 129 | 130 | it "should properly handle a Redirect" do 131 | pending "Implementation of redirect handling" 132 | fake_socket = ConnectionSpecHelper.current_socket 133 | 134 | # Queue up the resend and the eventual real response 135 | fake_socket._queue_response( Net::TNS::RedirectPacket.new ) 136 | 137 | expect {subject.receive_tns_packet()}.to raise_error(Exceptions::RedirectMessageReceived) 138 | 139 | expect( fake_socket._has_unread_data? ).to eql(false) 140 | expect( fake_socket._written_data ).to be_empty 141 | end 142 | end 143 | 144 | context "when calling #send_and_receive" do 145 | it "should send and then receive" do 146 | request_packet = Net::TNS::DataPacket.new(:data => "foo bar") 147 | response_packet = Net::TNS::DataPacket.new(:data => "foo baz") 148 | 149 | expect(subject).to receive(:send_tns_packet).ordered.with(request_packet) 150 | expect(subject).to receive(:receive_tns_packet).ordered.and_return(response_packet) 151 | 152 | expect(subject.send_and_receive(request_packet)).to eql(response_packet) 153 | end 154 | end 155 | 156 | context "when calling #connect" do 157 | it "should raise an error if neither :sid nor :service_name is given" do 158 | expect { subject.connect() }.to raise_error(ArgumentError) 159 | end 160 | 161 | it "should raise an error if both :sid and :service_name are given" do 162 | expect { subject.connect(:sid=>"TEST", :service_name=>"TEST") }.to raise_error(ArgumentError) 163 | end 164 | 165 | it "should properly handle connecting by SID" do 166 | fake_socket = ConnectionSpecHelper.current_socket 167 | accept_response = TnsSpecHelper.read_message('accept.raw') 168 | ano_response = TnsSpecHelper.read_message('ano_negotiation_response.raw') 169 | 170 | fake_socket._queue_response(accept_response) 171 | fake_socket._queue_response(ano_response) 172 | 173 | subject.connect(:sid => "TEST") 174 | 175 | expected_ano_request_data = ( 176 | "deadbeef00920a2001000004000004000300000000000400050a200100000800" + 177 | "0100000b58884d7db000120001deadbeef000300000004000400010001000200" + 178 | "01000300000000000400050a20010000020003e0e100020006fcff0002000200" + 179 | "000000000400050a200100000c0001001106100c0f0a0b080201030003000200" + 180 | "000000000400050a20010000030001000301").tns_unhexify 181 | expect(fake_socket._written_data).to eql_binary_string( 182 | Net::TNS::ConnectPacket.make_connection_by_sid( "127.0.0.1", "1521", "TEST" ).to_binary_s + 183 | Net::TNS::DataPacket.new( :data => expected_ano_request_data ).to_binary_s # there's more, but this is all we're speccing 184 | ) 185 | end 186 | 187 | it "should properly handle connecting by service name" do 188 | fake_socket = ConnectionSpecHelper.current_socket 189 | accept_response = TnsSpecHelper.read_message('accept.raw') 190 | ano_response = TnsSpecHelper.read_message('ano_negotiation_response.raw') 191 | 192 | fake_socket._queue_response(accept_response) 193 | fake_socket._queue_response(ano_response) 194 | 195 | subject.connect(:service_name => "TEST") 196 | 197 | expected_ano_request_data = ( 198 | "deadbeef00920a2001000004000004000300000000000400050a200100000800" + 199 | "0100000b58884d7db000120001deadbeef000300000004000400010001000200" + 200 | "01000300000000000400050a20010000020003e0e100020006fcff0002000200" + 201 | "000000000400050a200100000c0001001106100c0f0a0b080201030003000200" + 202 | "000000000400050a20010000030001000301").tns_unhexify 203 | expect(fake_socket._written_data).to eql_binary_string( 204 | Net::TNS::ConnectPacket.make_connection_by_service_name( "127.0.0.1", "1521", "TEST" ).to_binary_s + 205 | Net::TNS::DataPacket.new( :data => expected_ano_request_data ).to_binary_s # there's more, but this is all we're speccing 206 | ) 207 | end 208 | end 209 | end 210 | end 211 | end 212 | end 213 | -------------------------------------------------------------------------------- /spec/net/tns/packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | require 'net/tns/packet' 3 | 4 | module Net::TNS 5 | describe Header do 6 | test_data = [ 7 | { :length => 1, 8 | :binary_string => "0001000000000000".tns_unhexify, }, 9 | { :length => 512, 10 | :binary_string => "0200000000000000".tns_unhexify, }, 11 | { :packet_type_code => 1, :packet_type => "Connect", 12 | :binary_string => "0000000001000000".tns_unhexify, }, 13 | { :packet_type_code => 2, :packet_type => "Accept", 14 | :binary_string => "0000000002000000".tns_unhexify, }, 15 | { :packet_type_code => 3, :packet_type => "Ack", 16 | :binary_string => "0000000003000000".tns_unhexify, }, 17 | { :packet_type_code => 4, :packet_type => "Refuse", 18 | :binary_string => "0000000004000000".tns_unhexify, }, 19 | { :packet_type_code => 5, :packet_type => "Redirect", 20 | :binary_string => "0000000005000000".tns_unhexify, }, 21 | { :packet_type_code => 6, :packet_type => "Data", 22 | :binary_string => "0000000006000000".tns_unhexify, }, 23 | { :packet_type_code => 7, :packet_type => "Null", 24 | :binary_string => "0000000007000000".tns_unhexify, }, 25 | { :packet_type_code => 9, :packet_type => "Abort", 26 | :binary_string => "0000000009000000".tns_unhexify, }, 27 | { :packet_type_code => 11, :packet_type => "Resend", 28 | :binary_string => "000000000b000000".tns_unhexify, }, 29 | { :packet_type_code => 12, :packet_type => "Marker", 30 | :binary_string => "000000000c000000".tns_unhexify, }, 31 | { :packet_type_code => 13, :packet_type => "Attention", 32 | :binary_string => "000000000d000000".tns_unhexify, }, 33 | { :packet_type_code => 14, :packet_type => "Control", 34 | :binary_string => "000000000e000000".tns_unhexify, }, 35 | { :packet_type_code => 1, :packet_type => "Connect", 36 | :length => 40, 37 | :binary_string => "0028000001000000".tns_unhexify, }, 38 | ] 39 | 40 | 41 | test_data.each do | test_info | 42 | context "with length #{test_info[:length] or "nil"} and type #{test_info[:packet_type] or "nil"}" do 43 | it "should generate the correct binary string for the header" do 44 | subject.packet_length = test_info[:length] unless test_info[:length].nil? 45 | subject.packet_type = test_info[:packet_type_code] unless test_info[:packet_type_code].nil? 46 | 47 | expect(subject).to eql_binary_string( test_info[:binary_string] ) 48 | end 49 | 50 | context "when parsing a binary string" do 51 | before :each do 52 | subject.read( test_info[:binary_string] ) 53 | end 54 | 55 | its(:packet_length) {eql(test_info[:length] || 0)} 56 | its(:packet_type) {eql(test_info[:packet_type_code] || 0)} 57 | end 58 | end 59 | end 60 | end 61 | end 62 | 63 | 64 | -------------------------------------------------------------------------------- /spec/net/tns/packet_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "tns_spec_helper" 2 | 3 | shared_examples_for "a TNS packet class" do 4 | it "should instantiate with no arguments without any errors" do 5 | expect{ subject.inspect }.not_to raise_error 6 | end 7 | 8 | it "should have the correct type number" do 9 | subject.update_header() 10 | expect(subject.header.packet_type).to eql(tns_type) 11 | end 12 | end 13 | 14 | shared_examples_for "a TNS packet that can be properly created and sent" do 15 | it "should correctly compose a packet" do 16 | pending("Expected field values") if field_values.empty? 17 | field_values.each do |fieldname, value| 18 | accessor = (fieldname.to_s + "=").to_sym 19 | subject.__send__(accessor, value) 20 | end 21 | 22 | expect(subject).to eql_binary_string( raw_packet ) 23 | end 24 | end 25 | 26 | shared_examples_for "a TNS packet that can be properly received and parsed" do 27 | let(:socket) { SpecHelpers::FakeSocket.new() } 28 | 29 | before :each do 30 | socket._queue_response(raw_packet) if raw_packet 31 | end 32 | 33 | it "should correctly read a packet" do 34 | pending("Sample Data") unless socket._has_unread_data? 35 | 36 | packet = nil 37 | expect {packet = Net::TNS::Packet.from_socket(socket)}.not_to raise_error 38 | expect(packet).to be_a(subject.class) 39 | 40 | expect(socket._has_unread_data?).to be false 41 | end 42 | 43 | it "should correctly parse a packet" do 44 | pending("Sample Data") unless socket._has_unread_data? 45 | 46 | packet = Net::TNS::Packet.from_socket(socket) 47 | expect(packet.to_binary_s).to eql(raw_packet) 48 | 49 | field_values.each do |fieldname,value| 50 | expect(packet.__send__(fieldname)).to eql(value) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/net/tns/packets/abort_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe AbortPacket do 6 | let(:tns_type) {9} 7 | let(:field_values) {{ 8 | :user_reason => 0, 9 | :system_reason => 0, 10 | :data => "", 11 | }} 12 | let(:raw_packet) {nil} 13 | 14 | it_should_behave_like "a TNS packet class" 15 | it_should_behave_like "a TNS packet that can be properly received and parsed" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/net/tns/packets/accept_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe AcceptPacket do 6 | let(:tns_type) {2} 7 | 8 | describe "with an empty accept message" do 9 | let(:field_values) {{ 10 | :version => 0x0134, 11 | :service_flags => 0x0000, 12 | :sdu_size => 0x0800, 13 | :maximum_tdu_size => 0x7fff, 14 | :byte_order => 256, 15 | :data_length => 0, 16 | :data_offset => 24, 17 | :flags1 => 65, 18 | :flags2 => 1, 19 | :data => "" 20 | }} 21 | let(:raw_packet) {TnsSpecHelper.read_message('accept.raw')} 22 | 23 | it_should_behave_like "a TNS packet class" 24 | it_should_behave_like "a TNS packet that can be properly received and parsed" 25 | end 26 | 27 | describe "with an accept message with connection data" do 28 | let(:field_values) {Hash.new( 29 | :version => 0x0134, 30 | :service_flags => 0x0001, 31 | :sdu_size => 0x0800, 32 | :maximum_tdu_size => 0x7fff, 33 | :byte_order => 0x0100, 34 | :data_length => 45, 35 | :data_offset => 24, 36 | :flags1 => 13, 37 | :flags2 => 1, 38 | :data => "(DESCRIPTION=(TMP=)(VSNNUM=169869568)(ERR=0))" 39 | )} 40 | let(:raw_packet) {TnsSpecHelper.read_message('accept_with_data.raw')} 41 | 42 | it_should_behave_like "a TNS packet class" 43 | it_should_behave_like "a TNS packet that can be properly received and parsed" 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/net/tns/packets/ack_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe AckPacket do 6 | let(:tns_type) {3} 7 | let(:field_values) {Hash.new} 8 | let(:raw_packet) {nil} 9 | 10 | it_should_behave_like "a TNS packet class" 11 | it_should_behave_like "a TNS packet that can be properly received and parsed" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/net/tns/packets/attention_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe AttentionPacket do 6 | let(:tns_type) {13} 7 | let(:field_values) {Hash.new} 8 | let(:raw_packet) {nil} 9 | 10 | it_should_behave_like "a TNS packet class" 11 | it_should_behave_like "a TNS packet that can be properly received and parsed" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/net/tns/packets/connect_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe ConnectPacket do 6 | let(:tns_type) {1} 7 | let(:field_values) {{ 8 | :maximum_version => 0x0134, 9 | :minimum_version => 0x012c, 10 | :sdu_size => 0x0800, 11 | :maximum_tdu_size => 0x7fff, 12 | :protocol_flags => 0x4f98, 13 | :byte_order => 1, 14 | :flags1 => 1, 15 | :flags2 => 1, 16 | :data => raw_packet[34,123] 17 | }} 18 | let(:raw_packet) {TnsSpecHelper.read_message('connect.raw')} 19 | 20 | it_should_behave_like "a TNS packet class" 21 | it_should_behave_like "a TNS packet that can be properly created and sent" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/net/tns/packets/control_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe ControlPacket do 6 | let(:tns_type) {14} 7 | let(:field_values) {Hash.new} 8 | let(:raw_packet) {nil} 9 | 10 | it_should_behave_like "a TNS packet class" 11 | it_should_behave_like "a TNS packet that can be properly received and parsed" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/net/tns/packets/data_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe DataPacket do 6 | let(:tns_type) {6} 7 | 8 | context "with packets from the server" do 9 | context "with an ANO negotiation response" do 10 | let(:field_values) {{ 11 | :flags => 0x0000, 12 | :data => raw_packet[10..-1] 13 | }} 14 | let(:raw_packet) {TnsSpecHelper.read_message('ano_negotiation_response.raw')} 15 | 16 | it_should_behave_like "a TNS packet class" 17 | it_should_behave_like "a TNS packet that can be properly received and parsed" 18 | end 19 | 20 | context "with a query response" do 21 | let(:field_values) {{ 22 | :flags => 0x0000, 23 | :data => raw_packet[10..-1] 24 | }} 25 | let(:raw_packet) {TnsSpecHelper.read_message('data_query_response.raw')} 26 | 27 | it_should_behave_like "a TNS packet class" 28 | it_should_behave_like "a TNS packet that can be properly received and parsed" 29 | end 30 | end 31 | 32 | context "with packets from the client" do 33 | context "with an ANO negotiation request" do 34 | let(:field_values) {{ 35 | :flags => 0x0000, 36 | :data => raw_packet[10..-1] 37 | }} 38 | let(:raw_packet) {TnsSpecHelper.read_message('ano_negotiation_request.raw')} 39 | 40 | it_should_behave_like "a TNS packet class" 41 | it_should_behave_like "a TNS packet that can be properly created and sent" 42 | end 43 | 44 | context "with a query request" do 45 | let(:field_values) {{ 46 | :flags => 0x0000, 47 | :data => raw_packet[10..-1] 48 | }} 49 | let(:raw_packet) {TnsSpecHelper.read_message('data_query_request.raw')} 50 | 51 | it_should_behave_like "a TNS packet class" 52 | it_should_behave_like "a TNS packet that can be properly created and sent" 53 | end 54 | 55 | context "with a disconnect message" do 56 | let(:field_values) {{ 57 | :flags => 0x0040, 58 | :data => "" 59 | }} 60 | let(:raw_packet) {TnsSpecHelper.read_message('data_disconnect.raw')} 61 | 62 | it_should_behave_like "a TNS packet class" 63 | it_should_behave_like "a TNS packet that can be properly created and sent" 64 | end 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/net/tns/packets/marker_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe MarkerPacket do 6 | let(:tns_type) {12} 7 | 8 | context "with a marker from the server" do 9 | let(:field_values) {{ 10 | :marker_type => 1, 11 | :data => "0001".tns_unhexify 12 | }} 13 | let(:raw_packet) {TnsSpecHelper.read_message('marker_response.raw')} 14 | 15 | it_should_behave_like "a TNS packet class" 16 | it_should_behave_like "a TNS packet that can be properly received and parsed" 17 | end 18 | 19 | context "with a marker from the client" do 20 | let(:field_values) {{ 21 | :marker_type => 1, 22 | :data => "0002".tns_unhexify 23 | }} 24 | let(:raw_packet) {TnsSpecHelper.read_message('marker_request.raw')} 25 | 26 | it_should_behave_like "a TNS packet class" 27 | it_should_behave_like "a TNS packet that can be properly created and sent" 28 | end 29 | 30 | context "with a marker from the client" do 31 | let(:field_values) {{ 32 | :marker_type => 1, 33 | :data => "0002".tns_unhexify 34 | }} 35 | let(:raw_packet) {MarkerPacket.create_request().to_binary_s} 36 | 37 | it_should_behave_like "a TNS packet class" 38 | it_should_behave_like "a TNS packet that can be properly created and sent" 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/net/tns/packets/null_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe NullPacket do 6 | let(:tns_type) {7} 7 | let(:field_values) {Hash.new} 8 | let(:raw_packet) {nil} 9 | 10 | it_should_behave_like "a TNS packet class" 11 | it_should_behave_like "a TNS packet that can be properly received and parsed" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/net/tns/packets/redirect_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe RedirectPacket do 6 | let(:tns_type) {5} 7 | let(:field_values) {{ 8 | :data_length =>53, 9 | :data =>"(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.0.4)(PORT=2143))" 10 | }} 11 | let(:raw_packet) {TnsSpecHelper.read_message('redirect.raw')} 12 | 13 | it_should_behave_like "a TNS packet class" 14 | it_should_behave_like "a TNS packet that can be properly received and parsed" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/net/tns/packets/refuse_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe RefusePacket do 6 | let(:tns_type) {4} 7 | let(:field_values) {{ 8 | :user_reason => 0x22, 9 | :system_reason => 0x00, 10 | :data_length => 85, 11 | :data => "(DESCRIPTION=(ERR=12618)(VSNNUM=169869568)(ERROR_STACK=(ERROR=(CODE=12618)(EMFI=4))))" 12 | }} 13 | let(:raw_packet) {TnsSpecHelper.read_message('refuse.raw')} 14 | 15 | it_should_behave_like "a TNS packet class" 16 | it_should_behave_like "a TNS packet that can be properly received and parsed" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/net/tns/packets/resend_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require "net/tns/packet_spec_helper" 2 | 3 | module Net 4 | module TNS 5 | describe ResendPacket do 6 | let(:tns_type) {11} 7 | let(:field_values) {Hash.new} 8 | let(:raw_packet) {TnsSpecHelper.read_message('resend.raw')} 9 | 10 | it_should_behave_like "a TNS packet class" 11 | it_should_behave_like "a TNS packet that can be properly received and parsed" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/net/tns/raw/accept.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/accept.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/accept_with_data.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/accept_with_data.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/ano_negotiation_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/ano_negotiation_request.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/ano_negotiation_response.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/ano_negotiation_response.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/connect.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/connect.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/data_disconnect.raw: -------------------------------------------------------------------------------- 1 | 2 | @ -------------------------------------------------------------------------------- /spec/net/tns/raw/data_query_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/data_query_request.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/data_query_response.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tns/raw/data_query_response.raw -------------------------------------------------------------------------------- /spec/net/tns/raw/marker_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /spec/net/tns/raw/marker_response.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /spec/net/tns/raw/redirect.raw: -------------------------------------------------------------------------------- 1 | ?5(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.0.4)(PORT=2143)) -------------------------------------------------------------------------------- /spec/net/tns/raw/refuse.raw: -------------------------------------------------------------------------------- 1 | a"U(DESCRIPTION=(ERR=12618)(VSNNUM=169869568)(ERROR_STACK=(ERROR=(CODE=12618)(EMFI=4)))) -------------------------------------------------------------------------------- /spec/net/tns/raw/resend.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /spec/net/tti/crypto_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/crypto" 3 | 4 | module Net::TTI 5 | describe Crypto do 6 | context "when doing general tests" do 7 | plaintext = "My voice is my passport. Verify me." 8 | key = "000102030405060708090A0B0C0D0F" 9 | 10 | context "when using AES-128-CBC" do 11 | it "should encrypt and decrypt to the original value" do 12 | ciphertext = Crypto.openssl_encrypt( "AES-128-CBC", key.slice(0, 16), nil, plaintext, true ) 13 | decrypted_text = Crypto.openssl_decrypt( "AES-128-CBC", key.slice(0, 16), nil, ciphertext, true ) 14 | expect(decrypted_text).to eql( plaintext ) 15 | end 16 | end 17 | 18 | context "when using AES-192-CBC" do 19 | it "should encrypt and decrypt to the original value" do 20 | ciphertext = Crypto.openssl_encrypt( "AES-192-CBC", key.slice(0, 24), nil, plaintext, true ) 21 | decrypted_text = Crypto.openssl_decrypt( "AES-192-CBC", key.slice(0, 24), nil, ciphertext, true ) 22 | expect(decrypted_text).to eql( plaintext ) 23 | end 24 | end 25 | 26 | context "when using DES-CBC" do 27 | it "should encrypt and decrypt to the original value" do 28 | ciphertext = Crypto.openssl_encrypt( "DES-CBC", key.slice(0, 8), nil, plaintext, true ) 29 | decrypted_text = Crypto.openssl_decrypt( "DES-CBC", key.slice(0, 8), nil, ciphertext, true ) 30 | expect(decrypted_text).to eql( plaintext ) 31 | end 32 | end 33 | end 34 | 35 | context "when performing individual steps" do 36 | context "when calculating a known 10g hash" do 37 | username_password = "00530059005300540045004d004d0041004e0041004700450052000000000000".tns_unhexify 38 | key1 = "0123456789abcdef".tns_unhexify 39 | key2_full_known = "643624edc5fea9b402b0b017e7cb7db713108ac1914e984fe2eddfe949a0c3c1".tns_unhexify 40 | pwhash_full_known = "a2295a85f9b413c2d2b25971d5199a0ba6c4c6035a4906b2d4df7931ab130e37".tns_unhexify 41 | 42 | it "should calculate the second-stage key" do 43 | key2_full = Crypto.openssl_encrypt( "DES-CBC", key1, nil, username_password, false ) 44 | expect(key2_full).to eql( key2_full_known ) 45 | end 46 | 47 | it "should calculate the un-truncated password hash" do 48 | pwhash_full = Crypto.openssl_encrypt( "DES-CBC", key2_full_known[-8,8], nil, username_password, false ) 49 | expect(pwhash_full).to eql( pwhash_full_known ) 50 | end 51 | end 52 | 53 | context "when calculating a known 10g hash #2" do 54 | username_password = "00530059005300540045004d004d0041004e0041004700450052000000000000".tns_unhexify 55 | key1 = "0123456789abcdef".tns_unhexify 56 | key2_full_known = "643624edc5fea9b402b0b017e7cb7db713108ac1914e984fe2eddfe949a0c3c1".tns_unhexify 57 | pwhash_full_known = "a2295a85f9b413c2d2b25971d5199a0ba6c4c6035a4906b2d4df7931ab130e37".tns_unhexify 58 | 59 | it "should calculate the second-stage key" do 60 | key2_full = Crypto.openssl_encrypt( "DES-CBC", key1, nil, username_password, false ) 61 | expect(key2_full).to eql( key2_full_known ) 62 | end 63 | 64 | it "should calculate the un-truncated password hash" do 65 | pwhash_full = Crypto.openssl_encrypt( "DES-CBC", key2_full_known[-8,8], nil, username_password, false ) 66 | expect(pwhash_full).to eql( pwhash_full_known ) 67 | end 68 | end 69 | 70 | # These are real-world values pulled off the wire. We'd better be able to 71 | # handle them correctly. 72 | context "when handling known session key values" do 73 | 74 | context "for Oracle 9i" do 75 | it "should decrypt session key 1" do 76 | sesion_key_enc = "8cf28b36e4f3d2095729cf59510003bf".tns_unhexify 77 | session_key_known = "d3e11aa692f9e9f14c274c661228de1c".tns_unhexify 78 | key = "8cea3ba0903a2a8fea55807f2495062c5b2286a13baefee9".tns_unhexify 79 | iv = "8020400408021001".tns_unhexify 80 | 81 | session_key = Crypto.openssl_decrypt( "DES-EDE3-CBC", key, iv, sesion_key_enc, false ) 82 | expect(session_key).to eql( session_key_known ) 83 | end 84 | end 85 | 86 | context "for Oracle 10g/11g" do 87 | pwhash = "d4df7931ab130e370000000000000000".tns_unhexify 88 | 89 | it "should decrypt session key 1" do 90 | sesion_key_enc = "ab629dc2f03cde859ca5da438054ea59e064f0e8ba52b5541d4d8e9b437cb8eb".tns_unhexify 91 | session_key_known = "68d830ededbdc241480851a3348185929c4cb7a15deb5faa2f98374a51271501".tns_unhexify 92 | 93 | session_key = Crypto.openssl_decrypt( "AES-128-CBC", pwhash, nil, sesion_key_enc, false ) 94 | expect(session_key).to eql( session_key_known ) 95 | end 96 | 97 | it "should decrypt session key 2" do 98 | sesion_key_enc = "6B9506EE2E960C1B4870152C4B8D24D7706791F8F205B185C8AD26D93AA9CBA9".tns_unhexify 99 | session_key_known = "0F36690EE57A8156AF82428295654841F4EFA9851ADF6DF288A54FE111F139C9".tns_unhexify 100 | 101 | session_key = Crypto.openssl_decrypt( "AES-128-CBC", pwhash, nil, sesion_key_enc, false ) 102 | expect(session_key).to eql( session_key_known ) 103 | end 104 | 105 | it "should decrypt session key 3" do 106 | sesion_key_enc = "899883f8ee71e1e6dd8f96d0df3422ca1c5b566be458e047bb67eec1e36186df".tns_unhexify 107 | session_key_known = "C3C641EC9CFD76606488CF01B1BBF86D8AA7EE36523848AADB8CB36CDC85FE59".tns_unhexify 108 | 109 | session_key = Crypto.openssl_decrypt( "AES-128-CBC", pwhash, nil, sesion_key_enc, false ) 110 | expect(session_key).to eql( session_key_known ) 111 | end 112 | 113 | it "should encrypt session key 1" do 114 | session_key_enc_known = "37933e0e2d57332c89f74a00eed13fb0e14a47c5d79e54919dc12f249a9f950c".tns_unhexify 115 | session_key = "FAF5034314546426F329B1DAB1CDC5B8FF94349E0875623160350B0E13A0DA36".tns_unhexify 116 | 117 | session_key_enc = Crypto.openssl_encrypt( "AES-128-CBC", pwhash, nil, session_key, false ) 118 | expect(session_key_enc).to eql( session_key_enc_known ) 119 | end 120 | 121 | it "should encrypt session key 2" do 122 | session_key_enc_known = "d321b6e4c7b3174f21afb7fd73bdd3116f9407feb475820a89429063a6e8f0a9".tns_unhexify 123 | session_key = "4C31AFE05F3B012C0AE9AB0CDFF0C5084C31AFE05F3B012C0AE9AB0CDFF0C508".tns_unhexify 124 | 125 | session_key_enc = Crypto.openssl_encrypt( "AES-128-CBC", pwhash, nil, session_key, false ) 126 | expect(session_key_enc).to eql( session_key_enc_known ) 127 | end 128 | end 129 | end 130 | 131 | context "when encrypting passwords" do 132 | context "for Oracle 9i" do 133 | it "should encrypt password 1" do 134 | password_obf = "80789358776f7264396cfa1270617373".tns_unhexify 135 | password_enc_known = "3078d7de44385654cc952a9c56e2659b".tns_unhexify 136 | key = "31b22dc665e423e03a88c112c3a09c81487458df684897d1e9749cfe8d4704f828f642bde4abf5ca".tns_unhexify 137 | iv = "8020400408021001".tns_unhexify 138 | 139 | password_enc = Crypto.openssl_encrypt( "DES-EDE3-CBC", key.slice(0, 24), iv, password_obf, true ) 140 | expect(password_enc[0,16]).to eql( password_enc_known ) 141 | end 142 | end 143 | 144 | context "for Oracle 10g/11g" do 145 | it "should encrypt password 1" do 146 | combined_session_key = "906b64b47d8e6e97751b90c4b9776e55".tns_unhexify 147 | salted_password = "4c31afe05f3b012c0ae9ab0cdff0c5084d414e41474552".tns_unhexify 148 | password_enc_known = "8D061272A71886AB5F148F4BA3FB74D3BA88C6C19568760DDFB100BFA7585B58".tns_unhexify 149 | 150 | password_enc = Crypto.openssl_encrypt( "AES-128-CBC", combined_session_key, nil, salted_password, true ) 151 | expect(password_enc).to eql( password_enc_known ) 152 | end 153 | end 154 | end 155 | end 156 | 157 | 158 | context "calling hash_password_10g" do 159 | it "should return the correct hash for SYSTEM/MANAGER" do 160 | hash = Crypto.hash_password_10g( "SYSTEM", "MANAGER" ) 161 | expect(hash.tns_hexify).to eql( "d4df7931ab130e37" ) 162 | end 163 | 164 | it "should return the correct hash for SYSTEM/SYSTEM" do 165 | hash = Crypto.hash_password_10g( "SYSTEM", "SYSTEM" ) 166 | expect(hash.tns_hexify).to eql( "970baa5b81930a40" ) 167 | end 168 | 169 | it "should convert the username and password to upper-case before hashing" do 170 | hash1 = Crypto.hash_password_10g( "sYsTeM", "mAnAgEr" ) 171 | hash2 = Crypto.hash_password_10g( "SYSTEM", "MANAGER" ) 172 | expect(hash1.tns_hexify).to eql( hash2.tns_hexify ) 173 | end 174 | end 175 | 176 | context "calling get_10g_auth_values" do 177 | server_session_key_enc = "07A7223BEA0D24CCB1A9ADB947F82E34268A00A153053015CC246F83B1C72CB0".tns_unhexify 178 | # The encrypted client session key depends (obviously) on knowing the value of the client session key. 179 | # Currently, a static value is used. If this value were changed or made to be randomly generated, this 180 | # spec would break. 181 | client_session_key_enc = "17168F8A0A7D2827A8F4764A27627D95F5A07011E74602A2289F908B94FB8BC6".tns_unhexify 182 | # Ditto for the salt used here 183 | password_enc = "EAE776A214983E76926B231E8FD135DBCC73E6136B7E1332548C4AA2A554DF02".tns_unhexify 184 | 185 | enc_password, enc_client_session_key = Crypto.get_10g_auth_values( "system", "system", server_session_key_enc ) 186 | 187 | it "should return the correct encrypted password" do 188 | expect(enc_password).to eql( password_enc ) 189 | end 190 | it "should return the correct encrypted client session key" do 191 | expect(enc_client_session_key).to eql( client_session_key_enc ) 192 | end 193 | end 194 | 195 | context "calling get_11g_auth_values" do 196 | server_session_key_enc = "78c14513e9338ae8203d3a4a0354caca7b5e5db5d8664f5f979082fd16458eece8c8dc073addfea80c4aaec9d2988a32".tns_unhexify 197 | auth_vfr_data = "6af5bdb421e874f78625".tns_unhexify 198 | # The encrypted client session key depends (obviously) on knowing the value of the client session key. 199 | # Currently, a static value is used. If this value were changed or made to be randomly generated, this 200 | # spec would break. 201 | client_session_key_enc = "42B677AE69DAF917C6B87E8AE384F59A9475E7EFD41683188DF4EF24866615B1E2759BE8343F476711E14D0B71C48549".tns_unhexify 202 | # Ditto for the salt used here 203 | password_enc = "F9E7B726CB1FC85A81A1E8590C705A7CCA45F8A4E1A0F3110D3C7CE531862E6A".tns_unhexify 204 | 205 | enc_password, enc_client_session_key = Crypto.get_11g_auth_values( "admin", server_session_key_enc, auth_vfr_data ) 206 | 207 | it "should return the correct encrypted password" do 208 | expect(enc_password).to eql( password_enc ) 209 | end 210 | it "should return the correct encrypted client session key" do 211 | expect(enc_client_session_key).to eql( client_session_key_enc ) 212 | end 213 | end 214 | end 215 | end 216 | -------------------------------------------------------------------------------- /spec/net/tti/data_types/chunked_string_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/data_types" 3 | 4 | shared_examples_for "a ChunkedString that functions properly" do 5 | it "should serialize properly" do 6 | clr_string = Net::TTI::DataTypes::ChunkedString.new( data ) 7 | expect(clr_string).to eql_binary_string( binary_string ) 8 | end 9 | 10 | it "should read properly" do 11 | clr_string = Net::TTI::DataTypes::ChunkedString.read( binary_string ) 12 | expect(clr_string).to eql( data ) 13 | end 14 | end 15 | 16 | module Net::TTI::DataTypes 17 | describe ChunkedString do 18 | context "with a simple string" do 19 | it_should_behave_like "a ChunkedString that functions properly" do 20 | let(:data) {"ABCD"} 21 | let(:binary_string) {"0441424344".tns_unhexify} 22 | end 23 | end 24 | 25 | context "with an empty string" do 26 | it_should_behave_like "a ChunkedString that functions properly" do 27 | let(:data) {""} 28 | let(:binary_string) {"".tns_unhexify} 29 | end 30 | end 31 | 32 | context "with a string with null characters" do 33 | it_should_behave_like "a ChunkedString that functions properly" do 34 | let(:data) {"TEST\0"} 35 | let(:binary_string) {"055445535400".tns_unhexify} 36 | end 37 | end 38 | 39 | context "with a string with 64 characters" do 40 | it_should_behave_like "a ChunkedString that functions properly" do 41 | let(:data) {"0123456789012345678901234567890123456789012345678901234567890123"} 42 | let(:binary_string) {"4030313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233".tns_unhexify} 43 | end 44 | end 45 | 46 | context "with a string with 65 characters" do 47 | it_should_behave_like "a ChunkedString that functions properly" do 48 | let(:data) {"01234567890123456789012345678901234567890123456789012345678901234"} 49 | let(:binary_string) {"fe4030313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233013400".tns_unhexify} 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/net/tti/data_types/key_value_pair_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/data_types" 3 | 4 | 5 | shared_examples_for "a KeyValuePair that reads properly" do 6 | subject {Net::TTI::DataTypes::KeyValuePair.read( binary_string )} 7 | 8 | its(:kvp_key) {should == kvp_key} 9 | its(:kvp_value) {should == kvp_value} 10 | its(:flags) {should == flags} 11 | end 12 | 13 | shared_examples_for "a KeyValuePair that functions properly" do 14 | it "should serialize properly when built from #initialize arguments" do 15 | kvp = Net::TTI::DataTypes::KeyValuePair.new( :kvp_key => kvp_key, :kvp_value => kvp_value, :flags => flags ) 16 | expect(kvp.to_binary_s).to eql(binary_string) 17 | end 18 | 19 | it "should serialize properly when built from accessors" do 20 | kvp = Net::TTI::DataTypes::KeyValuePair.new 21 | kvp.kvp_key = kvp_key 22 | kvp.kvp_value = kvp_value 23 | kvp.flags = flags 24 | 25 | expect(kvp.to_binary_s).to eql(binary_string) 26 | end 27 | 28 | it_should_behave_like "a KeyValuePair that reads properly" 29 | end 30 | 31 | module Net::TTI::DataTypes 32 | describe KeyValuePair do 33 | let(:flags) {0x00} 34 | 35 | context "with a simple key-value pair" do 36 | it_should_behave_like "a KeyValuePair that functions properly" do 37 | let(:binary_string) {"010d0d415554485f5445524d494e414c010707756e6b6e6f776e00".tns_unhexify} 38 | let(:kvp_key) {"AUTH_TERMINAL"} 39 | let(:kvp_value) {"unknown"} 40 | let(:flags) {0x00} 41 | end 42 | end 43 | 44 | context "with a value with special characters" do 45 | it_should_behave_like "a KeyValuePair that functions properly" do 46 | let(:binary_string) {"0105055445535432010e0e544553545c544553543a5445535400".tns_unhexify} 47 | let(:kvp_key) {"TEST2"} 48 | let(:kvp_value) {"TEST\\TEST:TEST"} 49 | end 50 | end 51 | 52 | context "with a simple key-value pair with flags" do 53 | it_should_behave_like "a KeyValuePair that functions properly" do 54 | let(:binary_string) {"01040454455354010404544553540144".tns_unhexify} 55 | let(:kvp_key) {"TEST"} 56 | let(:kvp_value) {"TEST"} 57 | let(:flags) {68} 58 | end 59 | end 60 | 61 | context "with a key-value pair with no value" do 62 | it_should_behave_like "a KeyValuePair that functions properly" do 63 | let(:binary_string) {"01040454455354010000".tns_unhexify} 64 | let(:kvp_key) {"TEST"} 65 | let(:kvp_value) {""} 66 | end 67 | end 68 | 69 | context "with an AUTH_SESSKEY key-value pair" do 70 | it_should_behave_like "a KeyValuePair that functions properly" do 71 | let(:binary_string) { 72 | ( "010c0c415554485f534553534b45590140403938313146433737383734314141" + 73 | "4344304438433335324232333941373939383745423944374544324231413536" + 74 | "4245444239383430334341333535443634300101").tns_unhexify } 75 | let(:kvp_key) {"AUTH_SESSKEY"} 76 | let(:kvp_value) {"9811FC778741AACD0D8C352B239A79987EB9D7ED2B1A56BEDB98403CA355D640"} 77 | let(:flags) {1} 78 | end 79 | end 80 | 81 | context "with an AUTH_ALTER_SESSION key-value pair" do 82 | # This example is a server response, which includes unknown values. We 83 | # just need to read it correctly, not be able to build it. 84 | it_should_behave_like "a KeyValuePair that reads properly" do 85 | let(:binary_string) { 86 | ( "011212415554485f414c5445525f53455353494f4e015ffe40414c5445522053" + 87 | "455353494f4e205345542054494d455f5a4f4e453d27416d65726963612f4e65" + 88 | "775f596f726b27204e4c535f4c414e47554147453d27414d451f524943414e27" + 89 | "204e4c535f5445525249544f52593d27414d45524943412700000101" ).tns_unhexify } 90 | let(:kvp_key) {"AUTH_ALTER_SESSION"} 91 | let(:kvp_value) { 92 | "ALTER SESSION SET TIME_ZONE='America/New_York' NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA'\x00" } 93 | let(:flags) { 0x01 } 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/net/tti/messages/data_type_negotiation_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | 5 | shared_examples_for "a DataTypeNegotiationRequest that functions properly" do 6 | it "should serialize properly" do 7 | conn_params.ttc_version = 6 8 | conn_params.character_set = 0xb2 9 | conn_params.server_flags = 0x1 10 | conn_params.server_compiletime_capabilities = Net::TTI::Capabilities.from_binary_string( "\x06\x01\x01\x01\x0f\x01\x01\x06\x01\x01\x01\x01\x01\x01\x01\x7f\xff\x03\n\x03\a\x01\x01\x7f\x01\x7f\xff\x01\t\x01\x01\xbf\x01\x05\x06\x00\x01\a\x04" ) 11 | conn_params.server_runtime_capabilities = Net::TTI::Capabilities.from_binary_string( "\x02\x01\x00\x01\x18\x00\x03") 12 | kvp = Net::TTI::DataTypeNegotiationRequest.create_request(conn_params) 13 | expect(kvp).to eql_binary_string(binary_string) 14 | end 15 | end 16 | 17 | module Net::TTI 18 | describe DataTypeNegotiationRequest do 19 | context "with a request for a 11g2 server" do 20 | it_should_behave_like "a DataTypeNegotiationRequest that functions properly" do 21 | let(:conn_params) { Net::TTI::ConnectionParameters.new() } 22 | let(:binary_string) {TtiSpecHelper.read_message("data_type_negotiation_request_solaris_11g2.raw")} 23 | end 24 | end 25 | 26 | # TODO: fix this by passing conn_params with correct Capabilities for 10g 27 | # context "with a request for a 10g2 server" do 28 | # it_should_behave_like "a DataTypeNegotiationRequest that functions properly" do 29 | # let(:conn_params) { Net::TTI::ConnectionParameters.new() } 30 | # let(:binary_string) {TtiSpecHelper.read_message("data_type_negotiation_request_solaris_10g2.raw")} 31 | # end 32 | # end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/net/tti/messages/data_type_negotiation_response_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | 5 | shared_examples_for "a DataTypeNegotiationResponse that reads properly" do 6 | it "should read without errors" do 7 | response = Net::TTI::DataTypeNegotiationResponse.read( binary_string ) 8 | expect(response.data).not_to be_empty 9 | end 10 | end 11 | 12 | module Net::TTI 13 | describe DataTypeNegotiationResponse do 14 | context "with a response from a Windows server" do 15 | it_should_behave_like "a DataTypeNegotiationResponse that reads properly" do 16 | let(:binary_string) {TtiSpecHelper.read_message("data_type_negotiation_response_windows.raw")} 17 | end 18 | end 19 | 20 | context "with a response from a Linux server" do 21 | it_should_behave_like "a DataTypeNegotiationResponse that reads properly" do 22 | let(:binary_string) {TtiSpecHelper.read_message("data_type_negotiation_response_linux.raw")} 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/net/tti/messages/error_message_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | shared_examples_for "an ErrorMessage that reads properly" do 5 | context "when reading" do 6 | subject{Net::TTI::ErrorMessage.read( binary_string )} 7 | 8 | its(:message) {should eql( message )} 9 | end 10 | end 11 | 12 | module Net 13 | module TTI 14 | describe ErrorMessage do 15 | context "when parsing a bad-username/password error from an Oracle DB 10g server" do 16 | it_should_behave_like "an ErrorMessage that reads properly" do 17 | let(:binary_string) {("04010100000203f9000000000000000000000000000000000004000000000000334f52412d30313031373a20696e76616c696420757365726e616d652f70617373776f72643b206c6f676f6e2064656e6965640a").tns_unhexify } 18 | let(:message) {"ORA-01017: invalid username/password; logon denied\n"} 19 | end 20 | end 21 | 22 | context "when parsing an account-locked-out error from an Oracle DB 10g server" do 23 | it_should_behave_like "an ErrorMessage that reads properly" do 24 | let(:binary_string) {("0401010000026d60000000000000000000000000000000000004000000000000214f52412d32383030303a20746865206163636f756e74206973206c6f636b65640a").tns_unhexify } 25 | let(:message) {"ORA-28000: the account is locked\n"} 26 | end 27 | end 28 | 29 | context "when parsing ORA-01034 error from a Oracle DB 10g server" do 30 | it_should_behave_like "an ErrorMessage that reads properly" do 31 | let(:binary_string) {("040101000002040a000000000000000000000000000000000002000000000000804f52412d30313033343a204f5241434c45206e6f7420617661696c61626c650a4f52412d32373130313a20736861726564206d656d6f7279207265616c6d20646f6573206e6f742065786973740a536f6c617269732d414d443634204572726f723a20323a204e6f20737563682066696c65206f72206469726563746f72790a").tns_unhexify } 32 | let(:message) {"ORA-01034: ORACLE not available\nORA-27101: shared memory realm does not exist\nSolaris-AMD64 Error: 2: No such file or directory\n"} 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/net/tti/messages/function_calls/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | module Net 5 | module TTI 6 | describe Authentication do 7 | auth_request = ( 8 | "037304010106020101010103010173797374656d010d0d415554485f50415353" + 9 | "574f524401404046343638364543394337303541313145343930434337303031" + 10 | "3431313038453431393136343130374643423342364237434431393346363045" + 11 | "4532433235354600010d0d415554485f5445524d494e414c010707756e6b6e6f" + 12 | "776e00010c0c415554485f534553534b45590140404332344242313730423644" + 13 | "4635394431304239434439453835363841314334453342433438463946454438" + 14 | "3338363933464136323035304231444332463937410101" ).tns_unhexify 15 | 16 | context "when serializing authentication messages" do 17 | subject {Authentication.new( :username => "system" )} 18 | 19 | before :each do 20 | subject.sequence_number = 4 # Sequence number in example 21 | end 22 | 23 | it "should properly serialize authentication request" do 24 | subject.enc_password = "F4686EC9C705A11E490CC700141108E419164107FCB3B6B7CD193F60EE2C255F".tns_unhexify 25 | subject.add_parameter("AUTH_TERMINAL", "unknown") 26 | subject.enc_client_session_key = "C24BB170B6DF59D10B9CD9E8568A1C4E3BC48F9FED838693FA62050B1DC2F97A".tns_unhexify 27 | 28 | expect(subject).to eql_binary_string( auth_request ) 29 | end 30 | 31 | it "should properly serialize authentication request when calling #add_parameter" do 32 | subject.add_parameter( "AUTH_PASSWORD", "F4686EC9C705A11E490CC700141108E419164107FCB3B6B7CD193F60EE2C255F" ) 33 | subject.add_parameter("AUTH_TERMINAL", "unknown") 34 | subject.add_parameter( "AUTH_SESSKEY", "C24BB170B6DF59D10B9CD9E8568A1C4E3BC48F9FED838693FA62050B1DC2F97A", 1 ) 35 | 36 | expect(subject).to eql_binary_string( auth_request ) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/net/tti/messages/function_calls/pre_authentication_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | module Net 5 | module TTI 6 | describe PreAuthentication do 7 | username_only1 = "0376020101060101010100010173797374656d".tns_unhexify 8 | username_only2 = "0376020101040101010100010173797374".tns_unhexify 9 | request1 = "0376020101060101010101010173797374656d010d0d415554485f5445524d494e414c010f0f54455354484f53542d445936424a3500".tns_unhexify 10 | request2 = "0376020101060101010102010173797374656d010d0d415554485f5445524d494e414c010f0f54455354484f53542d445936424a3500010f0f415554485f50524f4752414d5f4e4d010b0b73716c706c75732e65786500".tns_unhexify 11 | 12 | context "when serializing pre-authentication messages" do 13 | subject {PreAuthentication.new( :username => "system" )} 14 | 15 | before :each do 16 | subject.sequence_number = 2 # Sequence number in example 17 | end 18 | 19 | 20 | it "should properly serialize a request with no parameters" do 21 | expect(subject).to eql_binary_string( username_only1 ) 22 | 23 | subject.username = "syst" 24 | expect(subject).to eql_binary_string( username_only2 ) 25 | end 26 | 27 | it "should properly serialize a request with one parameter" do 28 | subject.add_parameter( "AUTH_TERMINAL", "TESTHOST-DY6BJ5" ) 29 | 30 | expect(subject).to eql_binary_string( request1 ) 31 | end 32 | 33 | it "should properly serialize a request with two parameters" do 34 | subject.add_parameter( "AUTH_TERMINAL", "TESTHOST-DY6BJ5" ) 35 | subject.add_parameter( "AUTH_PROGRAM_NM", "sqlplus.exe" ) 36 | 37 | expect(subject).to eql_binary_string( request2 ) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/net/tti/messages/function_calls/pre_authentication_response_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | shared_examples_for "a PreAuthenticationResponse that reads properly" do |expected_parameters| 5 | context "when reading" do 6 | subject {Net::TTI::PreAuthenticationResponse.read( @binary_string )} 7 | 8 | its(:auth_sesskey) {should == @auth_sesskey} 9 | its(:auth_vfr_data) {should == @auth_vfr_data} 10 | 11 | context "when examining the parameters" do 12 | it "should have the right number of parameters" do 13 | expect(subject.parameters.count).to eql(expected_parameters.count) 14 | end 15 | 16 | expected_parameters.each do |expected_parameter| 17 | expected_key = expected_parameter[:key] 18 | 19 | it "#{expected_key} should have the right contents" do 20 | key = subject.parameters.find {|p| p.kvp_key == expected_key} 21 | 22 | expect(key.kvp_key).to eql(expected_parameter[:key]) 23 | expect(key.kvp_value).to eql(expected_parameter[:value]) 24 | expect(key.flags).to eql(expected_parameter[:flags] || 0) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | 31 | module Net 32 | module TTI 33 | describe PreAuthenticationResponse do 34 | context "with a 10g response" do 35 | before :each do 36 | @binary_string = ("080102010c0c415554485f534553534b45590140403930303739333939313736" + 37 | "3642423930323646453138453941373030383732324633424345413535463334" + 38 | "35423932464636343639323637303538343939354500010d0d415554485f5646" + 39 | "525f444154410001090401010102000000000000000000000000000000000000" + 40 | "0000020000000000").tns_unhexify 41 | @auth_sesskey = "900793991766BB9026FE18E9A7008722F3BCEA55F345B92FF64692670584995E".tns_unhexify 42 | @auth_vfr_data = "" 43 | end 44 | parameters = [ 45 | {:key=>"AUTH_SESSKEY", :value=>"900793991766BB9026FE18E9A7008722F3BCEA55F345B92FF64692670584995E"}, 46 | {:key=>"AUTH_VFR_DATA", :value=>"", :flags=>9}, 47 | ] 48 | 49 | it_should_behave_like "a PreAuthenticationResponse that reads properly", parameters 50 | end 51 | 52 | context "with an 11g response" do 53 | before :each do 54 | @binary_string = ("080103010C0C415554485F534553534B45590160604336374444454432354333" + 55 | "3431353943313937434544383533373546333032444443433939314239354242" + 56 | "4534454142363233433835443135433444423442413631373532433736323643" + 57 | "34463143443245353145324544303445424335413600010D0D415554485F5646" + 58 | "525F444154410114143638383736353037463035434445383644304436021B25" + 59 | "011A1A415554485F474C4F42414C4C595F554E495155455F4442494400012020" + 60 | "4337363744304239463736433446354646383942463445374238344438353832" + 61 | "0004010101020000000000000000000000000000000000000001000000000000").tns_unhexify 62 | 63 | @auth_sesskey = "C67DDED25C34159C197CED85375F302DDCC991B95BBE4EAB623C85D15C4DB4BA61752C7626C4F1CD2E51E2ED04EBC5A6".tns_unhexify 64 | @auth_vfr_data = "68876507F05CDE86D0D6".tns_unhexify 65 | end 66 | parameters = [ 67 | {:key=>"AUTH_SESSKEY", :value=>"C67DDED25C34159C197CED85375F302DDCC991B95BBE4EAB623C85D15C4DB4BA61752C7626C4F1CD2E51E2ED04EBC5A6"}, 68 | {:key=>"AUTH_VFR_DATA", :value=>"68876507F05CDE86D0D6", :flags=>0x1B25,}, 69 | {:key=>"AUTH_GLOBALLY_UNIQUE_DBID\0", :value=>"C767D0B9F76C4F5FF89BF4E7B84D8582",}, 70 | ] 71 | 72 | it_should_behave_like "a PreAuthenticationResponse that reads properly", parameters 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/net/tti/messages/protocol_negotiation_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | 4 | 5 | shared_examples_for "a ProtocolNegotiationRequest that functions properly" do 6 | it "should serialize properly" do 7 | kvp = Net::TTI::ProtocolNegotiationRequest.create_request() 8 | kvp.client_versions = versions if versions 9 | expect(kvp).to eql_binary_string(binary_string) 10 | end 11 | end 12 | 13 | module Net::TTI 14 | describe ProtocolNegotiationRequest do 15 | context "with a standard request" do 16 | it_should_behave_like "a ProtocolNegotiationRequest that functions properly" do 17 | let(:binary_string) {"01060504030201004a6176615f5454432d382e322e3000".tns_unhexify} 18 | let(:versions) {nil} 19 | end 20 | end 21 | 22 | context "with a request with multiple TTC versions" do 23 | it_should_behave_like "a ProtocolNegotiationRequest that functions properly" do 24 | let(:binary_string) {"01060504030201004a6176615f5454432d382e322e3000".tns_unhexify} 25 | let(:versions) {[6,5,4,3,2,1]} 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/net/tti/messages/protocol_negotiation_response_spec.rb: -------------------------------------------------------------------------------- 1 | require "tti_spec_helper" 2 | require "net/tti/message" 3 | require "net/tti/connection" 4 | 5 | 6 | shared_examples_for "a ProtocolNegotiationResponse that reads properly" do 7 | subject {Net::TTI::ConnectionParameters.new} 8 | 9 | before :each do 10 | response = Net::TTI::ProtocolNegotiationResponse.read( binary_string ) 11 | response.populate_connection_parameters( subject ) 12 | end 13 | 14 | its(:ttc_version) {should == version} 15 | its(:architecture) {should == architecture} 16 | its(:platform) {should == platform} 17 | end 18 | 19 | module Net::TTI 20 | describe ProtocolNegotiationResponse do 21 | context "with a response from a Windows 10g server" do 22 | it_should_behave_like "a ProtocolNegotiationResponse that reads properly" do 23 | let(:binary_string) {TtiSpecHelper.read_message("protocol_negotiation_response_windows_10g.raw")} 24 | let(:version) {6} 25 | let(:architecture) {:x86} 26 | let(:platform) {:windows} 27 | end 28 | end 29 | 30 | context "with a response from a Windows 11g server" do 31 | it_should_behave_like "a ProtocolNegotiationResponse that reads properly" do 32 | let(:binary_string) {TtiSpecHelper.read_message("protocol_negotiation_response_windows_11g.raw")} 33 | let(:version) {6} 34 | let(:architecture) {:x86} 35 | let(:platform) {:windows} 36 | end 37 | end 38 | 39 | context "with a response from a Linux 10g server" do 40 | it_should_behave_like "a ProtocolNegotiationResponse that reads properly" do 41 | let(:binary_string) {TtiSpecHelper.read_message("protocol_negotiation_response_linux_10g.raw")} 42 | let(:version) {6} 43 | let(:architecture) {:x86} 44 | let(:platform) {:linux} 45 | end 46 | end 47 | 48 | context "with a response from a Linux 11g server" do 49 | it_should_behave_like "a ProtocolNegotiationResponse that reads properly" do 50 | let(:binary_string) {TtiSpecHelper.read_message("protocol_negotiation_response_linux_11g.raw")} 51 | let(:version) {6} 52 | let(:architecture) {:x64} 53 | let(:platform) {:linux} 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_request_linux.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_request_linux.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_request_solaris_11g2.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_request_solaris_11g2.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_request_windows_10g.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_request_windows_10g.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_request_windows_9i.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_request_windows_9i.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_response_linux.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_response_linux.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/data_type_negotiation_response_windows.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/data_type_negotiation_response_windows.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/protocol_negotiation_response_linux_10g.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/protocol_negotiation_response_linux_10g.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/protocol_negotiation_response_linux_11g.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/protocol_negotiation_response_linux_11g.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/protocol_negotiation_response_linux_11g_r2.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/protocol_negotiation_response_linux_11g_r2.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/protocol_negotiation_response_windows_10g.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/protocol_negotiation_response_windows_10g.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/protocol_negotiation_response_windows_11g.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/net-tns/06a085f84aff46673d257be368636c40e76369f8/spec/net/tti/raw/protocol_negotiation_response_windows_11g.raw -------------------------------------------------------------------------------- /spec/net/tti/raw/stub.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "coveralls" 3 | Coveralls.wear! 4 | rescue LoadError 5 | end 6 | 7 | require "rspec" 8 | require "rspec/its" 9 | 10 | RSpec::Matchers.define :eql_binary_string do |expected| 11 | match do |actual| 12 | actual = actual.to_binary_s if actual.respond_to?(:to_binary_s) 13 | expected = expected.to_binary_s if expected.respond_to?(:to_binary_s) 14 | actual == expected 15 | end 16 | 17 | failure_message do |actual| 18 | actual = actual.to_binary_s if actual.respond_to?(:to_binary_s) 19 | expected = expected.to_binary_s if expected.respond_to?(:to_binary_s) 20 | [ 21 | " Expected: \"#{expected.tns_hexify}\"", 22 | " Got: \"#{actual.tns_hexify}\"", 23 | # " Expected: \"#{expected}\"", 24 | # " Got: \"#{actual}\"", 25 | ].join("\n") 26 | end 27 | 28 | failure_message_when_negated do |actual| 29 | "expected that the binary strings wouldn't match" 30 | end 31 | end 32 | 33 | module SpecHelpers 34 | class FakeSocket 35 | class SocketClosed < StandardError; end 36 | class NoMoreData < StandardError; end 37 | 38 | def initialize( host="127.0.0.1", port=1521 ) 39 | @dst_host = host 40 | @dst_port = port 41 | @io_out = StringIO.new() 42 | @io_in = StringIO.new() 43 | @closed = false 44 | end 45 | 46 | def peeraddr 47 | return [nil, @dst_port, nil, @dst_host] 48 | end 49 | 50 | # Socket-like functions 51 | def close 52 | @closed = true 53 | end 54 | 55 | def closed? 56 | @closed == true 57 | end 58 | 59 | def write(foo) 60 | raise SocketClosed if closed? 61 | 62 | @io_out.write(foo) 63 | end 64 | 65 | def read(length) 66 | data = @io_in.read(length) 67 | raise NoMoreData if data.nil? 68 | return data 69 | end 70 | 71 | 72 | # Administrative functions 73 | def _written_data 74 | return @io_out.string.dup.force_encoding("BINARY") 75 | end 76 | 77 | def _clear_written_data! 78 | @io_out = StringIO.new() 79 | end 80 | 81 | def _queue_response(data) 82 | data = data.to_binary_s if data.respond_to?(:to_binary_s) 83 | @io_in.string << data.force_encoding("BINARY") 84 | end 85 | 86 | def _has_unread_data? 87 | return ! @io_in.eof? 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/tns_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "net/tns" 3 | 4 | require "net/tns/helpers/string_helpers" 5 | 6 | module TnsSpecHelper 7 | SPEC_DIR = File.expand_path(File.dirname(__FILE__)) 8 | MSGS_DIR = File.join(SPEC_DIR, 'net', 'tns', 'raw') 9 | 10 | def self.read_message(filename) 11 | File.open(File.join(MSGS_DIR, filename), "rb") {|f| f.read} 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/tti_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "net/tti" 3 | 4 | require "net/tns/helpers/string_helpers" 5 | 6 | module TtiSpecHelper 7 | SPEC_DIR = File.expand_path(File.dirname(__FILE__)) 8 | MSGS_DIR = File.join(SPEC_DIR, 'net', 'tti', 'raw') 9 | 10 | def self.read_message(filename) 11 | File.open(File.join(MSGS_DIR, filename), "rb") {|f| f.read} 12 | end 13 | end 14 | --------------------------------------------------------------------------------