hanvon 0.0.2
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 +15 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +19 -0
- data/Rakefile +1 -0
- data/doc/HanvonSDKManual.pdf +0 -0
- data/hanvon.gemspec +24 -0
- data/lib/hanvon.rb +7 -0
- data/lib/hanvon/client.rb +85 -0
- data/lib/hanvon/crypto.rb +47 -0
- data/lib/hanvon/version.rb +3 -0
- data/spec/hanvon_crypto_spec.rb +77 -0
- data/spec/spec_helper.rb +1 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjMwZjdlYmU0ZDJjMjMzOTI1Y2Q1YzU5NTczYjBlZWY4OTBlNzZmNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZjlhYTY0YjAwODg4MTFjZGQyZmYxY2ExNjZlMDg0ZTgzN2M0M2MwNA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MTQ3MmJmMWQxOTFhNTQwY2ZhZWM5MDljZGNiYzFhMjI0YjRhYzBjMDE5MmI1
|
10
|
+
NjU0ZTczOGQxY2JhNjdjMGQxNzdlZGIzZmY0YmNlMTIxZTZjNmZhODk4NDcx
|
11
|
+
MTQ4NzE1ZmJjOTRhNTRlNTVhYmQ2ZThjYzA1ODQxYjNlNWNjOGE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
N2FmNWU3Y2Y5N2U3OGIwYTVjMzYxOWE2MzJiM2MxN2I4NDRiNjBkYTQ1NGJi
|
14
|
+
MDYyYzJhZmY2MmY1ODFiYTk0ODFmMGI3YzViNTQ2NWExZjhmMDNkMmQ0ZGE5
|
15
|
+
YTg1OTgyYTc5MjNjYmM5MjRiOGRhZjc3NWZjYjE3NjY2YjUwNWI=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Heinrich Lee Yu
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Ruby Hanvon Client Library
|
2
|
+
|
3
|
+
This is a ruby client that communicates with Hanvon Time & Attendance devices over the network.
|
4
|
+
|
5
|
+
Hanvon provides an SDK consisting only of a file named HwDevComm.dll. There is no official Linux support. Hence, I decided to reverse engineer the protocol. When used without a commukey / password, you can easily communicate with the device via a TCP socket and send the commands in plain text. However, when a key is used, messages sent and received are encrypted.
|
6
|
+
|
7
|
+
This is not official code from Hanvon. This is tested on the F710, but should work for the other models.
|
8
|
+
|
9
|
+
For more info on Hanvon devices, visit:
|
10
|
+
|
11
|
+
http://www.hanvon.com/en/products/FaceID/products/index.html
|
12
|
+
|
13
|
+
SDK documentation with list of functions is included in doc/
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'hanvon'
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
Binary file
|
data/hanvon.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hanvon/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hanvon"
|
8
|
+
spec.version = Hanvon::VERSION
|
9
|
+
spec.authors = ["Heinrich Lee Yu"]
|
10
|
+
spec.email = ["hleeyu@gmail.com"]
|
11
|
+
spec.description = %q{Ruby client for communicating with Hanvon Time & Attendance Devices over the network}
|
12
|
+
spec.summary = %q{Ruby client for Hanvon Time & Attendance Devices}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
data/lib/hanvon.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Hanvon
|
4
|
+
class Client
|
5
|
+
|
6
|
+
attr_accessor :host
|
7
|
+
attr_accessor :port
|
8
|
+
attr_accessor :password
|
9
|
+
|
10
|
+
attr_accessor :socket
|
11
|
+
attr_accessor :encryptor
|
12
|
+
|
13
|
+
def initialize(host, port = 9922, password = nil)
|
14
|
+
self.host = host
|
15
|
+
self.port = port
|
16
|
+
self.password = password
|
17
|
+
|
18
|
+
self.socket = TCPSocket.new(host, port)
|
19
|
+
self.encryptor = Crypto.new(password) unless password.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_command(command, params = {})
|
23
|
+
param_strings = []
|
24
|
+
params.each { |k,v|
|
25
|
+
param_strings << "#{k}=\"#{v}\""
|
26
|
+
}
|
27
|
+
|
28
|
+
send("#{command}(#{param_strings.join(" ")})")
|
29
|
+
end
|
30
|
+
|
31
|
+
def send(message)
|
32
|
+
socket.write(encryptor ? encryptor.encrypt(message) : message)
|
33
|
+
parse_reply(read_reply)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_reply(reply)
|
37
|
+
response = []
|
38
|
+
|
39
|
+
response_data = reply.sub(/\AReturn\(/, "").chomp(")")
|
40
|
+
response_data.split("\n").each_with_index { |r, i|
|
41
|
+
row_hash = {}
|
42
|
+
r.scan(/(\S+?)="(.*?)"/).each do |m|
|
43
|
+
row_hash[m[0]] = m[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
if i == 0
|
47
|
+
row_hash.delete('result')
|
48
|
+
row_hash.delete('dev_id')
|
49
|
+
end
|
50
|
+
|
51
|
+
response << row_hash unless row_hash.empty?
|
52
|
+
}
|
53
|
+
|
54
|
+
response = response.first if response.size == 1
|
55
|
+
|
56
|
+
return response
|
57
|
+
end
|
58
|
+
|
59
|
+
def close
|
60
|
+
socket.close
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def read_reply
|
66
|
+
reply = ""
|
67
|
+
|
68
|
+
while true
|
69
|
+
chunk = socket.recv(1024)
|
70
|
+
reply += encryptor ? encryptor.decrypt(chunk, reply.length % 8) : chunk
|
71
|
+
|
72
|
+
if reply[-1] == ')' and reply =~ /\A\w+\((?:\w+\s*=\s*".*"\s*)*\)\z/
|
73
|
+
if reply.start_with?("Wait(")
|
74
|
+
reply = ""
|
75
|
+
else
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
reply
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hanvon
|
2
|
+
class Crypto
|
3
|
+
|
4
|
+
class InvalidPasswordException < StandardError; end
|
5
|
+
|
6
|
+
attr_accessor :password
|
7
|
+
|
8
|
+
def initialize(password)
|
9
|
+
self.password = password
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert(message, offset = 0)
|
13
|
+
message.each_char.each_with_index.collect do |char, pos|
|
14
|
+
convert_char(char, pos + offset)
|
15
|
+
end.pack('C*')
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :encrypt, :convert
|
19
|
+
alias_method :decrypt, :convert
|
20
|
+
|
21
|
+
def password=(password)
|
22
|
+
if valid_password?(password)
|
23
|
+
@password = password
|
24
|
+
compute_key
|
25
|
+
else
|
26
|
+
raise InvalidPasswordException
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def convert_char(char, pos)
|
33
|
+
return char.ord ^ @key[pos % 8]
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_password?(password)
|
37
|
+
password =~ /\A\d{1,8}\z/
|
38
|
+
end
|
39
|
+
|
40
|
+
def compute_key
|
41
|
+
@key = (0..7).collect do |pos|
|
42
|
+
(password[pos] || 0).ord + (2 ** pos >> 1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hanvon::Crypto do
|
4
|
+
|
5
|
+
it "should not accept empty password" do
|
6
|
+
lambda {
|
7
|
+
Hanvon::Crypto.new('')
|
8
|
+
}.should raise_exception(Hanvon::Crypto::InvalidPasswordException)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not accept password with more than 8 digits" do
|
12
|
+
lambda {
|
13
|
+
Hanvon::Crypto.new('123123123')
|
14
|
+
}.should raise_exception(Hanvon::Crypto::InvalidPasswordException)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should not accept non-numeric password" do
|
18
|
+
lambda {
|
19
|
+
Hanvon::Crypto.new('abc')
|
20
|
+
}.should raise_exception(Hanvon::Crypto::InvalidPasswordException)
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with password 12345678" do
|
24
|
+
before :all do
|
25
|
+
@crypto = Hanvon::Crypto.new('12345678')
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#encrypt" do
|
29
|
+
it "returns encrypted message" do
|
30
|
+
@crypto.encrypt("GetDeviceInfo()").should == ['7656417c58303e1b547a5b5e526e7e'].pack("H*")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#decrypt" do
|
35
|
+
it "returns original message" do
|
36
|
+
@crypto.decrypt(['7656417c58303e1b547a5b5e526e7e'].pack("H*")).should == "GetDeviceInfo()"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "with password 00000000" do
|
42
|
+
before :all do
|
43
|
+
@crypto = Hanvon::Crypto.new('00000000')
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#encrypt" do
|
47
|
+
it "returns encrypted message" do
|
48
|
+
@crypto.encrypt("GetDeviceInfo()").should == ['775446705d36391355785c52576879'].pack("H*")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#decrypt" do
|
53
|
+
it "returns original message" do
|
54
|
+
@crypto.decrypt(['775446705d36391355785c52576879'].pack("H*")).should == "GetDeviceInfo()"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with password 1234" do
|
60
|
+
before :all do
|
61
|
+
@crypto = Hanvon::Crypto.new('1234')
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#encrypt" do
|
65
|
+
it "returns encrypted message" do
|
66
|
+
@crypto.encrypt("GetDeviceInfo()").should == ['7656417c6d664923547a5b5e673809'].pack("H*")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#decrypt" do
|
71
|
+
it "returns original message" do
|
72
|
+
@crypto.decrypt(['7656417c6d664923547a5b5e673809'].pack("H*")).should == "GetDeviceInfo()"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'hanvon'
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hanvon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Heinrich Lee Yu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-29 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.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '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: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Ruby client for communicating with Hanvon Time & Attendance Devices over
|
56
|
+
the network
|
57
|
+
email:
|
58
|
+
- hleeyu@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- doc/HanvonSDKManual.pdf
|
69
|
+
- hanvon.gemspec
|
70
|
+
- lib/hanvon.rb
|
71
|
+
- lib/hanvon/client.rb
|
72
|
+
- lib/hanvon/crypto.rb
|
73
|
+
- lib/hanvon/version.rb
|
74
|
+
- spec/hanvon_crypto_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
homepage: ''
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.2.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Ruby client for Hanvon Time & Attendance Devices
|
100
|
+
test_files:
|
101
|
+
- spec/hanvon_crypto_spec.rb
|
102
|
+
- spec/spec_helper.rb
|