client_for_poslynx 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZmJmMWFiZTY5MDAyYWRiMDM4ZGUxY2ZhYzJhZjEwNmJmMDc3ODU3NQ==
4
+ YzhiNDI0YzUzYjUzNTZjMTQ0ZDQ0ZmYzOTk1MmY2ZWUyNjY4ZjNlNw==
5
5
  data.tar.gz: !binary |-
6
- NmZkZTU4MGVlOGVjY2VkMzgxNjU2MjdhM2E3MDFhOWUyNmQyOWY1Yg==
6
+ N2Y1MDE0NGU2NDczOWNiYzVjMTU0ODMwMWQ5MDNjNTM3NWE5NDVjNg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YTliZjg2OGE3MjIwYjMwNzFiZmI5ZjVhZGVkMDRmZWE2ZmVkM2E0ZWQwZjk5
10
- NjQ4NzUxNzg5OGI0NGZhZGVjNGY3YjI0ZGNiYTZkNDAxMDVlNDE3ZjY0Y2U0
11
- ZDkxOThmODRmMWYwMTI5ZjRkMWVlM2YwZGVjNzgxMThiYTliNWY=
9
+ OWJmOGE4ZWFiZWJkNDIwMDgzYzhkOTdhNDYyOGI5NjA1ZjRhYjllMzM5OGUx
10
+ MzVjNjkyNmEzOGUyNWQxNThjM2Q0MWM0ZjJjMjNlYWZkODMxNmY0YTA4ZDhh
11
+ ZDNkOTA1ZTc3NWU1MjA3MWY5OGMwZGFmOWRkYjgxOTExODMxZmI=
12
12
  data.tar.gz: !binary |-
13
- YmEwNTQ1NzZlM2RhNjc1YzNlZTg0ZDk2NDhhMmQ1OWMxODZhMjY2YWZkYjkw
14
- ZDg3NWJiZTJkOWZiMDY2ODY5MTkyODZlMTlmMTlhNmVlODNlNzcwY2UzMTFl
15
- NDRmZjlhNGViZWZhNjk2ZjJlZjM4OWE1YzNjZmUxMjkxNmZmY2Q=
13
+ Mjk2YjA5MWU2Zjg4NTQ3M2YxMDEwOGM5YzJjYjc2MTAwYWNlNDJjMmQwMDM5
14
+ NGJkNDEzM2I2YmNlZDRkMDNiMWU0YWQ5MmYwYjBiM2JmMWQ5YjdlODI5NDhm
15
+ MmMwMDlkMTAzMzgzOWExM2ZlNWZiMGE0Mjc2Mzk2YmIzNTE5OTk=
data/CHANGELOG CHANGED
@@ -24,8 +24,14 @@ Version 0.2.4
24
24
  - Fix bug: Fake terminal script crashes during user interaction
25
25
  for a debit sale request without a specified cash back amount.
26
26
 
27
- Version 0.2.4
27
+ Version 0.2.5
28
28
  - Introduce experimental signature image parsing and conversion
29
29
  to SVG.
30
30
  - Fix bug: New format of POSLynx response message XML was causing
31
31
  client to hang.
32
+
33
+ Version 0.2.6
34
+ - Add enhanced-format signature data handling.
35
+ - Fix misconception about signed number representation in
36
+ signature image data.
37
+ - Bring signature image handling out of experimental status.
data/README.md CHANGED
@@ -20,11 +20,12 @@ unit with IP address 192.168.1.123, listening on port 54321, with
20
20
  a registered client MAC of 000000000000, an example poslynx
21
21
  client session might look like...
22
22
 
23
- bundle exec poslynx_client_console 192.168.1.123:54321
24
- 1.9.3-p545 :001 > poslynx_client.client_mac_for_examples = '0' * 12
25
- => "000000000000"
26
- 1.9.3-p545 :002 > resp = poslynx_client.send_request( poslynx_client.example_pin_pad_display_message_request )
27
- => #<ClientForPoslynx::Data::Responses::PinPadDisplayMessage:0x007fbf529a17f8 @result="SUCCESS", @result_text="Success", @error_code="0000", @button_response="2nd button", @source_data="<PLResponse><Command>PPDISPLAY</Command><Result>SUCCESS</Result><ResultText>Success</ResultText><ErrorCode>0000</ErrorCode><Response>2nd button</Response></PLResponse>\n">
23
+ $ bundle exec poslynx_client_console 173.195.60.144:14270 --client_mac=000000000000
24
+ 1.9.3-p545 :001 > req = poslynx_client.example_pin_pad_display_message_request
25
+ => #<ClientForPoslynx::Data::Requests::PinPadDisplayMessage:0x007f941b4b1fe8 @client_mac="001C42E644FE", @text_lines=["First example line", "Second example line"], @line_count=2, @button_labels=["1st of optional buttons", "2nd button"]>
26
+ 1.9.3-p545 :002 > resp = poslynx_client.send_request(req)
27
+ <?xml version="1.0" standalone="yes" ?><PLResponse><Command>PPDISPLAY</Command><Result>Success</Result><ResultText>Success</ResultText><Response>2nd button</Response></PLResponse>
28
+ => #<ClientForPoslynx::Data::Responses::PinPadDisplayMessage:0x007f941b4b8230 @result="Success", @result_text="Success", @button_response="2nd button", @source_data="<?xml version=\"1.0\" standalone=\"yes\" ?><PLResponse><Command>PPDISPLAY</Command><Result>Success</Result><ResultText>Success</ResultText><Response>2nd button</Response></PLResponse>\r\n">
28
29
 
29
30
  This gem also provides a fake POS/terminal application that you
30
31
  can run in a separate console window when you are working without
@@ -41,7 +42,7 @@ fake POS/terminal instance on the same machine...
41
42
  ## Usage
42
43
 
43
44
  The code in the
44
- lib/client_for_poslynx/has_client_colsole_support.rb file
45
+ lib/client_for_poslynx/has_client_console_support.rb file
45
46
  provides a good example of how to use the facilities that this
46
47
  gem provides.
47
48
 
@@ -53,7 +54,7 @@ be separately loaded by requiring "client_for_poslynx/experimental'.
53
54
  * Only a subset of the possible messages and elements is supported.
54
55
  __More will be added. Contributions are welcome and encouraged. :)__
55
56
  * Performs serialization of requests and parsing of responses, but
56
- does not ecapsulate actually making TCP connections and requests.
57
+ does not encapsulate actually making TCP connections and requests.
57
58
 
58
59
  ## Installation
59
60
 
@@ -12,8 +12,13 @@ end
12
12
  # file name for input.
13
13
  $*.replace []
14
14
 
15
- # The only option we car about is --help, so we either show usage
16
- # for --help or because an unrecognized option was given.
15
+ # Take --client_mac=<hexadecimal-value> if present.
16
+ client_mac_opts = option_args.select{ |opt| opt =~ /^--client_mac=[0-9a-fA-F]+$/ }
17
+ option_args -= client_mac_opts
18
+ client_mac_option = client_mac_opts.last
19
+
20
+ # The only remaining option we care about is --help, so we either
21
+ # show usage for --help or because an unrecognized option was given.
17
22
  show_usage = option_args.length > 0
18
23
 
19
24
  # Should have exactly 1 value argument for the address + port number.
@@ -33,19 +38,27 @@ if show_usage
33
38
 
34
39
  puts <<-EOS
35
40
 
36
- Usage: poslynx_client_console [<host>]:<port-number> Host is the
37
- domain name or IP address of the POSLynx or fake POS terminal
38
- host. If this is not supplied, then 127.0.0.1 is assumed.
41
+ Usage: poslynx_client_console [<host>]:<port-number>
42
+
43
+ Host is the domain name or IP address of the POSLynx or fake POS
44
+ terminal host. If this is not supplied, then 127.0.0.1 is
45
+ assumed.
46
+
47
+ Options:
48
+
49
+ --client_mac=<hexadecimal> The client MAC value to be assigned
50
+ to example-request objects.
51
+
52
+ --help Prints this usage information.
39
53
 
40
54
  Opens an interactive Ruby shell with a globally acccessible
41
55
  poslynx_client method that returns an object with conveniences
42
56
  for making requests to a POSLynx or to your fake
43
57
  POSLynx+terminal host.
44
58
 
45
- For example, you might execute the following commands from
59
+ For example, you might execute the following command from
46
60
  within the console...
47
61
 
48
- poslynx_client.client_mac_for_examples = '123456789012'
49
62
  resp = poslynx_client.send_request( poslynx_client.example_credit_card_sale_request )
50
63
 
51
64
  To see what else the poslynx_client object provides, see the
@@ -70,6 +83,10 @@ else
70
83
  config.host = host
71
84
  config.port = port
72
85
 
86
+ if client_mac_option
87
+ config.client_mac_for_examples = client_mac_option.split('=').last
88
+ end
89
+
73
90
  IRB.start
74
91
 
75
92
  end
@@ -41,15 +41,25 @@ module ClientForPoslynx
41
41
  bit_seq
42
42
  end
43
43
 
44
- def from_sign_and_magnitude_of(value, seq_length)
45
- magnitude = value.abs
46
- magnitude_seq = from_unsigned( magnitude, seq_length - 1 )
47
- sign_bit = from_bit_digits( value < 0 ? '1' : '0' )
48
- magnitude_seq.unshift( sign_bit )
44
+ def from_signed(value, seq_length)
45
+ if seq_length > 64
46
+ raise TooManyBitsLong, "Can't build a representation more than 64 bits long from a signed number"
47
+ end
48
+ max_magnitude = 2 ** (seq_length - 1)
49
+ if value < -max_magnitude
50
+ raise NumberOutOfBounds, "The largest negative value representable in #{seq_length} bits is smaller than than #{value}"
51
+ end
52
+ if value >= max_magnitude
53
+ raise NumberOutOfBounds, "The largest positive value representable in #{seq_length} bits is less than #{value}"
54
+ end
55
+ packed_bits = [ value ].pack('q>')
56
+ bit_seq = from_packed_bits( packed_bits )
57
+ bit_seq.shift( 64 - seq_length )
58
+ bit_seq
49
59
  end
50
60
 
51
- def from_uuencoded(uuencoded)
52
- packed_bits = uuencoded.unpack('u').first
61
+ def from_base64(encoded)
62
+ packed_bits = encoded.unpack('m0').first
53
63
  from_packed_bits( packed_bits )
54
64
  end
55
65
  end
@@ -81,8 +91,8 @@ module ClientForPoslynx
81
91
  digits_string
82
92
  end
83
93
 
84
- def uuencode
85
- [ to_packed_bits ].pack('u')
94
+ def base64_encode
95
+ [ to_packed_bits ].pack('m0')
86
96
  end
87
97
 
88
98
  def length
@@ -111,24 +121,25 @@ module ClientForPoslynx
111
121
  self
112
122
  end
113
123
 
114
- def as_unsigned
124
+ def as_signed
115
125
  if length > 64
116
126
  raise TooManyBitsLong,
117
- "Cannot coerce sequence longer than 64 bits to unsigned number"
127
+ "Cannot coerce sequence longer than 64 bits to signed number"
118
128
  end
119
- little_endian_digits = digits_string.reverse + '0' * 64
129
+ fill = first_bit_digit * 64
130
+ little_endian_digits = digits_string.reverse + fill
120
131
  packed_little_endian = [ little_endian_digits ].pack('b*')
121
- packed_little_endian.unpack('Q<').first
132
+ packed_little_endian.unpack('q<').first
122
133
  end
123
134
 
