sip2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []