cddl 0.10.3 → 0.12.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e60b48882767d7e76926473b44c79369891e4203326d4e31f5693a3819f391d
4
- data.tar.gz: 6ed2f45e7fa8468557e73c96a4dbfdd142d65fdebeeb362efeebec8007b7948f
3
+ metadata.gz: 4c98730c57c25c57066cc2c35d0912b14df39e1d3f3ff3815950217435513d30
4
+ data.tar.gz: 1da65aafd52644087e1fa33d5e158ae4c85d58b11658fc39bf64a465f8cf3b43
5
5
  SHA512:
6
- metadata.gz: 2124a4fc046c3dc03d007ca949e4f9191497b359fe19fafe307ed2227c4b5654d909815575504e452add310dfc8487bfc13db94794d4b6c06a7af0bb2701e55f
7
- data.tar.gz: 8a223332d81282eb9ad651dad887579ceb7b44a01d05ccff6458e271ea999a3c5fad5f81c87a6a2ccf927307b3bb9c4e06075361b0f8fc8b02005926a3d9a5b7
6
+ metadata.gz: 54a58e321f94a6c8e3c61dd0216f1e6afd214972dbd71cc4326a5ec294714b4f0ce5b1695ab951bd5526e1cd9a1550c5102333395e3fdda4cac3a87d0c4d075b
7
+ data.tar.gz: ad35e2794090e2d403ffa218fd3c9414052c00092b0a1b34ab11d9a7563be3ac06b0e3b5923982f1f46e9b0a2b13e578c7c3da0b12b4cc4ee6eaff0bb5581f5b
data/bin/cddl CHANGED
@@ -7,10 +7,13 @@ require 'json'
7
7
 
8
8
  Encoding.default_external = "UTF-8" # wake up, smell the coffee
9
9
 
10
+ CDDL_VERSION = Gem.loaded_specs["cddl"].version rescue "unknown-version"
11
+
10
12
  EX_USAGE = 64
11
13
  EX_DATAERR = 65
12
14
 
13
15
  def usage
16
+ warn "cddl tool version #{CDDL_VERSION}"
14
17
  warn "Usage:"
15
18
  warn "#$0 spec.cddl generate [n]"
16
19
  warn "#$0 spec.cddl json-generate [n]"
@@ -19,6 +22,13 @@ def usage
19
22
  exit EX_USAGE
20
23
  end
21
24
 
25
+ def utfify(s)
26
+ s.force_encoding(Encoding::UTF_8).scrub { |c|
27
+ warn "*** replaced invalid UTF-8 byte sequence #{c.inspect} by U+FFFD REPLACEMENT CHARACTER"
28
+ 0xFFFD.chr(Encoding::UTF_8)
29
+ }
30
+ end
31
+
22
32
  def read_arg(arg, remember_fn = true)
23
33
  if arg == "-"
24
34
  $fn = "(stdin)" if remember_fn
@@ -31,7 +41,7 @@ def read_arg(arg, remember_fn = true)
31
41
  end
32
42
 
33
43
  def parser
34
- @parser ||= CDDL::Parser.new(read_arg(ARGV[0], false))
44
+ @parser ||= CDDL::Parser.new(utfify(read_arg(ARGV[0], false)))
35
45
  end
36
46
 
37
47
  def my_pp(v)
@@ -96,7 +106,12 @@ begin
96
106
  instance = if $sequence
97
107
  CBOR.decode("\x9f".b << instance.b << "\xff".b)
98
108
  else
99
- CBOR.decode(instance.b) rescue JSON.load(instance)
109
+ begin
110
+ CBOR.decode(instance.b)
111
+ rescue => e
112
+ warn e.inspect if verbose
113
+ JSON.load(utfify(instance))
114
+ end
100
115
  end
101
116
  instance = instance.cbor_clone if $annotate && ENV["EXPERIMENTAL_ANNOTATE"]
102
117
  warn "#{fn}:" if verbose
@@ -104,6 +119,7 @@ begin
104
119
  # my_pp ann
105
120
  instance.cbor_add_annotations_from(ann) rescue nil
106
121
  my_diag(instance) if $annotate
122
+ warn "ann: #{!!ann}" if verbose
107
123
  unless ann
108
124
  retcode = 1
109
125
  end
data/cddl.gemspec CHANGED
@@ -1,24 +1,26 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'cddl'
3
- s.version = '0.10.3'
3
+ s.version = '0.12.9'
4
4
  s.summary = "CDDL generator and validator."
5
5
  s.description = %{A parser, generator, and validator for CDDL}
6
6
  s.add_dependency('cbor-diag')
7
- s.add_dependency('abnc')
7
+ s.add_dependency('abnc', '~> 0.1.1')
8
8
  s.add_dependency('json_pure')
9
9
  s.add_dependency('abnftt')
10
- s.add_dependency('regexp-examples') # , '1.1.0')
10
+ s.add_dependency('regexp-examples', '~> 1.5.1') # , '1.1.0')
11
11
  s.add_dependency('colorize')
12
12
  s.add_dependency('base32', '~> 0.3')
13
+ s.add_dependency('base45_lite', '~> 1.0')
14
+ s.add_dependency('scanf', '~> 1.0')
13
15
  s.files = `git ls-files`.split("\n").grep(/^[a-z]/)
14
16
  s.files = Dir['lib/**/*.rb'] + %w(cddl.gemspec) + Dir['data/**/*.abnf'] + Dir['data/**/*.cddl'] + Dir['test-data/**/*.cddl'] + Dir['test/**/*.rb']
15
17
  s.require_path = 'lib'
16
18
  s.executables = ['cddl']
17
19
  s.default_executable = 'cddl'
18
- s.required_ruby_version = '>= 2.0'
20
+ s.required_ruby_version = '>= 2.3'
19
21
  s.author = "Carsten Bormann"
20
22
  s.email = "cabo@tzi.org"
21
- s.homepage = "http://github.com/cabo/cddl"
23
+ s.homepage = "https://www.rfc-editor.org/rfc/rfc8610#appendix-F"
22
24
  s.license = 'MIT'