124
- def as_sign_and_magnitude
125
- if length > 65
135
+ def as_unsigned
136
+ if length > 64
126
137
  raise TooManyBitsLong,
127
- "Cannot coerce sequence longer than 65 bits to unsigned number"
138
+ "Cannot coerce sequence longer than 64 bits to unsigned number"
128
139
  end
129
- magnitude_seq = self.class.from_bit_digits( digits_string[1..-1] )
130
- magnitude = magnitude_seq.as_unsigned
131
- first_bit_digit == '1' ? - magnitude : magnitude
140
+ little_endian_digits = digits_string.reverse + '0' * 64
141
+ packed_little_endian = [ little_endian_digits ].pack('b*')
142
+ packed_little_endian.unpack('Q<').first
132
143
  end
133
144
 
134
145
  private
@@ -15,12 +15,6 @@ module ClientForPoslynx
15
15
  def initialize(root_element_name)
16
16
  @root_element_name = root_element_name
17
17
  end
18
-
19
- # def parse(source_xml)
20
- # xml_document = XmlDocument.new( source_xml )
21
- # xml_document.property_element_values
22
- # end
23
-
24
18
  end
25
19
 
26
20
  end
@@ -27,6 +27,13 @@ module ClientForPoslynx
27
27
  attr_element_mapping attribute: :receipt, element: 'Receipt', numbered_lines: 'Receipt%d'
28
28
  attr_element_mapping attribute: :customer_receipt, element: 'ReceiptCustomer', numbered_lines: 'Receipt%d'
29
29
 
30
+ def signature=(value)
31
+ if value =~ /^<!\[CDATA\[(.*)\]\]>$/
32
+ @signature = $1
33
+ else
34
+ @signature = value
35
+ end
36
+ end
30
37
  end
31
38
 
32
39
  end
@@ -3,14 +3,4 @@
3
3
  # Require this file to load experimental features that
4
4
  # are not yet expected to be fully usable.
5
5
 
6
- # Bit sequence is actually in pretty good shape, but it's
7
- # here because it is only used by SignatureImage.
8
- require "client_for_poslynx/bit_sequence"
9
-
10
- # SignatureImage currently only supports the legacy
11
- # Hypercom/Equinox signature data format, which is useful
12
- # to approximately 0% of POSLynx users. It has also not
13
- # been tested with real data produced by a payment terminal,
14
- # so it is possible that some understandings about the
15
- # format are incorrect.
16
- require "client_for_poslynx/signature_image"
6
+ # No experimental features at this time.
@@ -12,8 +12,6 @@ module ClientForPoslynx
12
12
  end
13
13
 
14
14
  class Client
15
- attr_accessor :client_mac_for_examples
16
-
17
15
  def config
18
16
  @config ||= Config.new
19
17
  end
@@ -95,13 +93,13 @@ module ClientForPoslynx
95
93
  end
96
94
 
97
95
  def assign_common_example_request_attrs_to(request)
98
- request.client_mac = client_mac_for_examples if client_mac_for_examples
96
+ request.client_mac = config.client_mac_for_examples if config.client_mac_for_examples
99
97
  end
100
98
 
101
99
  end
102
100
 
103
101
  class Config
104
- attr_accessor :host, :port
102
+ attr_accessor :host, :port, :client_mac_for_examples
105
103
  end
106
104
 
107
105
  end
@@ -23,7 +23,6 @@ module ClientForPoslynx
23
23
  root_name = nil
24
24
  while true
25
25
  line = stream.gets
26
- puts line
27
26
  message << line
28
27
  if (! root_name) && line =~ /^(?:<\?.+?\?>)?<([A-Za-z_][^\s>]*)[ >]/
29
28
  root_name = $1
@@ -7,16 +7,16 @@ module ClientForPoslynx
7
7
  DELTA_BITS_LONG = 6
8
8
  BIT_SEQUENCE_LENGTH = 1 + DELTA_BITS_LONG * 2
9
9
 
10
- def self.first_in_bit_sequence(bit_seq)
10
+ def self.first_in_bit_sequence(bit_seq, format=nil)
11
11
  bit_seq.first_bit_digit == '0' &&
12
12
  bit_seq.length >= BIT_SEQUENCE_LENGTH
13
13
  end
14
14
 
15
- def self.parse_from_bit_sequence!(bit_seq)
15
+ def self.parse_from_bit_sequence!(bit_seq, format=nil)
16
16
  bit_seq.shift 1
17
17
  dx_bit_seq = bit_seq.shift( DELTA_BITS_LONG )
18
18
  dy_bit_seq = bit_seq.shift( DELTA_BITS_LONG )
19
- new( dx_bit_seq.as_sign_and_magnitude, dy_bit_seq.as_sign_and_magnitude )
19
+ new( dx_bit_seq.as_signed, dy_bit_seq.as_signed )
20
20
  end
21
21
 
22
22
  attr_reader :dx, :dy
@@ -31,10 +31,10 @@ module ClientForPoslynx
31
31
  return dx == other.dx && dy == other.dy
32
32
  end
33
33
 
34
- def to_bit_sequence
34
+ def to_bit_sequence(serialization_format=nil)
35
35
  bit_seq = ClientForPoslynx::BitSequence / '0'
36
- bit_seq << ClientForPoslynx::BitSequence.from_sign_and_magnitude_of( dx, DELTA_BITS_LONG )
37
- bit_seq << ClientForPoslynx::BitSequence.from_sign_and_magnitude_of( dy, DELTA_BITS_LONG )
36
+ bit_seq << ClientForPoslynx::BitSequence.from_signed( dx, DELTA_BITS_LONG )
37
+ bit_seq << ClientForPoslynx::BitSequence.from_signed( dy, DELTA_BITS_LONG )
38
38
  end
39
39
 
40
40
  end
