sip2 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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