sip2 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8f73fce390c230b1c0d95bba4221ebe77368b49a394635d84d8bd8314208ebb
4
- data.tar.gz: c1b9beff001b1e3774a57853d2b031c5154fe51b793be2c301968744e61ca08d
3
+ metadata.gz: 4d3b9a457d4ca0131eb2039eda501a303c75c69e4891fc1128f75096279134e7
4
+ data.tar.gz: 6bb443020825760ba59150d79eb23490d220123ab0b86567535f6b89cee5a46a
5
5
  SHA512:
6
- metadata.gz: 44d2323ee3939e99bdb192c5f61ce0be327321e99f5260592f62260ad3145d2681353ef759aff7a3193c09c7b5403e1f08f50fac661ecf22a063ec3ba475cfa8
7
- data.tar.gz: 22e1645b0b13fcdff5de58757a9ba052191173c7fd374572fdadfaeba99a66b4b495d3dbb2b98dc717692f2f3c0442c975769c6917aaf7097d4c6f9a600ffeee
6
+ metadata.gz: e0d0ce1e898a903b9713ab305631fa1e12a0d28eee2efad234b00c858a5d4aca2fef8e18449d1a1dd34ec2b0e549be6859eb9ace0d563a5037716ca62508c898
7
+ data.tar.gz: 800ff16573a4c25e76a8bcaf234dcf79745e3c3ee119d3276b8195ff8a0bc7fe1c6ad7eb0c1c920cce19076cd503e273ac3169b32ec7524e0f04872742a57557
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sip2/version'
2
4
 
5
+ require 'openssl'
6
+
3
7
  require 'sip2/patron_information'
4
8
 
5
9
  require 'sip2/messages/login'
@@ -1,20 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
4
  #
3
5
  # Sip2 Client
4
6
  #
5
7
  class Client
6
- def initialize(host:, port:, ignore_error_detection: false, timeout: nil)
8
+ def initialize(host:, port:, ignore_error_detection: false, timeout: nil, ssl_context: nil)
7
9
  @host = host
8
10
  @port = port
9
11
  @ignore_error_detection = ignore_error_detection
10
12
  @timeout = timeout || NonBlockingSocket::DEFAULT_TIMEOUT
13
+ @ssl_context = ssl_context
11
14
  end
12
15
 
16
+ # rubocop:disable Metrics/MethodLength
13
17
  def connect
14
- socket = NonBlockingSocket.connect @host, @port, @timeout
15
- yield Connection.new(socket, @ignore_error_detection) if block_given?
18
+ socket = NonBlockingSocket.connect host: @host, port: @port, timeout: @timeout
19
+
20
+ # If we've been provided with an SSL context then use it to wrap out existing connection
21
+ if @ssl_context
22
+ socket = ::OpenSSL::SSL::SSLSocket.new socket, @ssl_context
23
+ socket.hostname = @host # Needed for SNI
24
+ socket.sync_close = true
25
+ socket.connect
26
+ socket.post_connection_check @host # Validate the peer certificate matches the host
27
+ end
28
+
29
+ if block_given?
30
+ yield Connection.new(socket: socket, ignore_error_detection: @ignore_error_detection)
31
+ end
16
32
  ensure
17
- socket.close if socket
33
+ socket&.close
18
34
  end
35
+ # rubocop:enable Metrics/MethodLength
19
36
  end
20
37
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
4
  #
3
5
  # Sip2 Connection
4
6
  #
5
7
  class Connection
8
+ LINE_SEPARATOR = "\r"
9
+
6
10
  @connection_modules = []
7
11
 
8
12
  class << self
@@ -16,12 +20,17 @@ module Sip2
16
20
  include Messages::Login
17
21
  include Messages::PatronInformation
18
22
 
19
- def initialize(socket, ignore_error_detection)
23
+ def initialize(socket:, ignore_error_detection: false)
20
24
  @socket = socket
21
25
  @ignore_error_detection = ignore_error_detection
22
26
  @sequence = 1