@@ -0,0 +1,17 @@
1
+ module ClientForPoslynx
2
+ class SignatureImage
3
+
4
+ class Metrics <
5
+ Struct.new(
6
+
7
+ # [x,y] scaled resolution
8
+ :resolution,
9
+
10
+ # [x,y] pysical size in dekamicrometers
11
+ # 1 dekamicrometer = 10 micrometers or 0.01 millimeters
12
+ :size_in_dum,
13
+ )
14
+ end
15
+
16
+ end
17
+ end
@@ -4,19 +4,19 @@ module ClientForPoslynx
4
4
  class SignatureImage
5
5
 
6
6
  class Move
7
- X_BITS_LONG = 10
8
- Y_BITS_LONG = 7
9
- BIT_SEQUENCE_LENGTH = 1 + X_BITS_LONG + Y_BITS_LONG
7
+ X_BITS_LONG = { legacy: 10, enhanced_narrow: 10, enhanced_wide: 11 }
8
+ Y_BITS_LONG = { legacy: 7, enhanced_narrow: 10, enhanced_wide: 10 }
10
9
 
11
- def self.first_in_bit_sequence(bit_seq)
10
+ def self.first_in_bit_sequence(bit_seq, format=:legacy)
11
+ bit_sequence_length = 1 + X_BITS_LONG[format] + Y_BITS_LONG[format]
12
12
  bit_seq.first_bit_digit == '1' &&
13
- bit_seq.length >= BIT_SEQUENCE_LENGTH
13
+ bit_seq.length >= bit_sequence_length
14
14
  end
15
15
 
16
- def self.parse_from_bit_sequence!(bit_seq)
16
+ def self.parse_from_bit_sequence!(bit_seq, format=:legacy)
17
17
  bit_seq.shift 1
18
- x_bit_seq = bit_seq.shift( X_BITS_LONG )
19
- y_bit_seq = bit_seq.shift( Y_BITS_LONG )
18
+ x_bit_seq = bit_seq.shift( X_BITS_LONG[format] )
19
+ y_bit_seq = bit_seq.shift( Y_BITS_LONG[format] )
20
20
  new( x_bit_seq.as_unsigned, y_bit_seq.as_unsigned )
21
21
  end
22
22
 
@@ -32,10 +32,10 @@ module ClientForPoslynx
32
32
  return x == other.x && y == other.y
33
33
  end
34
34
 
35
- def to_bit_sequence
35
+ def to_bit_sequence(format=:legacy)
36
36
  bit_seq = ClientForPoslynx::BitSequence / '1'
37
- bit_seq << ClientForPoslynx::BitSequence.from_unsigned( x, X_BITS_LONG )
38
- bit_seq << ClientForPoslynx::BitSequence.from_unsigned( y, Y_BITS_LONG )
37
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( x, X_BITS_LONG[format] )
38
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( y, Y_BITS_LONG[format] )
39
39
  end
40
40
  end
41
41
 
@@ -38,9 +38,10 @@ module ClientForPoslynx
38
38
  private
39
39
 
40
40
  def apply_document_characteristics
41
- svg_element['width' ] = '67.17mm'
42
- svg_element['height' ] = '13.434mm'
43
- svg_element['viewBox' ] = '0 0 640 128'
41
+ metrics = signature_image.metrics || SignatureImage::Metrics.new([6717, 1343], [640, 128])
42
+ svg_element['width' ] = '%fmm' % ( metrics.size_in_dum[0] * 0.01 )
43
+ svg_element['height' ] = '%fmm' % ( metrics.size_in_dum[1] * 0.01 )
44
+ svg_element['viewBox' ] = '0 0 %d %d' % metrics.resolution
44
45
  svg_element['preserveAspectRatio'] = 'none'
45
46
  end
46
47
 
@@ -1,5 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
+ require_relative 'signature_image/metrics'
3
4
  require_relative 'signature_image/move'
4
5
  require_relative 'signature_image/draw'
5
6
  require_relative 'signature_image/to_svg_converter'
@@ -11,30 +12,59 @@ module ClientForPoslynx
11
12
  class << self
12
13
 
13
14
  def deserialize(serialized_data)
14
- packed_binary = uudecode( serialized_data )
15
+ packed_binary = base64_decode( serialized_data )
15
16
  bit_seq = ClientForPoslynx::BitSequence.from_packed_bits( packed_binary )
16
17
 
17
18
  sig_image = new
19
+
20
+ if bit_seq.first_bit_digit == '0' then
21
+ leader_seq = bit_seq.shift( 16 )
22
+ header_content_length_bytes = leader_seq.as_unsigned
23
+ if header_content_length_bytes < 8 then
24
+ # TODO: Should have an exception class for this error.
25
+ raise "Expected header content length to be >= 8, but got #{header_content_length}."
26
+ end
27
+ if header_content_length_bytes > 255 then
28
+ # TODO: Should have an exception class for this error.
29
+ byte_0_bit_digits = leader_seq.to_bit_digits[0..7]
30
+ raise "Expected first byte of leader to be zero, but got #{byte_0_bit_digits}."
31
+ end
32
+ header_content_seq = bit_seq.shift( header_content_length_bytes * 8 )
33
+ x_scaled_res = header_content_seq.shift(16).as_unsigned
34
+ y_scaled_res = header_content_seq.shift(16).as_unsigned
35
+ x_phys_size = header_content_seq.shift(16).as_unsigned
36
+ y_phys_size = header_content_seq.shift(16).as_unsigned
37
+ sig_image.metrics = SignatureImage::Metrics.new(
38
+ [ x_scaled_res, y_scaled_res ],
39
+ [ x_phys_size, y_phys_size ],
40
+ )
41
+ end
42
+
43
+ format = sig_image.serialization_format
44
+
18
45
  step_types = [ SignatureImage::Move, SignatureImage::Draw ]
19
46
  while true