23
25
  end
24
26
 
data/data/cddl.abnf CHANGED
@@ -13,7 +13,8 @@ genericarg = "<" S type1 S *("," S type1 S ) ">"
13
13
  type = type1 S *("/" S type1 S)
14
14
 
15
15
  type1 = type2 [S (rangeop / annotator) S type2]
16
- / "#" "6" ["." uint] "(" S type S ")" ; note no space!
16
+ / "#" "6" ["." headnumber] "(" S type S ")" ; note no space!
17
+ / "#" "7" ["." headnumber] ; note no space!
17
18
  / "#" DIGIT ["." uint] ; major/ai
18
19
  / "#" ; any
19
20
  / "~" S typename [genericarg]
@@ -22,6 +23,8 @@ type1 = type2 [S (rangeop / annotator) S type2]
22
23
  / "&" S "(" S group S ")"
23
24
  / "&" S groupname [genericarg]
24
25
 
26
+ headnumber = uint / ("<" type ">")
27
+
25
28
  type2 = value
26
29
  / typename [genericarg]
27
30
  / "(" type ")"
@@ -38,7 +41,7 @@ grpent = [occur S] [memberkey S] type optcom
38
41
  / [occur S] groupname [genericarg] optcom ; preempted by above
39
42
  / [occur S] "(" S group S ")" optcom
40
43
 
41
- memberkey = type1 S "=>"
44
+ memberkey = type1 S ["^" S] "=>"
42
45
  / bareword S ":"
43
46
  / value S ":"
44
47
 
@@ -67,27 +70,39 @@ fraction = 1*DIGIT
67
70
  exponent = int
68
71
 
69
72
  text = %x22 *SCHAR %x22
70
- SCHAR = %x20-21 / %x23-7E / SESC
71
- SESC = "\" %x20-7E
73
+ SCHAR = %x20-21 / %x23-5B / %x5D-7E / NONASCII / SESC
74
+
75
+ SESC = "\" ( %x22 / "/" / "\" / ; \" \/ \\
76
+ %x62 / %x66 / %x6E / %x72 / %x74 / ; \b \f \n \r \t
77
+ (%x75 hexchar) ) ; \uXXXX
78
+
79
+ hexchar = "{" (1*"0" [ hexscalar ] / hexscalar) "}" /
80
+ non-surrogate / (high-surrogate "\" %x75 low-surrogate)
81
+ non-surrogate = ((DIGIT / "A"/"B"/"C" / "E"/"F") HEXDIG HEXDIG HEXDIG) /
82
+ ("D" %x30-37 HEXDIG HEXDIG)
83
+ high-surrogate = "D" ("8"/"9"/"A"/"B") HEXDIG HEXDIG
84
+ low-surrogate = "D" ("C"/"D"/"E"/"F") HEXDIG HEXDIG
85
+ hexscalar = "10" HEXDIG HEXDIG HEXDIG HEXDIG / HEXDIG1 HEXDIG HEXDIG HEXDIG HEXDIG
86
+ / non-surrogate / HEXDIG [HEXDIG [HEXDIG]]
72
87
 
73
88
  bytes = [bsqual] %x27 *BCHAR %x27
74
- BCHAR = %x20-26 / %x28-5B / %x5D-7E / SESC / CRLF
75
- bsqual = %x68 ; "h"
76
- / %x62.36.34 ; "b64"
89
+ BCHAR = %x20-26 / %x28-5B / %x5D-7E / NONASCII / SESC / "\'" / CRLF
90
+ bsqual = "h" / "b64"
77
91
 
78
92
  id = EALPHA *(*("-" / ".") (EALPHA / DIGIT))
79
93
  ALPHA = %x41-5A / %x61-7A
80
- EALPHA = %x41-5A / %x61-7A / "@" / "_" / "$"
94
+ EALPHA = ALPHA / "@" / "_" / "$"
81
95
  DIGIT = %x30-39
82
96
  DIGIT1 = %x31-39
83
97
  HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
98
+ HEXDIG1 = DIGIT1 / "A" / "B" / "C" / "D" / "E" / "F"
84
99
  BINDIG = %x30-31
85
100
 
86
101
  S = *WS
87
102
  WS = SP / NL
88
103
  SP = %x20
89
104
  NL = COMMENT / CRLF
90
- COMMENT = ";" *(SP / VCHAR) CRLF
91
- VCHAR = %x21-7E
105
+ COMMENT = ";" *PCHAR CRLF
106
+ PCHAR = %x20-7E / NONASCII
107
+ NONASCII = %xA0-D7FF / %xE000-10FFFD
92
108
  CRLF = %x0A / %x0D.0A
93
-
data/lib/cddl.rb CHANGED
@@ -2,6 +2,7 @@ require 'abnc'
2
2
  require 'pp'
3
3
  require 'pathname'
4
4
  require 'cbor-pure' unless defined?(CBOR::Tagged)
5
+ require 'half'
5
6
  require 'cbor-deterministic'
6
7
  require 'regexp-examples'
7
8
  require 'abnftt'
@@ -9,6 +10,7 @@ require 'colorize'
9
10
  require 'base64'
10
11
  require 'base32'
11
12
  require 'base45_lite'
13
+ require 'scanf'
12
14
 
13
15
  module CDDL
14
16
 
@@ -38,6 +40,7 @@ module CDDL
38
40
  [{}, {}]
39
41
  end
40
42
 
43
+ CDDL_UNUSED_OK = ENV["CDDL_UNUSED_OK"]
41
44
 
42
45
  class BackTrack < Exception; end
43
46
 
@@ -59,7 +62,7 @@ module CDDL
59
62
  if upto - presult < 100
60
63
  part2 = source_text[presult...upto]
61
64
  else
62
- part2 = source_text[presult, 50] + "......." + source_text[upto-50, 50]
65
+ part2 = source_text[presult, 50] + "......." + (source_text[upto-50, 50] || "")
63
66
  end
64
67
  warn "*** Look for syntax problems around the #{
65
68
  "%%%".colorize(background: :light_yellow)} markers:\n#{
@@ -246,7 +249,7 @@ module CDDL
246
249
  r_process("used_in_cddl_prelude", @rules["used_in_cddl_prelude"])
247
250
  @rules.each do |n, r|
248
251
  # r_process(n, r) # debug only loop
249
- warn "*** Unused rule #{n}" unless @stage1[n]
252
+ warn "*** Unused rule #{n}" unless @stage1[n] || CDDL_UNUSED_OK
250
253
  end
251
254
  if result[0] == :grpent
252
255
  warn "Group at top -- first rule must be a type!"
@@ -300,6 +303,12 @@ module CDDL
300
303
  abnfb: Encoding::BINARY
301
304
  }
302
305
 
306
+ def myfrand(exp)
307
+ exprange = 1 << exp
308
+ expoffset= exprange >> 1
309
+ (rand(2)*2-1) * rand() * 2.0**(rand(exprange)-expoffset)
310
+ end
311
+
303
312
  def generate
304
313
  @recursion = 0
305
314
  generate1(rules)
@@ -422,9 +431,17 @@ module CDDL
422
431
  when 3
423
432
  gen_word
424
433
  when 6
425
- CBOR::Tagged.new(where[2], generate1(where[3]))
434
+ tn = Integer === where[2] ? where[2] : generate1(where[2])
435
+ unless Integer === tn && tn >= 0
436
+ fail "Can't generate a tag number out of #{where[2]}"
437
+ end
438
+ CBOR::Tagged.new(tn, generate1(where[3]))
426
439
  when 7
427
- case where[2]
440
+ w2 = where[2]
441
+ if Array === w2
442
+ w2 = generate1(w2)
443
+ end
444
+ case w2
428
445
  when nil
429
446
  Math::PI
430
447
  when 20
@@ -433,10 +450,22 @@ module CDDL
433
450
  true
434
451
  when 22
435
452
  nil
436
- when 23
437
- :undefined
438
- when 25, 26, 27
439
- rand()
453
+ when 0..19, 23, 32..255
454
+ CBOR::Simple.new(w2)
455
+ when 25
456
+ while !(hs = Half.encode_from_single_bytes([myfrand(5)].pack("g")))
457
+ end
458
+ Half.decode(hs)
459
+ when 26
460
+ while (a = [myfrand(8)].pack("g").unpack("g").first).to_cbor.size != 5
461
+ end
462
+ a
463
+ when 27
464
+ while (a = myfrand(11)).to_cbor.size != 9
465
+ end
466
+ a
467
+ else
468
+ fail "Can't generate prim #7.#{where[2].inspect}" # XXX retry array generate
440
469
  end
441
470
  else
442
471
  fail "Can't generate prim #{where[1]}"
@@ -551,8 +580,7 @@ module CDDL
551
580
  warn "HUH gen #{where.inspect} #{try.inspect}" unless try.nil?
552
581
  end
553
582
  end
554
- 32.times do
555
- content = generate1(target)
583
+ try_generate(target) do |content|
556
584
  if validate1(content, where)
557
585
  return content
558
586
  end
@@ -605,22 +633,22 @@ module CDDL
605
633
  content = Integer(generate1(control)).to_s
606
634
  content
607
635
  when :join
608
- content = generate1(control)
609
- unless Array === content &&
610
- content.all? {|x| String === x &&
611
- ((target == [:prim, 2] && x.encoding == Encoding::BINARY) ||
612
- (target == [:prim, 3] && x.encoding != Encoding::BINARY))}
613
- fail "Don't know yet how to generate #{where}"
636
+ try_generate(control) do |content|
637
+ if Array === content &&
638
+ content.all? {|x| String === x} &&
639
+ Set[content.map {|x| x.encoding}].size == 1
640
+ content = content.join
641
+ if validate1(content, target)
642
+ return content
643
+ end
644
+ end
614
645
  end
615
- content = content.join
616
- content
646
+ fail "Don't know yet how to generate #{where}"
617
647
  when :b64u, :"b64u-sloppy", :b64c, :"b64c-sloppy",
618
648
  :b45, :b32, :h32, :hex, :hexlc, :hexuc
619
- content = generate1(control)
620
- unless String === content
621
- fail "Don't know yet how to generate #{where}"
622
- end
623
- content = case conop
649
+ try_generate(control) do |content|
650
+ if String === content
651
+ content = case conop
624
652
  when :b64u, :"b64u-sloppy"
625
653
  Base64.urlsafe_encode64(content, padding: false)
626
654
  when :b64c, :"b64c-sloppy"
@@ -639,10 +667,32 @@ module CDDL
639
667
  content.unpack("H*")[0]
640
668
  else fail "Cannot happen"
641
669
  end
642
- content
670
+ if validate1(content, target)
671
+ return content
672
+ end
673
+ end
674
+ end
675
+ fail "Not smart enough to generate #{where}"
676
+ when :printf
677
+ try_generate(control) do |content|
678
+ if Array === content && content.size >= 1
679
+ fmt, *data = content
680
+ if String === fmt
681
+ begin
682
+ content = fmt % data
683
+ if validate1(content, target)
684
+ return content
685
+ end
686
+ rescue ArgumentError => e
687
+ # be verbose about mismatches here
688
+ @last_message << "\n** #{fmt.inspect} ArgumentError #{e}"
689
+ end
690
+ end
691
+ end
692
+ end
693
+ fail "Not smart enough to generate #{where}#{@last_message}"
643
694
  when :within, :and
644
- 32.times do
645
- content = generate1(target)
695
+ try_generate(target) do |content|
646
696
  if validate1(content, control)
647
697
  return content
648
698
  elsif conop == :within
@@ -670,22 +720,41 @@ module CDDL
670
720
  end
671
721
  end
672
722
 
723
+ def try_generate(target)
724
+ 32.times do
725
+ content = generate1(target)
726
+ yield content # should return if success
727
+ end
728
+ end
729
+
673
730
  VALUE_TYPE = {text: String, bytes: String, int: Integer, float: Float}
674
731
  SIMPLE_VALUE = {
675
732
  [:prim, 7, 20] => [true, false, :bool],
676
733
  [:prim, 7, 21] => [true, true, :bool],
677
734
  [:prim, 7, 22] => [true, nil, :nil],
678
- [:prim, 7, 23] => [true, :undefined, :undefined],
679
735
  }
736
+ SIMPLE_VALUE_SIMPLE = Set[23] + (0..19) + (32..255)
680
737
 
681
738
  def extract_value(t) # []
682
739
  if vt = VALUE_TYPE[t[0]]
683
740
  [true, t[1], vt]
684
- elsif v = SIMPLE_VALUE[t]
685
- v
741
+ elsif t[0] == :prim && t[1] == 7
742
+ case t2 = t[2]
743
+ when Integer
744
+ when Array
745
+ a, b, c = extract_value(t2)
746
+ if a && c == Integer
747
+ t2 = b
748
+ end
749
+ end
750
+ if v = SIMPLE_VALUE[[:prim, 7, t2]]
751
+ v
752
+ elsif SIMPLE_VALUE_SIMPLE === t2
753
+ [true, CBOR::Simple.new(t2), :simple]
754
+ end
686
755
  elsif t[0] == :anno
687
756
  _, conop, target, control = t
688
- # warn ["EXV0", conop, target, control].inspect
757
+ warn ["EXV0", conop, target, control].inspect if ENV["CDDL_TRACE"]
689
758
  if conop == :cat || conop == :plus || conop == :det
690
759
  ok1, v1, vt1 = extract_value(target)
691
760
  ok2, v2, vt2 = extract_value(control)
@@ -717,6 +786,19 @@ module CDDL
717
786
  }]
718
787
  end
719
788
 
789
+
790
+ def extract_arg0(t)
791
+ return [false] unless t[0] == :array
792
+ [true,
793
+ (el = t[1]
794
+ return [false] unless el[0..3] == [:member, 1, 1, nil]
795
+ ok, v, vt = extract_value(el[4])
796
+ return [false] unless ok
797
+ [v, vt]
798
+ ),
799
+ *t[2..-1]]
800
+ end
801
+
720
802
  def extract_feature(control, d)
721
803
  ok, v, vt = extract_value(control)
722
804
  if ok
@@ -905,6 +987,19 @@ module CDDL
905
987
  }
906
988
  end
907
989
 
990
+ def validate1a_ignore_encoding(d, where)
991
+ if String === d
992
+ validate1a(d.force_encoding(Encoding::BINARY), where) or (
993
+ text = d.force_encoding(Encoding::UTF_8).scrub {
994
+ fail "can't use bytes as text"
995
+ } rescue :NOT_A_TEXT_STRING
996
+ validate1a(text, where)
997
+ )
998
+ else
999
+ validate1a(d, where)
1000
+ end
1001
+ end
1002
+
908
1003
  def validate1a(d, where)
909
1004
  if ann = validate1(d, where)
910
1005
  here = [d, where]
@@ -917,6 +1012,8 @@ module CDDL
917
1012
  end
918
1013
 
919
1014
  OPERATORS = {lt: :<, le: :<=, gt: :>, ge: :>=, eq: :==, ne: :!=}
1015
+ OPERATORS_MATCH = {eq: true, ne: false}
1016
+ FLOAT_AI_FROM_SIZE = {3 => 25, 5 => 26, 9 => 27}
920
1017
 
921
1018
  def validate1(d, where)
922
1019
  if ENV["CDDL_TRACE"]
@@ -963,7 +1060,7 @@ module CDDL
963
1060
  if conop == :cat || conop == :plus || conop == :det
964
1061
  ok1, v1, vt1 = extract_value(target)
965
1062
  ok2, v2, vt2 = extract_value(control)
966
- # warn ["ANNO0", ok1, v1, vt1, ok2, v2, vt2, d].inspect
1063
+ warn ["ANNO0", ok1, v1, vt1, ok2, v2, vt2, d].inspect if ENV["CDDL_TRACE"]
967
1064
  if ok1 && ok2
968
1065
  v2 = Integer(v2) if vt1 == Integer
969
1066
  if conop == :det
@@ -972,6 +1069,30 @@ module CDDL
972
1069
  end
973
1070
  # warn ["ANNO", ok1, v1, vt1, ok2, v2, vt2, d].inspect
974
1071
  [] if d == v1 + v2 # XXX Focus ArgumentError
1072
+ elsif conop == :cat && String === d
1073
+ warn ["CAT-L", ok1, v1, vt1, ok2, v2, vt2, d].inspect if ENV["CDDL_TRACE"]
1074
+ if ok1 && vt1 == String
1075
+ # d and lhs (v1) need to agree in encoding
1076
+ if d.encoding == v1.encoding
1077
+ if d[0...v1.length] == v1
1078
+ d2 = d[v1.length..-1]
1079
+ warn ["CAT-L1", d2, d2.encoding, control].inspect if ENV["CDDL_TRACE"]
1080
+ validate1a_ignore_encoding(d2, control)
1081
+ end
1082
+ end
1083
+ elsif ok2 && vt2 == String
1084
+ warn ["CAT-R", ok1, v1, vt1, ok2, v2, vt2, d].inspect if ENV["CDDL_TRACE"]
1085
+ if d[-v2.length..-1] == v2
1086
+ d1 = d[0...-v2.length]
1087
+ validate1a(d1, control)
1088
+ end
1089
+ end
1090
+ elsif conop == :plus && Integer === d
1091
+ if ok1 && vt1 == Integer
1092
+ validate1a(d - Integer(v1), control)
1093
+ elsif ok2 && vt2 == Integer
1094
+ validate1a(d - Integer(v2), target)
1095
+ end
975
1096
  end
976
1097
  elsif ann = validate1a(d, target)
977
1098
  case conop
@@ -1022,6 +1143,11 @@ module CDDL
1022
1143
  ok, v, _vt = extract_value(control)
1023
1144
  if ok
1024
1145
  ann if d.send(op, v) rescue nil # XXX Focus ArgumentError
1146
+ else
1147
+ needs_to_match = OPERATORS_MATCH[conop]
1148
+ unless needs_to_match.nil?
1149
+ ann if !!validate1(d, control) == needs_to_match
1150
+ end
1025
1151
  end
1026
1152
  when :regexp
1027
1153
  ann if (
@@ -1081,14 +1207,75 @@ module CDDL
1081
1207
  end
1082
1208
  )
1083
1209
  when :join
1084
- ok, *v = extract_array(control)
1085
- # warn "@@@JJJ@@@ #{ok.inspect} #{v.inspect}"
1086
- if ok
1087
- expected = v.map {|ve|
1088
- fail "Not a string in #{where}" unless String === ve[0]
1089
- ve[0]
1090
- }.join
1091
- ann if d == expected
1210
+ t = control
1211
+ v = if t[0] == :array
1212
+ t[1..-1].map { |el|
1213
+ if el[0..2] == [:member, 1, 1]
1214
+ ok, v, vt = extract_value(el[4])
1215
+ if ok
1216
+ [true, v, vt]
1217
+ else
1218
+ [false, el[4]]
1219
+ end
1220
+ end
1221
+ }
1222
+ end
1223
+ warn "@@@JOIN@@@ #{v.inspect}" if ENV["CDDL_TRACE"]
1224
+ ok = true
1225
+ if v
1226
+ ix = 0
1227
+ rest = d.dup
1228
+ while ix < v.length
1229
+ if left = v[ix]
1230
+ if left[0] # match constant value first
1231
+ fail "Not a string for #{left.inspect} in #{where}" unless String == left[2]
1232
+ want = left[1]
1233
+ have = rest[0...left[1].length]
1234
+ if want == have
1235
+ rest[0...left[1].length] = ''
1236
+ ix += 1
1237
+ next
1238
+ else
1239
+ fail ".join: want #{want.inspect}, have #{have.inspect}"
1240
+ end
1241
+ else
1242
+ ix += 1
1243
+ if ix == v.length # match remaining
1244
+ warn "@@@JOIN ok in #{ok.inspect} rest #{rest.inspect}" if ENV["CDDL_TRACE"]
1245
+ ok &&= validate1(rest, left[1])
1246
+ warn "@@@JOIN ok out #{ok.inspect}" if ENV["CDDL_TRACE"]
1247
+ # more diag
1248
+ rest = ''
1249
+ next
1250
+ else
1251
+ if mid = v[ix]
1252
+ if mid[0] # have constant value to split over
1253
+ fail "Not a string for #{mid} in #{where}" unless String == mid[2]
1254
+ rest1, rest2 = rest.split(mid[1], 2)
1255
+ if rest2
1256
+ warn "@@@JOIN ok in #{ok.inspect} rest1 #{rest1.inspect}" if ENV["CDDL_TRACE"]
1257
+ ok &&= validate1(rest1, left[1])
1258
+ warn "@@@JOIN ok out #{ok.inspect}" if ENV["CDDL_TRACE"]
1259
+ rest = rest2
1260
+ ix += 1
1261
+ next
1262
+ else
1263
+ fail "Can't find #{mid[1].inspect} in #{rest.inspect}"
1264
+ end
1265
+ else
1266
+ fail "Cannot validate consecutive non-constant members of .join in #{where}"
1267
+ end
1268
+ else
1269
+ fail "Cannot handle .join over #{t[ix+1]} in #{where}"
1270
+ end
1271
+ end
1272
+ end
1273
+ else
1274
+ fail "Cannot handle .join over #{t[ix+1]} in #{where}"
1275
+ end
1276
+ end
1277
+ fail "Can't match #{rest.inspect} for .join in #{where}" if rest != ''
1278
+ ann if ok
1092
1279
  else
1093
1280
  fail "Don't know yet how to validate against #{where}"
1094
1281
  end
@@ -1097,7 +1284,7 @@ module CDDL
1097
1284
  String === d && (
1098
1285
  decoded = case conop
1099
1286
  when :b64u
1100
- /=/ !~ d &&
1287
+ /[+\/=]/ !~ d &&
1101
1288
  Base64.urlsafe_decode64(d)
1102
1289
  when :"b64u-sloppy"
1103
1290
  /[-_=]/ !~ d &&
@@ -1126,6 +1313,21 @@ module CDDL
1126
1313
  end
1127
1314
  ) && validate1(decoded.b, control)
1128
1315
  )
1316
+ when :printf
1317
+ ann if String === d && (
1318
+ ok, fmt, *v = extract_arg0(control)
1319
+ if ok && String == fmt[1]
1320
+ fmt = fmt[0]
1321
+ # warn "** ok #{ok.inspect} fmt #{fmt.inspect} v #{v.inspect}"
1322
+ decoded = d.scanf(fmt) # this is a bit too lenient, so let's do:
1323
+ encode_again = fmt % decoded
1324
+ if encode_again != d
1325
+ warn "** fmt #{fmt.inspect} d #{d.inspect} decoded #{decoded.inspect} encode_again #{encode_again.inspect}"
1326
+ else
1327
+ validate1(decoded, [:array, *v])
1328
+ end
1329
+ end
1330
+ )
1129
1331
  when :within
1130
1332
  if validate1(d, control)
1131
1333
  ann
@@ -1161,20 +1363,35 @@ module CDDL
1161
1363
  end
1162
1364
  d = CBOR::Tagged.new(t, d == 0 ? "".b : d.digits(256).reverse!.pack("C*"))
1163
1365
  end
1164
- CBOR::Tagged === d && d.tag == where[2] && validate1a(d.data, where[3])
1366
+ CBOR::Tagged === d && (
1367
+ Integer === where[2] ? d.tag == where[2] : validate1a(d.tag, where[2])
1368
+ ) && validate1a(d.data, where[3])
1165
1369
  when 7
1166
1370
  t, v = extract_value(where)
1167
1371
  if t
1168
1372
  v.eql? d
1169
1373
  else
1170
- case where[2]
1374
+ case w2 = where[2]
1171
1375
  when nil
1172
- # XXX
1173
- fail
1376
+ Float === d || CBOR::Simple === d || [false, true, nil].include?(d)
1377
+ when Array
1378
+ headnum = case d
1379
+ when Float
1380
+ FLOAT_AI_FROM_SIZE[d.to_cbor.size]
1381
+ when false
1382
+ 20
1383
+ when true
1384
+ 21
1385
+ when nil
1386
+ 22
1387
+ when CBOR::Simple
1388
+ d.value
1389
+ end
1390
+ validate1a(headnum, w2)
1174
1391
  when 25, 26, 27
1175
- Float === d
1392
+ Float === d && FLOAT_AI_FROM_SIZE[d.to_cbor.size] == w2
1176
1393
  else
1177
- fail
1394
+ fail [:val7, d, where].inspect
1178
1395
  end
1179
1396
  end
1180
1397
  else
@@ -1224,6 +1441,10 @@ module CDDL
1224
1441
  else
1225
1442
  [:type1]
1226
1443
  end
1444
+ elsif s = ENV["CDDL_INVENT"]
1445
+ s = "_" if s == ""
1446
+ r = [:type1, [:text, "#{s}-#{name}"]]
1447
+ return r
1227
1448
  end
1228
1449
  end
1229
1450
  if r
@@ -1269,6 +1490,18 @@ module CDDL
1269
1490
  end
1270
1491
  end
1271
1492
 
1493
+ STRING_ESCAPES = {
1494
+ "\"" => "\"",
1495
+ "/" => "/",
1496
+ "\\" => "\\",
1497
+ "'" => "'",
1498
+ "b" => "\b",
1499
+ "f" => "\f",
1500
+ "n" => "\n",
1501
+ "r" => "\r",
1502
+ "t" => "\t",
1503
+ }
1504
+
1272
1505
  def value(n)
1273
1506
  # cheat:
1274
1507
  # warn n
@@ -1278,7 +1511,20 @@ module CDDL
1278
1511
  if parts[-1] != ""
1279
1512
  warn "*** Problem decoding byte string #{s.inspect}"
1280
1513
  end
1281
- bsval = parts[0...-1].join("'").gsub(/\\(.)/){$1}
1514
+ bsval = parts[0...-1].join("'").gsub(/\\u([Dd][89AaBb][0-9a-zA-Z]{2})\\u([Dd][CcDdEeFf][0-9a-zA-Z]{2})|\\u([0-9a-zA-Z]{4})|\\u\{([0-9a-zA-Z]+)\}|\r?(\n)|\\([^u])/) {
1515
+ if hex = $3 || $4
1516
+ hex.to_i(16).chr(Encoding::UTF_8)
1517
+ elsif lf = $5
1518
+ lf
1519
+ elsif escaped = $6
1520
+ STRING_ESCAPES[$6] or (
1521
+ fail "Invalid String Escape #{escaped.inspect} in #{s.inspect}"
1522
+ )
1523
+ else
1524
+ ((($1.to_i(16) & 0x3ff) << 10) +
1525
+ ($2.to_i(16) & 0x3ff) + 0x10000).chr(Encoding::UTF_8)
1526
+ end
1527
+ }
1282
1528
  [:bytes,
1283
1529
  case bsqual
1284
1530
  when ""
@@ -1292,7 +1538,17 @@ module CDDL
1292
1538
  end
1293
1539
  ]
1294
1540
  else
1295
- val = eval(n.to_s)
1541
+ if s[-1] == '"'
1542
+ s.gsub!(/\\u([Dd][89AaBb][0-9a-zA-Z]{2})\\u([Dd][CcDdEeFf][0-9a-zA-Z]{2})|\\([^u]|u[0-9a-zA-Z]{4})/) {
1543
+ if $3
1544
+ "\\#$3" # skip this, use eval parser
1545
+ else
1546
+ ((($1.to_i(16) & 0x3ff) << 10) +
1547
+ ($2.to_i(16) & 0x3ff) + 0x10000).chr(Encoding::UTF_8)
1548
+ end
1549
+ }
1550
+ end
1551
+ val = eval(s)
1296
1552
  # warn val
1297
1553
  case val
1298
1554
  when Integer; [:int, val]
@@ -1488,6 +1744,7 @@ module CDDL
1488
1744
  :json, :decimal, :join,
1489
1745
  :b64u, :"b64u-sloppy", :b64c, :"b64c-sloppy",
1490
1746
  :b45, :b32, :h32, :hex, :hexlc, :hexuc,
1747
+ :printf,
1491
1748
  ]
1492
1749
 
1493
1750
  def type1(n, canbegroup = false)
@@ -1520,6 +1777,13 @@ module CDDL
1520
1777
  when /\A#(\d+)/
1521
1778
  maj = $1.to_i
1522
1779
  s = [:prim, maj, *n.children(:uint).map(&:to_s).map(&:to_i)]
1780
+ if tn = n.headnumber
1781
+ if ui = tn.uint
1782
+ s << ui.to_s.to_i
1783
+ elsif tt = tn.type
1784
+ s << type(tt)
1785
+ end
1786
+ end
1523
1787
  if tagged_type = n.type
1524
1788
  s << type(tagged_type)
1525
1789
  end
@@ -0,0 +1,12 @@
1
+ a = text .b64u bytes
2
+
3
+ ;;gp 10
4
+
5
+ ;;vp "AAAA"
6
+ ;;vp "AAA-"
7
+ ;;vp "AAA_"
8
+ ; fail with 0.11.5:
9
+ ;;vp "AAA+"
10
+ ;;vp "AAA/"
11
+ ;;vp "AAA="
12
+ ;;vp "+kd8"
@@ -1,2 +1,4 @@
1
1
  foo = text .regexp myregexp
