cddl 0.10.3 → 0.12.9

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