20
- step_type = step_types.detect{ |st| st.first_in_bit_sequence( bit_seq ) }
47
+ step_type = step_types.detect{ |st| st.first_in_bit_sequence( bit_seq, format ) }
21
48
  break if step_type.nil?
22
- step = step_type.parse_from_bit_sequence!( bit_seq )
49
+ step = step_type.parse_from_bit_sequence!( bit_seq, format )
23
50
  sig_image << step
24
51
  end
25
52
 
26
53
  sig_image
27
54
  end
28
55
 
29
- def uudecode(uuencoded)
30
- uuencoded.unpack('u').first
56
+ def base64_decode(encoded)
57
+ encoded.unpack('m').first
31
58
  end
32
59
 
33
60
  end
34
61
 
62
+ attr_accessor :metrics
63
+
35
64
  def ==(other)
36
65
  return false unless self.class === other
37
- sequence == other.sequence
66
+ metrics == other.metrics &&
67
+ sequence == other.sequence
38
68
  end
39
69
 
40
70
  def move(*args)
@@ -66,15 +96,34 @@ module ClientForPoslynx
66
96
  groups
67
97
  end
68
98
 
69
- def serialize_legacy
99
+ def serialize
70
100
  unless Move === sequence.first
71
101
  raise 'Must have an initial move-type step in order to serialize'
72
102
  end
73
103
  bit_seq = ClientForPoslynx::BitSequence.new_empty
104
+ case serialization_format
105
+ when :enhanced_narrow, :enhanced_wide
106
+ bit_seq << ClientForPoslynx::BitSequence / '00000000'
107
+ bit_seq << ClientForPoslynx::BitSequence / '00001000'
108
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( metrics.resolution[0], 16 )
109
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( metrics.resolution[1], 16 )
110
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( metrics.size_in_dum[0], 16 )
111
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( metrics.size_in_dum[1], 16 )
112
+ end
74
113
  sequence.each do |step|
75
- bit_seq << step.to_bit_sequence
114
+ bit_seq << step.to_bit_sequence( serialization_format )
115
+ end
116
+ bit_seq.base64_encode
117
+ end
118
+
119
+ def serialization_format
120
+ if metrics.nil?
121
+ :legacy
122
+ else
123
+ metrics.resolution[0] > 1024 ?
124
+ :enhanced_wide :
125
+ :enhanced_narrow
76
126
  end
77
- bit_seq.uuencode
78
127
  end
79
128
 
80
129
  protected
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module ClientForPoslynx
4
- VERSION = '0.2.5'
4
+ VERSION = '0.2.6'
5
5
  end
@@ -3,6 +3,8 @@
3
3
  require "client_for_poslynx/version"
4
4
  require "client_for_poslynx/data"
5
5
  require "client_for_poslynx/message_handling"
6
+ require "client_for_poslynx/bit_sequence"
7
+ require "client_for_poslynx/signature_image"
6
8
 
7
9
  module ClientForPoslynx
8
10
 
@@ -69,7 +69,7 @@ module ClientForPoslynx
69
69
  expect( taken ).to eq( expected_taken )
70
70
  end
71
71
 
72
- it "can have another bit sequence unshifted onto its fron" do
72
+ it "can have another bit sequence unshifted onto its front" do
73
73
  bit_target = klass / '000111000'
74
74
  bit_target.unshift klass / '111000111'
75
75
 
@@ -89,17 +89,17 @@ module ClientForPoslynx
89
89
  raise_exception( klass::TooManyBitsLong, /\b64\b/ )
90
90
  end
91
91
 
92
- it "can be (big-endian) interpreted as the sign-bit and magnitude of an integer" do
93
- expect( ( klass / '000' ).as_sign_and_magnitude ).to eq( 0 )
94
- expect( ( klass / '0111000011110000' ).as_sign_and_magnitude ).to eq( 0x70F0 )
95
- expect( ( klass / '1111000011110000' ).as_sign_and_magnitude ).to eq( - 0x70F0 )
96
- expect( ( klass / ('1' + '11110000' * 8) ).as_sign_and_magnitude ).to eq( - 0xF0F0F0F0F0F0F0F0 )
92
+ it "can be (big-endian) interpreted as a 2s-complement signed integer" do
93
+ expect( ( klass / '000' ).as_signed ).to eq( 0 )
94
+ expect( ( klass / '0111000011110000' ).as_signed ).to eq( 0x70F0 )
95
+ expect( ( klass / '1000111100010000' ).as_signed ).to eq( - 0x70F0 )
96
+ expect( ( klass / ('11110000' * 8) ).as_signed ).to eq( - 0x0F0F0F0F0F0F0F10 )
97
97
  end
98
98
 
99
- it "fails to be interpreted as sign-bit and magnitude if longer than 65 bits" do
100
- bit_seq = klass / ( '0' + '11110000' * 8 + '1' )
101
- expect{ bit_seq.as_sign_and_magnitude }.
102
- to raise_exception( klass::TooManyBitsLong, /\b65\b/ )
99
+ it "fails to be interpreted as 2s complement if longer than 64 bits" do
100
+ bit_seq = klass / ( '0' + '11110000' * 8 )
101
+ expect{ bit_seq.as_signed }.
102
+ to raise_exception( klass::TooManyBitsLong, /\b64\b/ )
103
103
  end
104
104
 
105
105
  it "can be constructed as the big-endian representation of an unsigned integer" do
@@ -108,13 +108,6 @@ module ClientForPoslynx
108
108
  expect( klass.from_unsigned( (2 ** 64) - 1, 64 ) ).to eq( klass / ( '1' * 64 ) )
109
109
  end
110
110
 