2
2
  myregexp = "A" .cat "B"
3
+
4
+ ;;gp 10
@@ -0,0 +1,8 @@
1
+ a1 = '' .cat a2
2
+ a2 = text .b64u ain
3
+ ; a = "e30" .b64u ain
4
+ ain = '{}'
5
+
6
+ ;;gp 3
7
+ ;;vp 'e30'
8
+ ;;vp "e30"
@@ -0,0 +1,3 @@
1
+ a = b<13..15>
2
+ b<T> = #7.<32 .plus T>
3
+ ; cddl dotplus1.cddl gp 10
@@ -0,0 +1,3 @@
1
+ a = d<17..19>
2
+ d<T> = #7.<T .plus 32>
3
+ ; cddl dotplus.cddl gp 10
@@ -0,0 +1,3 @@
1
+ a = d<17..19>
2
+ d<T> = 32.5 .plus T
3
+ ; cddl dotplus4.cddl gp 10
@@ -0,0 +1,3 @@
1
+ a = d<17..19>
2
+ d<T> = T .plus 32.5
3
+ ; cddl dotplus4.cddl gp 10
@@ -0,0 +1,14 @@
1
+ terminal-color = &basecolors
2
+ basecolors = (
3
+ black: 0, red: 1, green: 2, yellow: 3,
4
+ blue: 4, magenta: 5, cyan: 6, white: 7,
5
+ reserved: (8..255) .feature "extension"
6
+ )
7
+ ; extended-color = &(
8
+ ; basecolors,
9
+ ; orange: 8, pink: 9, purple: 10, brown: 11,
10
+ ; )
11
+
12
+ ;;gp 10
13
+ ;;vp 42
14
+ ;;vp 433
@@ -0,0 +1,12 @@
1
+ terminal-color = $terminal-color / text .feature "extension"
2
+ $terminal-color /= &basecolors
3
+ basecolors = (
4
+ black: "black", red: "red", green: "green",
5
+ )
6
+
7
+ pastelcolors = ( pink: "pink", mauve: "mauve", peach: "peach", lavender: "lavender" )
8
+ $terminal-color /= &pastelcolors
9
+
10
+ ;;gp 10
11
+ ;;vp "florp"
12
+ ;;vp "lavender"
@@ -0,0 +1,15 @@
1
+ ; JWT-JWS = text .join ([
2
+ ; b64u<jwt-headers>, ".",
3
+ ; b64u<jwt-payload>, ".",
4
+ ; b64u<jwt-signature>])
5
+ ; ; a = jwt-headers
6
+ ; ; a = b64u<"abc"> ; fails
7
+ ; ; a = b64u<'abc'> ; succeeds
8
+ ; b64u<B> = text .b64u B
9
+ jwt-headers = '' .cat jwt-headers1
10
+ jwt-headers1 = text .json jwt-headermap
11
+ jwt-headermap = { * text => any } ; simplified
12
+ jwt-payload = bytes
13
+ jwt-signature = bytes
14
+
15
+ ;;gp 10
@@ -0,0 +1,15 @@
1
+ JWT-JWS = text .join ([
2
+ b64u<jwt-headers>, ".",
3
+ b64u<jwt-payload>, ".",
4
+ b64u<jwt-signature>])
5
+ ; a = jwt-headers
6
+ ; a = b64u<"abc"> ; fails
7
+ ; a = b64u<'abc'> ; succeeds
8
+ b64u<B> = text .b64u B
9
+ jwt-headers = '' .cat jwt-headers1
10
+ jwt-headers1 = text .json jwt-headermap
11
+ jwt-headermap = { * text => any } ; simplified
12
+ jwt-payload = bytes
13
+ jwt-signature = bytes
14
+
15
+ ;;gp 10
@@ -0,0 +1,7 @@
1
+ start = [a, e, b, c, d]
2
+ a = false
3
+ e = undefined
4
+ b = float32
5
+ ; c = #7.32
6
+ c = #6.<12>(3)
7
+ d = #6.13(4)
@@ -0,0 +1,2 @@
1
+ a = text .printf (["%8d %06d", ~arr])
2
+ arr = [uint, uint]
@@ -0,0 +1 @@
1
+ my_label = text .printf (["0x%x: %a", 4711, 81.5])
@@ -0,0 +1,2 @@
1
+ my_alg_sha = hexlabel<4>
2
+ hexlabel<K> = text .printf (["0x%04x", K])
@@ -0,0 +1,2 @@
1
+ my_alg_sha = hexlabel<int>
2
+ hexlabel<K> = text .printf (["0x%04x", K])
@@ -0,0 +1,2 @@
1
+ any_alg = hexlabel<1..20>
2
+ hexlabel<K> = text .printf (["0x%04x", K])
@@ -0,0 +1,16 @@
1
+ cwt = [
2
+ bstr .cbor protected-map,
3
+ unprotected-map,
4
+ bstr .cbor payload-map,
5
+ bstr ; signature
6
+ ]
7
+
8
+ protected-map = {
9
+ &(alg: 1) => int,
10
+ ? &(typ: 16) => text,
11
+ * key => any
12
+ }
13
+
14
+ unprotected-map = ()
15
+ key = "key"
16
+ payload-map = { payload: "map"}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cddl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 0.12.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carsten Bormann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-09 00:00:00.000000000 Z
11
+ date: 2024-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cbor-diag
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: abnc
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.1.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.1.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: json_pure
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: regexp-examples
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 1.5.1
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: 1.5.1
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: colorize
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,34 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: base45_lite
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: scanf
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
111
139
  description: A parser, generator, and validator for CDDL
112
140
  email: cabo@tzi.org
113
141
  executables:
@@ -119,7 +147,6 @@ files:
119
147
  - cddl.gemspec
120
148
  - data/cddl.abnf
121
149
  - data/prelude.cddl
122
- - lib/base45_lite.rb
123
150
  - lib/cbor-pp-play.rb
124
151
  - lib/cbor-pp.rb