23
27
  end
24
28
 
29
+ def send_message(message)
30
+ write_with_timeout message
31
+ read_with_timeout
32
+ end
33
+
25
34
  def method_missing(method_name, *args)
26
35
  if Connection.connection_modules.include?(method_name.to_sym)
27
36
  send_and_handle_message(method_name, *args)
@@ -45,13 +54,27 @@ module Sip2
45
54
  send "handle_#{message_type}_response", response
46
55
  end
47
56
 
48
- def send_message(message)
49
- @socket.send_with_timeout message
50
- @socket.gets_with_timeout
57
+ def write_with_timeout(message, separator: LINE_SEPARATOR)
58
+ ::Timeout.timeout connection_timeout, WriteTimeout do
59
+ @socket.write message + separator
60
+ end
61
+ end
62
+
63
+ def read_with_timeout(separator: LINE_SEPARATOR)
64
+ ::Timeout.timeout connection_timeout, ReadTimeout do
65
+ @socket.gets(separator)&.chomp(separator)
66
+ end
67
+ end
68
+
69
+ def connection_timeout
70
+ # We want the underlying connection where the timeout is configured,
71
+ # so if we're dealing with an SSLSocket then we need to unwrap it
72
+ io = @socket.respond_to?(:io) ? @socket.io : @socket
73
+ io.connection_timeout || NonBlockingSocket::DEFAULT_TIMEOUT
51
74
  end
52
75
 
53
76
  def with_error_detection(message)
54
- message + '|AY' + @sequence.to_s
77
+ "#{message}|AY#{@sequence}"
55
78
  end
56
79
 
57
80
  def with_checksum(message)
@@ -64,13 +87,13 @@ module Sip2
64
87
  message.each_char { |m| check += m.ord }
65
88
  check += "\0".ord
66
89
  check = (check ^ 0xFFFF) + 1
67
- format '%4.4X', check
90
+ format '%<check>4.4X', check: check
68
91
  end
69
92
 
70
93
  def sequence_and_checksum_valid?(response)
71
94
  return true if @ignore_error_detection
72
95
 
73
- sequence_regex = /^(?<message>.*?AY(?<sequence>[0-9]+)AZ)(?<checksum>[A-F0-9]{4})$/
96
+ sequence_regex = /\A(?<message>.*?AY(?<sequence>[0-9]+)AZ)(?<checksum>[A-F0-9]{4})\z/
74
97
  match = response.strip.match sequence_regex
75
98
  match &&
76
99
  match[:sequence] == @sequence.to_s &&
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
4
  module Messages
3
5
  #
@@ -10,11 +12,11 @@ module Sip2
10
12
 
11
13
  private
12
14
 
13
- def build_login_message(username, password, location_code = nil)
15
+ def build_login_message(username:, password:, location_code: nil)
14
16
  code = '93' # Login
15
17
  uid_algorithm = pw_algorithm = '0' # Plain text
16
- username_field = 'CN' + username
17
- password_field = 'CO' + password
18
+ username_field = "CN#{username}"
19
+ password_field = "CO#{password}"
18
20
  location_code = location_code.strip if location_code.is_a? String
19
21
  location_field = location_code ? "|CP#{location_code}" : ''
20
22
 
@@ -24,7 +26,7 @@ module Sip2
24
26
  end
25
27
 
26
28
  def handle_login_response(response)
27
- sequence_and_checksum_valid?(response) && response[/^94([01])AY/, 1] == '1'
29
+ sequence_and_checksum_valid?(response) && response[/\A94([01])AY/, 1] == '1'
28
30
  end
29
31
  end
30
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
4
  module Messages
3
5
  #
@@ -10,7 +12,7 @@ module Sip2
10
12
 
11
13
  private
12
14
 
13
- def build_patron_information_message(uid, password, terminal_password = nil)
15
+ def build_patron_information_message(uid:, password:, terminal_password: nil)
14
16
  code = '63' # Patron information
15
17
  language = '000' # Unknown
