1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
# frozen_string_literal: true
desc "Generate Huffman precompiled table in huffman_statemachine.rb"
task :generate_huffman_table do
HuffmanTable::Node.generate_state_table
end
require_relative "../lib/http/2/header/huffman"
module HuffmanTable
BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
EOS = 256
class Node
attr_accessor :next, :emit, :final, :depth, :transitions, :id
@@id = 0 # rubocop:disable Style/ClassVars
def initialize(depth)
@next = [nil, nil]
@id = @@id
@@id += 1 # rubocop:disable Style/ClassVars
@final = false
@depth = depth
end
def add(code, len, chr)
self.final = true if chr == EOS && @depth <= 7
if len.zero?
@emit = chr
else
bit = code.nobits?((1 << (len - 1))) ? 0 : 1
node = @next[bit] ||= Node.new(@depth + 1)
node.add(code, len - 1, chr)
end
end
class Transition
attr_accessor :emit, :node
def initialize(emit, node)
@emit = emit
@node = node
end
end
def self.generate_tree
@root = new(0)
HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
code, len = c
@root.add(code, len, chr)
end
puts "#{@@id} nodes"
@root
end
def self.generate_machine
generate_tree
togo = Set[@root]
@states = Set[@root]
until togo.empty?
node = togo.first
togo.delete(node)
next if node.transitions
node.transitions = [1 << BITS_AT_ONCE]
(1 << BITS_AT_ONCE).times do |input|
n = node
emit = "".b
(BITS_AT_ONCE - 1).downto(0) do |i|
bit = input.nobits?((1 << i)) ? 0 : 1
n = n.next[bit]
next unless n.emit
if n.emit == EOS
emit = EOS # cause error on decoding
else
emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
end
n = @root
end
node.transitions[input] = Transition.new(emit, n)
togo << n
@states << n
end
end
puts "#{@states.size} states"
@root
end
def self.generate_state_table
generate_machine
state_id = {}
id_state = {}
state_id[@root] = 0
id_state[0] = @root
max_final = 0
id = 1
(@states - [@root]).sort_by { |s| s.final ? 0 : 1 }.each do |s|
state_id[s] = id
id_state[id] = s
max_final = id if s.final
id += 1
end
File.open(File.expand_path("../lib/http/2/header/huffman_statemachine.rb", File.dirname(__FILE__)), "w") do |f|
f.print <<~HEADER
# Machine generated Huffman decoder state machine.
# DO NOT EDIT THIS FILE.
# The following task generates this file.
# rake generate_huffman_table
module HTTP2
module Header
class Huffman
# :nodoc:
MAX_FINAL_STATE = #{max_final}
MACHINE = [
HEADER
id.times do |i|
n = id_state[i]
f.print " ["
string = Array.new((1 << 4)) do |t|
transition = n.transitions.fetch(t)
emit = transition.emit
unless emit == EOS
bytes = emit.bytes
raise ArgumentError if bytes.size > 1
emit = bytes.first
end
"[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
end.join(", ")
f.print(string)
f.print "],\n"
end
f.print <<~TAILER
].each { |arr| arr.each { |subarr| subarr.freeze }.freeze }.freeze
end
end
end
TAILER
end
end
class << self
attr_reader :root
end
# Test decoder
def self.decode(input)
emit = ""
n = root
nibbles = input.unpack("C*").flat_map { |b| [((b & 0xf0) >> 4), b & 0xf] }
until nibbles.empty?
nb = nibbles.shift
t = n.transitions[nb]
emit << t.emit
n = t.node
end
puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}" unless n.final && nibbles.all?(0xf)
emit
end
end
end
|