111
- it "can be constructed as the sign-bit and big-endian magnitude representation of an integer" do
112
- expect( klass.from_sign_and_magnitude_of( 0, 10 ) ).to eq( klass / ( '0' * 10 ) )
113
- expect( klass.from_sign_and_magnitude_of( 0x23, 8 ) ).to eq( klass / ( '00100011' ) )
114
- expect( klass.from_sign_and_magnitude_of( - 0x23, 8 ) ).to eq( klass / ( '10100011' ) )
115
- expect( klass.from_sign_and_magnitude_of( -(2 ** 64) + 1, 65 ) ).to eq( klass / ( '1' * 65 ) )
116
- end
117
-
118
111
  it "fails to be constructed as a longer than 64-bit representation of an unsigned number" do
119
112
  expect{ klass.from_unsigned(0x23, 65) }.
120
113
  to raise_exception( klass::TooManyBitsLong, /\b64\b/ )
@@ -130,16 +123,38 @@ module ClientForPoslynx
130
123
  to raise_exception( klass::NumberOutOfBounds )
131
124
  end
132
125
 
133
- it "can be build by unpacking bits from a uuencoded string" do
134
- actual = klass.from_uuencoded( "&,C(R,C(R\n" )
126
+ it "can be constructed as the big-endian, 2's complement representation of a signed integer" do
127
+ expect( klass.from_signed( 0, 9 ) ).to eq( klass / ( '0' * 9 ) )
128
+ expect( klass.from_signed( -0x23, 7 ) ).to eq( klass / ( '1011101' ) )
129
+ expect( klass.from_signed( -1, 4 ) ).to eq( klass / ( '1111' ) )
130
+ expect( klass.from_signed( -(2 ** 63), 64 ) ).to eq( klass / ( '1' + '0' * 63 ) )
131
+ end
132
+
133
+ it "fails to be constructed as a longer than 64-bit representation of a signed number" do
134
+ expect{ klass.from_signed(0x23, 65) }.
135
+ to raise_exception( klass::TooManyBitsLong, /\b64\b/ )
136
+ end
137
+
138
+ it "fails to be constructed as a an under-sized representation a negative signed number" do
139
+ expect{ klass.from_signed(-0x23, 6) }.
140
+ to raise_exception( klass::NumberOutOfBounds )
141
+ end
142
+
143
+ it "fails to be constructed as an under-sized representation of a positive signed number" do
144
+ expect{ klass.from_signed(0x23, 6) }.
145
+ to raise_exception( klass::NumberOutOfBounds )
146
+ end
147
+
148
+ it "can be build by unpacking bits from a base-64 encoded string" do
149
+ actual = klass.from_base64( "MjIyMjIy" )
135
150
  expected = klass ^ "\x32" * 6
136
151
 
137
152
  expect( actual ).to eq( expected )
138
153
  end
139
154
 
140
- it "can be packed to a uuencoded string" do
155
+ it "can be packed to a base-64 string" do
141
156
  bit_seq = klass ^ "\x32" * 6
142
- expect( bit_seq.uuencode ).to eq( "&,C(R,C(R\n" )
157
+ expect( bit_seq.base64_encode ).to eq( "MjIyMjIy" )
143
158
  end
144
159
 
145
160
  end
@@ -6,6 +6,17 @@ module ClientForPoslynx
6
6
 
7
7
  it_behaves_like "a data object"
8
8
 
9
+ # At least one revision of the POSLynx returns signature
10
+ # data surrounded by &lt[CDATA[...]]&gt which is obviously
11
+ # supposed to be CDATA encoding in XML. Since the angle
12
+ # brackets are esacped, however, the element contains the
13
+ # CDATA-encoded XML as its text value instead of the
14
+ # intended content.
15
+ it "repairs malformed signature image CDATA" do
16
+ subject.signature = "<![CDATA[ABC123]]>"
17
+ expect( subject.signature ).to eq( 'ABC123' )
18
+ end
19
+
9
20
  it "Serializes to a PLResponse XML document for a CCSALE response" do
10
21
  expected_xml = "<PLResponse><Command>CCSALE</Command></PLResponse>\n"
11
22
 
@@ -8,43 +8,82 @@ module ClientForPoslynx
8
8
 
9
9
  def build_example_image
10
10
  described_class.new.tap { |si|
11
- si.move 10, 120
12
- si.draw 0, -30
13
- si.draw 0, -30
14
- si.draw 0, -30
15
- si.draw 0, -20
16
- si.draw 30, 30
17
- si.draw -25, 25
18
- si.draw 0, -2
11
+ si.move 20, 120
12
+ si.draw 10, -30
13
+ si.draw 10, -30
14
+ si.draw 10, -30
15
+ si.draw 10, 30
16
+ si.draw 10, 30
17
+ si.draw 10, 30
18
+ si.move 35, 75
19
+ si.draw 30, -1
19
20
  }
20
21
  end
21
22
 