16
18
  timestamp = Time.now.strftime('%Y%m%d %H%M%S')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
4
  require 'timeout'
3
5
 
@@ -8,24 +10,11 @@ module Sip2
8
10
  #
9
11
  class NonBlockingSocket < Socket
10
12
  DEFAULT_TIMEOUT = 5
11
- SEPARATOR = "\r".freeze
12
13
 
13
14
  attr_accessor :connection_timeout
14
15
 
15
- def send_with_timeout(message, separator = SEPARATOR)
16
- ::Timeout.timeout (connection_timeout || DEFAULT_TIMEOUT), WriteTimeout do
17
- send message + separator, 0
18
- end
19
- end
20
-
21
- def gets_with_timeout(separator = SEPARATOR)
22
- ::Timeout.timeout (connection_timeout || DEFAULT_TIMEOUT), ReadTimeout do
23
- gets separator
24
- end
25
- end
26
-
27
16
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
28
- def self.connect(host, port, timeout = DEFAULT_TIMEOUT)
17
+ def self.connect(host:, port:, timeout: DEFAULT_TIMEOUT)
29
18
  # Convert the passed host into structures the non-blocking calls can deal with
30
19
  addr = Socket.getaddrinfo(host, nil)
31
20
  sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
@@ -47,7 +36,7 @@ module Sip2
47
36
  begin
48
37
  # Verify there is now a good connection
49
38
  socket.connect_nonblock(sockaddr)
50
- rescue Errno::EISCONN # rubocop:disable Lint/HandleExceptions
39
+ rescue Errno::EISCONN
51
40
  # Good news everybody, the socket is connected!
52
41
  rescue StandardError
53
42
  # An unexpected exception was raised - the connection is no good.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
4
  #
3
5
  # Sip2 Patron Information
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sip2
2
- VERSION = '0.1.1'.freeze
4
+ VERSION = '0.2.1'
3
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sip2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - abrom
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-01 00:00:00.000000000 Z
11
+ date: 2020-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.11'
19
+ version: '2'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.11'
26
+ version: '2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '12.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,28 +58,28 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '0.60'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '0.60'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: timecop
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '0.9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '0.9'
83
83
  description: 3M™ Standard Interchange Protocol v2 client implementation in Ruby
84
84
  email:
85
85
  - a.bromwich@gmail.com
@@ -87,13 +87,7 @@ executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
- - ".gitignore"
91
- - ".rubocop.yml"
92
- - ".travis.yml"
93
- - Gemfile
94
90
  - LICENSE
95
- - README.md
96
- - Rakefile
97
91
  - lib/sip2.rb
98
92
  - lib/sip2/client.rb
99
93
  - lib/sip2/connection.rb
@@ -102,7 +96,6 @@ files:
102
96
  - lib/sip2/non_blocking_socket.rb
103
97
  - lib/sip2/patron_information.rb
104
98
  - lib/sip2/version.rb
105
- - sip2.gemspec
106
99
  homepage: https://github.com/Studiosity/sip2-ruby
107
100
  licenses:
108
101
  - MIT
@@ -115,16 +108,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
108
  requirements:
116
109
  - - ">="
117
110
  - !ruby/object:Gem::Version
118
- version: 2.2.0
111
+ version: 2.4.0
119
112
  required_rubygems_version: !ruby/object:Gem::Requirement
120
113
  requirements:
121
114
  - - ">="
122
115
  - !ruby/object:Gem::Version
123
116
  version: '0'
124
117
  requirements: []
125
- rubyforge_project:
126
- rubygems_version: 2.7.6
118
+ rubygems_version: 3.0.6
127
119
  signing_key:
128
120
  specification_version: 4
