client_for_poslynx 0.2.5 → 0.2.6

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,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>