22
- let( :bit_sequence ) {
23
- BitSequence.from_bit_digits( bit_digit_sequence )
23
+ let( :legacy_bit_sequence ) {
24
+ BitSequence.from_bit_digits( legacy_bit_digit_sequence )
24
25
  }
25
26
 
26
- let( :bit_digit_sequence ) {
27
- # TODO: Currently assuming that deltas for move are
28
- # expressed as sign bit and magnitude, where sign bit
29
- # of 1 means negative. Documentation is unclear about
30
- # this though except to say that there is a sign bit,
31
- # and that values can range from -31 to 31.
32
- # Once I can connect to the virtual POSLynx again, I
33
- # need to do a test, and find out whether this
34
- # assumption is true or not.
35
-
36
- '1' + '0000001010' + '1111000' + # move 10, 120
37
- '0' + '000000' + '111110' + # draw 0, -30
38
- '0' + '000000' + '111110' + # draw 0, -30
39
- '0' + '000000' + '111110' + # draw 0, -30
40
- '0' + '000000' + '110100' + # draw 0, -20
41
- '0' + '011110' + '011110' + # draw 30, 30
42
- '0' + '111001' + '011001' + # draw -25, 25
43
- '0' + '000000' + '100010' + # draw 0, -2
44
- '000' # remaining bits in last byte
27
+ let( :legacy_bit_digit_sequence ) {
28
+ '1' + '0000010100' + '1111000' + # move 20, 120 (10-bit, 7-bit)
29
+ '0' + '001010' + '100010' + # draw 10, -30
30
+ '0' + '001010' + '100010' + # draw 10, -30
31
+ '0' + '001010' + '100010' + # draw 10, -30
32
+ '0' + '001010' + '011110' + # draw 10, 30
33
+ '0' + '001010' + '011110' + # draw 10, 30
34
+ '0' + '001010' + '011110' + # draw 10, 30
35
+ '1' + '0000100011' + '1001011' + # move 35, 75 64+8+2+1
36
+ '0' + '011110' + '111111' + # draw 30, -1
37
+ '0' # remaining bit in last byte
45
38
  }
46
39
 
47
- it "is unequal to another instance with a different sequence of steps" do
40
+ let( :enhanced_narrow_bit_sequence ) {
41
+ BitSequence.from_bit_digits( enhanced_narrow_bit_digit_sequence )
42
+ }
43
+
44
+ let( :enhanced_narrow_bit_digit_sequence ) {
45
+ '00000000' + # 1st byte all 0s: Indicates enhanced format.
46
+ '00001000' + # Header content length: 8 bytes
47
+ '0000010000000000' + # X scaled resolution: 1024 (maximum to be treated as narrow)
48
+ '0000000110000000' + # Y scaled resolution: 384
49
+ '0001111101000000' + # X physical size in 0.01 mm units: 8000
50
+ '0000101110111000' + # Y physical size in 0.01 mm units: 3000
51
+ '1' + '0000010100' + '0001111000' + # move 20, 120 (10-bit, 10-bit)
52
+ '0' + '001010' + '100010' + # draw 10, -30
53
+ '0' + '001010' + '100010' + # draw 10, -30
54
+ '0' + '001010' + '100010' + # draw 10, -30
55
+ '0' + '001010' + '011110' + # draw 10, 30
56
+ '0' + '001010' + '011110' + # draw 10, 30
57
+ '0' + '001010' + '011110' + # draw 10, 30
58
+ '1' + '0000100011' + '0001001011' + # move 35, 75
59
+ '0' + '011110' + '111111' + # draw 30, -1
60
+ '000' # remaining bits in last byte
61
+ }
62
+
63
+ let( :enhanced_wide_bit_sequence ) {
64
+ BitSequence.from_bit_digits( enhanced_wide_bit_digit_sequence )
65
+ }
66
+
67
+ let( :enhanced_wide_bit_digit_sequence ) {
68
+ '00000000' + # 1st byte all 0s: Indicates enhanced format.
69
+ '00001000' + # Header content length: 8 bytes
70
+ '0000010000000001' + # X scaled resolution: 1025 (minimum to be treated as wide)
71
+ '0000000110011010' + # Y scaled resolution: 410
72
+ '0001111101000000' + # X physical size in 0.01 mm units: 8000
73
+ '0000110010000000' + # Y physical size in 0.01 mm units: 3200
74
+ '1' + '00000010100' + '0001111000' + # move 20, 120 (11-bit, 10-bit)
75
+ '0' + '001010' + '100010' + # draw 10, -30
76
+ '0' + '001010' + '100010' + # draw 10, -30
77
+ '0' + '001010' + '100010' + # draw 10, -30
78
+ '0' + '001010' + '011110' + # draw 10, 30
79
+ '0' + '001010' + '011110' + # draw 10, 30
80
+ '0' + '001010' + '011110' + # draw 10, 30
81
+ '1' + '00000100011' + '0001001011' + # move 35, 75
82
+ '0' + '011110' + '111111' + # draw 30, -1
83
+ '0' # remaining bit in last byte
84
+ }
85
+
86
+ it "is unequal to another instance with a different sequence of steps and no metrics" do
48
87
  subject = build_example_image
49
88
 
50
89
  other_sig = described_class.new
@@ -54,35 +93,129 @@ module ClientForPoslynx
54
93
  expect( subject ).not_to eq( other_sig )
55
94
  end
56
95
 
57
- it "is equal to another instance with the same sequence of steps" do
96
+ it "is unequal to another instance with a different sequence of steps and same metrics" do
97
+ subject = build_example_image
98
+ subject.metrics = SignatureImage::Metrics.new([1000, 250], [6080, 1520])
99
+
100
+ other_sig = described_class.new
101
+ other_sig.metrics = SignatureImage::Metrics.new([1000, 250], [6080, 1520])
102
+ other_sig.move 10, 120
103
+ other_sig.draw 0, -29
104
+
105
+ expect( subject ).not_to eq( other_sig )
106
+ end
107
+
108
+ it "is unequal to another instance with the same sequence of steps and different metrics" do
109
+ subject = build_example_image
110
+ subject.metrics = SignatureImage::Metrics.new([1000, 250], [6080, 1520])
111
+
112
+ other_sig = build_example_image
113
+ other_sig.metrics = SignatureImage::Metrics.new([999, 250], [6080, 1520])
114
+
115
+ expect( subject ).not_to eq( other_sig )
116
+ end
117
+
118
+ it "is equal to another instance with the same sequence of steps and no metrics" do
58
119
  subject = build_example_image
59
120
  other_sig = build_example_image
60
121
 
61
122
  expect( subject ).to eq( other_sig )
62
123
  end
63
124
 
64
- context "serializing" do
65
- subject{ build_example_image }
125
+ it "is equal to another instance with the same sequence of steps and same metrics" do
126
+ subject = build_example_image
127
+ subject.metrics = SignatureImage::Metrics.new([1000, 250], [6080, 1520])
66
128
 
67
- it "serializes data in legacy format" do
68
- actual_serialized = subject.serialize_legacy
69
- actual_bit_sequence = BitSequence.from_uuencoded( actual_serialized )
129
+ other_sig = build_example_image
130
+ other_sig.metrics = SignatureImage::Metrics.new([1000, 250], [6080, 1520])
131
+
132
+ expect( subject ).to eq( other_sig )
133
+ end
134
+
135
+ context "legacy serialized format" do
136
+ context "serializing" do
137
+ subject{ build_example_image }
138
+
139
+ it "serializes data" do
140
+ actual_serialized = subject.serialize
141
+ actual_bit_sequence = BitSequence.from_base64( actual_serialized )
70
142
 
71
- expect( actual_bit_sequence ).to eq( bit_sequence )
143
+ expect( actual_bit_sequence ).to eq( legacy_bit_sequence )
144
+ end
145
+ end
146
+
147
+ context "deserializing" do
148
+ subject{ described_class.new(serialized_data) }
149
+ let( :serialized_data ) {
150
+ legacy_bit_sequence.base64_encode
151
+ }
152
+
153
+ it "deserializes data" do
154
+ actual = described_class.deserialize( serialized_data )
155
+ expected = build_example_image
156
+
157
+ expect( actual ).to eq( expected )
158
+ end
72
159
  end
73
160
  end
74
161
 
75
- context "deserializing" do
76
- subject{ described_class.new(serialized_data) }
77
- let( :serialized_data ) {
78
- bit_sequence.uuencode
79
- }
162
+ context "enhanced format with narrow resolution" do
163
+ context "serializing" do
164
+ subject{ build_example_image.tap{ |img|
165
+ img.metrics = SignatureImage::Metrics.new([1024,384], [8000,3000])
166
+ } }
167
+
168
+ it "serializes data" do
169
+ actual_serialized = subject.serialize
170
+ actual_bit_sequence = BitSequence.from_base64( actual_serialized )
171
+
172
+ expect( actual_bit_sequence ).to eq( enhanced_narrow_bit_sequence )
173
+ end
174
+ end
175
+
176
+ context "deserializing" do
177
+ subject{ described_class.new(serialized_data) }
178
+ let( :serialized_data ) {
179
+ enhanced_narrow_bit_sequence.base64_encode
180
+ }
181
+
182
+ it "deserializes data" do
183
+ actual = described_class.deserialize( serialized_data )
184
+ expected = build_example_image
185
+ expected.metrics = SignatureImage::Metrics.new([1024,384], [8000,3000])
186
+
187
+ expect( actual ).to eq( expected )
188
+ end
189
+ end
190
+ end
191
+
192
+ context "enhanced format with wide resolution" do
193
+ context "serializing" do
194
+ subject{ build_example_image.tap{ |img|
195
+ img.metrics = SignatureImage::Metrics.new([1025,410], [8000,3200])
196
+ } }
197
+
198
+ it "serializes data" do
199
+ actual_serialized = subject.serialize
200
+ actual_bit_sequence = BitSequence.from_base64( actual_serialized )
201
+
202
+ expect( actual_bit_sequence ).to eq( enhanced_wide_bit_sequence )
203
+ end
204
+ end
205
+
206
+ context "deserializing" do
207
+ subject{ described_class.new(serialized_data) }
208
+ let( :serialized_data ) {
209
+ enhanced_wide_bit_sequence.base64_encode
210
+ }
80
211
 
81
- it "deserializes data from legacy format" do
82
- actual = described_class.deserialize( serialized_data )
83
- expected = build_example_image
212
+ it "deserializes data" do
213
+ actual = described_class.deserialize( serialized_data )
214
+ expected = build_example_image
215
+ expected.metrics = SignatureImage::Metrics.new([1025,410], [8000,3200])
84
216
 
85
- expect( actual ).to eq( expected )
217
+ expect( actual ).to eq( expected )
218
+ end
86
219
  end
87
220
  end
88
221
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: client_for_poslynx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Jorgensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-07 00:00:00.000000000 Z
11
+ date: 2014-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -86,7 +86,6 @@ files:
86
86
  - bin/fake_pos_terminal
87
87
  - bin/poslynx_client_console
88
88
  - client_for_poslynx.gemspec
89
- - foo.svg
90
89
  - lib/client_for_poslynx.rb
91
90
  - lib/client_for_poslynx/bit_sequence.rb
92
91
  - lib/client_for_poslynx/data.rb
@@ -135,6 +134,7 @@ files:
135
134
  - lib/client_for_poslynx/message_handling/xml_extractor.rb
136
135
  - lib/client_for_poslynx/signature_image.rb
137
136
  - lib/client_for_poslynx/signature_image/draw.rb
137
+ - lib/client_for_poslynx/signature_image/metrics.rb
138
138
  - lib/client_for_poslynx/signature_image/move.rb
139
139
  - lib/client_for_poslynx/signature_image/to_svg_converter.rb
140
140
  - lib/client_for_poslynx/version.rb
data/foo.svg DELETED
@@ -1,20 +0,0 @@
1
- <svg version="1.1"
2
- width="67.17mm" height="13.434mm"
3
- viewBox="0 0 640 128"
4
- preserveAspectRatio='none'
5
- xmlns="http://www.w3.org/2000/svg"
6
- >
7
-
8
- <path d='M 40,10 l 0,30 0,30 -5,5 -3,1 -3,-1 -5,-5
9
- M 24,10 l 30,0
10
- M 65,40 l 10,0 6,4 4,6 0,15 -4,6 -6,4 -10,0 -6,-4 -4,-6 0,-15, 4,-6
11
- M 93,40 l 14,28 5,10
12
- M 130,40 l -15,30 -15,30 M 160,50 l 0,0'
13
- stroke='black'
14
- stroke-width='1mm'
15
- stroke-linejoin='round'
16
- stroke-linecap='square'
17
- fill='none'
18
- />
19
-
20
- </svg>