129
- summary: 3M™ Standard Interchange Protocol v2 client implementation in Ruby
121
+ summary: SIP2 Ruby client
130
122
  test_files: []
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- *.gem
2
- Gemfile.lock
@@ -1,9 +0,0 @@
1
- Metrics/BlockLength:
2
- Exclude:
3
- - "**/*_spec.rb"
4
-
5
- Metrics/LineLength:
6
- Max: 100
7
-
8
- Metrics/ClassLength:
9
- Enabled: false
@@ -1,22 +0,0 @@
1
- language: ruby
2
- sudo: required
3
-
4
- rvm:
5
- - 2.2
6
- - 2.3
7
- - 2.4
8
- - 2.5
9
- # - 2.6 waiting for https://github.com/rubocop-hq/rubocop/issues/6412 to land
10
-
11
- before_install:
12
- - gem update bundler
13
-
14
- install:
15
- - bundle install --jobs=3 --retry=3
16
- - gem install rubocop
17
-
18
- script:
19
- - rubocop
20
- # Blackhole 127.0.0.2 for testing connection timeouts
21
- - sudo iptables -I INPUT -s 127.0.0.2 -j DROP
22
- - bundle exec rake
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in sip2.gemspec
4
- gemspec
data/README.md DELETED
@@ -1,65 +0,0 @@
1
- [![Travis Build Status](http://img.shields.io/travis/Studiosity/sip2-ruby.svg?style=flat)](https://travis-ci.org/Studiosity/sip2-ruby)
2
- [![Gem Version](http://img.shields.io/gem/v/sip2.svg?style=flat)](#)
3
-
4
- # 3M™ Standard Interchange Protocol v2 (SIP2) client implementation in Ruby
5
-
6
- This is a gem wrapping the SIP v2 protocol.
7
-
8
- http://multimedia.3m.com/mws/media/355361O/sip2-protocol.pdf
9
-
10
-
11
- ## Installation
12
-
13
- Add this line to your application's Gemfile:
14
-
15
- ```ruby
16
- gem 'sip2'
17
- ```
18
-
19
- And then execute:
20
-
21
- ```bash
22
- $ bundle
23
- ```
24
-
25
-
26
- ## Protocol support
27
-
28
- So far only login (code 93) and patron_information (code 63) are supported
29
-
30
-
31
- ## Usage
32
-
33
- ```ruby
34
- client = Sip2::Client.new(host: 'my.sip2.host.net', port: 6001)
35
- patron =
36
- client.connect do |connection|
37
- if connection.login 'sip_username', 'sip_password'
38
- connection.patron_information 'patron_username', 'patron_password'
39
- end
40
- end
41
-
42
- puts 'Valid patron' if patron && patron.authenticated?
43
- ```
44
-
45
-
46
- ## Contributing
47
-
48
- Bug reports and pull requests are welcome on GitHub at https://github.com/Studiosity/sip2-ruby.
49
-
50
- Note that spec tests are appreciated to minimise regressions. Before submitting a PR, please ensure that:
51
-
52
- ```bash
53
- $ rspec
54
- ```
55
- and
56
-
57
- ```bash
58
- $ rubocop
59
- ```
60
- both succeed
61
-
62
-
63
- ## License
64
-
65
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env rake
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
@@ -1,27 +0,0 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
-
4
- require 'sip2/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'sip2'
8
- spec.version = Sip2::VERSION
9
- spec.authors = ['abrom']
10
- spec.email = ['a.bromwich@gmail.com']
11
-
12
- spec.summary = '3M™ Standard Interchange Protocol v2 client implementation in Ruby'
13
- spec.description = '3M™ Standard Interchange Protocol v2 client implementation in Ruby'
14
- spec.homepage = 'https://github.com/Studiosity/sip2-ruby'
15
- spec.license = 'MIT'
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
18
- spec.require_paths = ['lib']
19
-
20
- spec.required_ruby_version = '>= 2.2.0'
21
-
22
- spec.add_development_dependency 'bundler', '>= 1.11'
23
- spec.add_development_dependency 'rake', '>= 10.0'
24
- spec.add_development_dependency 'rspec', '~> 3.0'
25
- spec.add_development_dependency 'rubocop', '~> 0'
26
- spec.add_development_dependency 'timecop', '~> 0'
27
- end