125
152
  - lib/cddl.rb
@@ -136,6 +163,7 @@ files:
136
163
  - test-data/b.cddl
137
164
  - test-data/b64c-sloppy.cddl
138
165
  - test-data/b64c.cddl
166
+ - test-data/b64u-strict.cddl
139
167
  - test-data/b64u.cddl
140
168
  - test-data/badaddr.cddl
141
169
  - test-data/base32.cddl
@@ -146,6 +174,7 @@ files:
146
174
  - test-data/bpv7a.cddl
147
175
  - test-data/bpv7b.cddl
148
176
  - test-data/cat-re.cddl
177
+ - test-data/catb64.cddl
149
178
  - test-data/cdni-ct.cddl
150
179
  - test-data/complex-occ.cddl
151
180
  - test-data/coral.cddl
@@ -158,7 +187,13 @@ files:
158
187
  - test-data/decimal.cddl
159
188
  - test-data/decimal2.cddl
160
189
  - test-data/det1.cddl
190
+ - test-data/dotplus1.cddl
191
+ - test-data/dotplus2.cddl
192
+ - test-data/dotplus3.cddl
193
+ - test-data/dotplus4.cddl
161
194
  - test-data/dotsize.cddl
195
+ - test-data/enum.cddl
196
+ - test-data/enum1.cddl
162
197
  - test-data/extractor-demo.cddl
163
198
  - test-data/feat1.cddl
164
199
  - test-data/feature-controller.cddl
@@ -186,8 +221,10 @@ files:
186
221
  - test-data/joini.cddl
187
222
  - test-data/joinx.cddl
188
223
  - test-data/json.cddl
224
+ - test-data/json1.cddl
189
225
  - test-data/json2.cddl
190
226
  - test-data/jsoniodef.cddl
227
+ - test-data/jwt.cddl
191
228
  - test-data/kevin5.cddl
192
229
  - test-data/lint1.cddl
193
230
  - test-data/map-group.cddl
@@ -208,6 +245,12 @@ files:
208
245
  - test-data/oidbat.cddl
209
246
  - test-data/patch1.cddl
210
247
  - test-data/plus.cddl
248
+ - test-data/prim.cddl
249
+ - test-data/printf.cddl
250
+ - test-data/printf0.cddl
251
+ - test-data/printf1.cddl
252
+ - test-data/printf2.cddl
253
+ - test-data/printf3.cddl
211
254
  - test-data/reused_named_group.cddl
212
255
  - test-data/sasl.cddl
213
256
  - test-data/sequence.cddl
@@ -220,6 +263,7 @@ files:
220
263
  - test-data/toerless0.cddl
221
264
  - test-data/toerless1.cddl
222
265
  - test-data/two_anonymous_groups.cddl
266
+ - test-data/valemb.cddl
223
267
  - test-data/wrong1.cddl
224
268
  - test-data/wrong1a.cddl
225
269
  - test-data/wrong2.cddl
@@ -228,7 +272,7 @@ files:
228
272
  - test-data/yangid.cddl
229
273
  - test-data/yaron1.cddl
230
274
  - test/test-cddl.rb
231
- homepage: http://github.com/cabo/cddl
275
+ homepage: https://www.rfc-editor.org/rfc/rfc8610#appendix-F
232
276
  licenses:
233
277
  - MIT
234
278
  metadata: {}
@@ -240,14 +284,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
240
284
  requirements:
241
285
  - - ">="
242
286
  - !ruby/object:Gem::Version
243
- version: '2.0'
287
+ version: '2.3'
244
288
  required_rubygems_version: !ruby/object:Gem::Requirement
245
289
  requirements:
246
290
  - - ">="
247
291
  - !ruby/object:Gem::Version
248
292
  version: '0'
249
293
  requirements: []
250
- rubygems_version: 3.4.6
294
+ rubygems_version: 3.5.14
251
295
  signing_key:
252
296
  specification_version: 4
253
297
  summary: CDDL generator and validator.
data/lib/base45_lite.rb DELETED
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2022 Weihang Jian <https://tonytonyjan.net>
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.
22
-
23
- # An implementation of draft-faltstrom-base45-10, see
24
- # https://datatracker.ietf.org/doc/draft-faltstrom-base45/
25
- module Base45Lite
26
- class Error < ::StandardError; end
27
- class OverflowError < Error; end
28
- class InvalidCharacterError < Error; end
29
- class ForbiddenLengthError < Error; end
30
-
31
- MAX_UINT18 = 2**16 - 1
32
- SQUARED_45 = 45**2
33
- MAPPING = [
34
- *'0'..'9',
35
- *'A'..'Z',
36
- ' ', '$', '%', '*', '+', '-', '.', '/', ':'
37
- ].map!.with_index { |x, i| [i, x] }.to_h.freeze
38
- REVERSE_MAPPING = MAPPING.invert.freeze
39
-
40
- def self.encode(input)
41
- sequence = []
42
- input.unpack('n*').map! do |uint16|
43
- i, c = uint16.divmod(45)
44
- i, d = i.divmod(45)
45
- _, e = i.divmod(45)
46
- sequence.push(c, d, e)
47
- end
48
- if input.bytesize.odd?
49
- i, c = input.getbyte(-1).divmod(45)
50
- _, d = i.divmod(45)
51
- sequence.push(c, d)
52
- end
53
- sequence.map!{ |n| MAPPING[n] }.join
54
- end
55
-
56
- def self.decode(input)
57
- input
58
- .chars.map! { |c| REVERSE_MAPPING[c] || raise(InvalidCharacterError) }
59
- .each_slice(3).map do |slice|
60
- c, d, e = slice
61
- raise ForbiddenLengthError if d.nil?
62
-
63
- sum = c + d * 45
64
- sum += e * SQUARED_45 if e
65
- raise OverflowError if sum > MAX_UINT18
66
-
67
- sum
68
- end
69
- .pack((input.length % 3).zero? ? 'n*' : "n#{input.length / 3}C")
70
- end
71
- end