sip2 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d87384752360718cc923357a88f890ab47ad5a3
4
+ data.tar.gz: 8adea44884170a7c508ca20e9fa4e074f7ef0546
5
+ SHA512:
6
+ metadata.gz: 26c9cb0b476a7c35939b14975a241b3e1d73a0fecdc2bdaecb20f3cec1f570ce8aae649d7d810a2cc246a2a138ac1037a26a7dd4828f7fb7be02b787e11422f8
7
+ data.tar.gz: f77ac5118fe0ab0f2ea2db4acf622424168f872f475ac7331e6e5e6ee63c618a6b014da3de12c4fc11b152cb82c73105fde8e9ecf63d38c3a1710b3d8d048c5e
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,6 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - "**/*_spec.rb"
4
+
5
+ Metrics/LineLength:
6
+ Max: 100
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - 2.2.5
6
+ - 2.3.0
7
+ - 2.4.0
8
+
9
+ before_install:
10
+ - gem update bundler
11
+
12
+ install:
13
+ - bundle install --jobs=3 --retry=3
14
+ - gem install rubocop
15
+
16
+ script:
17
+ - rubocop
18
+ - bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sip2.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Andrew Bromwich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ [![Travis Build Status](http://img.shields.io/travis/abrom/sip2-ruby.svg?style=flat)](https://travis-ci.org/abrom/sip2-ruby)
2
+ [![Code Climate Score](http://img.shields.io/codeclimate/github/abrom/sip2-ruby.svg?style=flat)](https://codeclimate.com/github/abrom/sip2-ruby)
3
+ [![Gem Version](http://img.shields.io/gem/v/sip2.svg?style=flat)](#)
4
+
5
+ # 3M™ Standard Interchange Protocol v2 (SIP2) client implementation in Ruby
6
+
7
+ This is a gem wrapping the SIP v2 protocol.
8
+
9
+ http://multimedia.3m.com/mws/media/355361O/sip2-protocol.pdf
10
+
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'sip2'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```bash
23
+ $ bundle
24
+ ```
25
+
26
+
27
+ ## Usage
28
+
29
+ TODO
30
+
31
+
32
+ ## Contributing
33
+
34
+ Bug reports and pull requests are welcome on GitHub at https://github.com/abrom/sip2-ruby.
35
+
36
+ Note that spec tests are appreciated to minimise regressions. Before submitting a PR, please ensure that:
37
+
38
+ ```bash
39
+ $ rspec
40
+ ```
41
+ and
42
+
43
+ ```bash
44
+ $ rubocop
45
+ ```
46
+ both succeed
47
+
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
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
data/lib/sip2.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'sip2/version'
2
+
3
+ require 'sip2/patron_information'
4
+
5
+ require 'sip2/non_blocking_socket'
6
+ require 'sip2/connection'
7
+ require 'sip2/client'
@@ -0,0 +1,22 @@
1
+ module Sip2
2
+ #
3
+ # Sip2 Client
4
+ #
5
+ class Client
6
+ def initialize(host:, port:, ignore_error_detection: false)
7
+ @host = host
8
+ @port = port
9
+ @ignore_error_detection = ignore_error_detection
10
+ end
11
+
12
+ def connect
13
+ socket = NonBlockingSocket.connect @host, @port
14
+ if block_given?
15
+ connection = Connection.new(socket, @ignore_error_detection)
16
+ yield connection
17
+ end
18
+ ensure
19
+ socket.close if socket
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,92 @@
1
+ module Sip2
2
+ #
3
+ # Sip2 Connection
4
+ #
5
+ class Connection
6
+ def initialize(socket, ignore_error_detection)
7
+ @socket = socket
8
+ @ignore_error_detection = ignore_error_detection
9
+ @sequence = 1
10
+ end
11
+
12
+ def login(username, password, location_code = nil)
13
+ login_message = build_login_message(username, password, location_code)
14
+ response = send_message login_message
15
+ login_successful? response
16
+ end
17
+
18
+ def patron_information(patron_uid, password)
19
+ patron_message = build_patron_message(patron_uid, password)
20
+ response = send_message patron_message
21
+ return unless sequence_and_checksum_valid?(response)
22
+ PatronInformation.new response
23
+ end
24
+
25
+ private
26
+
27
+ def send_message(message)
28
+ @socket.send(message + "\r", 0)
29
+ @socket.gets "\r"
30
+ end
31
+
32
+ def with_error_detection_and_checksum(message)
33
+ with_checksum with_error_detection message
34
+ end
35
+
36
+ def with_error_detection(message)
37
+ message + '|AY' + @sequence.to_s
38
+ end
39
+
40
+ def with_checksum(message)
41
+ message += 'AZ'
42
+ message + checksum_for(message)
43
+ end
44
+
45
+ def checksum_for(message)
46
+ check = 0
47
+ message.each_char { |m| check += m.ord }
48
+ check += "\0".ord
49
+ check = (check ^ 0xFFFF) + 1
50
+ format '%4.4X', check
51
+ end
52
+
53
+ def sequence_and_checksum_valid?(response)
54
+ return true if @ignore_error_detection
55
+ sequence_regex = /^(?<message>.*?AY(?<sequence>[0-9]+)AZ)(?<checksum>[A-F0-9]{4})$/
56
+ match = response.strip.match sequence_regex
57
+ match &&
58
+ match[:sequence] == @sequence.to_s &&
59
+ match[:checksum] == checksum_for(match[:message])
60
+ ensure
61
+ @sequence += 1
62
+ end
63
+
64
+ def build_login_message(username, password, location_code)
65
+ code = '93' # Login
66
+ uid_algorithm = pw_algorithm = '0' # Plain text
67
+ username_field = 'CN' + username
68
+ password_field = 'CO' + password
69
+ location_code = location_code.strip if location_code.is_a? String
70
+ location_field = location_code ? "|CP#{location_code}" : ''
71
+
72
+ message = [
73
+ code, uid_algorithm, pw_algorithm, username_field, '|', password_field, location_field
74
+ ].join
75
+
76
+ with_error_detection_and_checksum message
77
+ end
78
+
79
+ def login_successful?(response)
80
+ sequence_and_checksum_valid?(response) && response[/^94([01])AY/, 1] == '1'
81
+ end
82
+
83
+ def build_patron_message(uid, password)
84
+ code = '63' # Patron information
85
+ language = '000' # Unknown
86
+ timestamp = Time.now.strftime('%Y%m%d %H%M%S')
87
+ summary = ' ' * 10
88
+ message = "#{code}#{language}#{timestamp}#{summary}AO|AA#{uid}|AC|AD#{password}"
89
+ with_error_detection_and_checksum message
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,48 @@
1
+ require 'socket'
2
+
3
+ module Sip2
4
+ #
5
+ # Sip2 Non-blocking socket
6
+ # From https://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/
7
+ #
8
+ class NonBlockingSocket
9
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
+ def self.connect(host, port, timeout = 5)
11
+ # Convert the passed host into structures the non-blocking calls can deal with
12
+ addr = Socket.getaddrinfo(host, nil)
13
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
14
+
15
+ Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
16
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
17
+
18
+ begin
19
+ # Initiate the socket connection in the background. If it doesn't fail
20
+ # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
21
+ # indicating the connection is in progress.
22
+ socket.connect_nonblock(sockaddr)
23
+ rescue IO::WaitWritable
24
+ # IO.select will block until the socket is writable or the timeout
25
+ # is exceeded - whichever comes first.
26
+ if IO.select(nil, [socket], nil, timeout)
27
+ begin
28
+ # Verify there is now a good connection
29
+ socket.connect_nonblock(sockaddr)
30
+ rescue Errno::EISCONN # rubocop:disable Lint/HandleExceptions
31
+ # Good news everybody, the socket is connected!
32
+ rescue
33
+ # An unexpected exception was raised - the connection is no good.
34
+ socket.close
35
+ raise
36
+ end
37
+ else
38
+ # IO.select returns nil when the socket is not ready before timeout
39
+ # seconds have elapsed
40
+ socket.close
41
+ raise 'Connection timeout'
42
+ end
43
+ end
44
+ end
45
+ end
46
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ module Sip2
2
+ #
3
+ # Sip2 Patron Information
4
+ #
5
+ class PatronInformation
6
+ attr_reader :raw_response
7
+
8
+ def initialize(patron_response)
9
+ @raw_response = patron_response
10
+ end
11
+
12
+ def patron_valid?
13
+ raw_response[/\|BL([YN])\|/, 1] == 'Y'
14
+ end
15
+
16
+ def authenticated?
17
+ raw_response[/\|CQ([YN])\|/, 1] == 'Y'
18
+ end
19
+
20
+ def email
21
+ raw_response[/\|BE(.*?)\|/, 1]
22
+ end
23
+
24
+ def inspect
25
+ format(
26
+ '#<%s:0x%p @patron_valid="%s" @email="%s" @authenticated="%s">',
27
+ self.class.name,
28
+ object_id,
29
+ patron_valid?,
30
+ email,
31
+ authenticated?
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Sip2
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/sip2.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'sip2/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'sip2'
10
+ spec.version = Sip2::VERSION
11
+ spec.authors = ['abrom']
12
+ spec.email = ['a.bromwich@gmail.com']
13
+
14
+ spec.summary = '3M™ Standard Interchange Protocol v2 client implementation in Ruby'
15
+ spec.description = '3M™ Standard Interchange Protocol v2 client implementation in Ruby'
16
+ spec.homepage = 'https://github.com/abrom/sip2-ruby'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.11'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'rubocop', '~> 0.48.1'
28
+ spec.add_development_dependency 'timecop', '~> 0'
29
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sip2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - abrom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.48.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.48.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: 3M™ Standard Interchange Protocol v2 client implementation in Ruby
84
+ email:
85
+ - a.bromwich@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rubocop.yml"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - lib/sip2.rb
98
+ - lib/sip2/client.rb
99
+ - lib/sip2/connection.rb
100
+ - lib/sip2/non_blocking_socket.rb
101
+ - lib/sip2/patron_information.rb
102
+ - lib/sip2/version.rb
103
+ - sip2.gemspec
104
+ homepage: https://github.com/abrom/sip2-ruby
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.0.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.4.8
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: 3M™ Standard Interchange Protocol v2 client implementation in Ruby
128
+ test_files: []