# encoding: binary # frozen_string_literal: true module Miscreant module Internals # Internal utility functions module Util # :nodoc: module_function # :nodoc: Lookup table for the number of trailing zeroes in a byte CTZ_TABLE = [ 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 ].freeze # Perform a constant time-ish comparison of two bytestrings def ct_equal(a, b) return false unless a.bytesize == b.bytesize l = a.unpack("C*") r = 0 i = -1 b.each_byte { |v| r |= v ^ l[i += 1] } r.zero? end # Perform a constant time(-ish) branch operation def ct_select(subject, result_if_one, result_if_zero) (~(subject - 1) & result_if_one) | ((subject - 1) & result_if_zero) end # Count the number of zeros in a given 8-bit integer # # @param value [Integer] an integer in the 8-bit unsigned range (0-255) def ctz(value) CTZ_TABLE.fetch(value) end # Pad value with a 0x80 value and zeroes up to the given length def pad(message, length) padded_length = message.length + length - (message.length % length) message += "\x80" message.ljust(padded_length, "\0") end # Ensure a string is a valid bytestring (potentially of a given length) def validate_bytestring(name, string, length: nil) raise TypeError, "expected String for #{name}, got #{string.class}" unless string.is_a?(String) raise ArgumentError, "#{name} must be Encoding::BINARY" unless string.encoding == Encoding::BINARY case length when Array raise ArgumentError, "#{name} must be #{length.join(' or ')} bytes long" unless length.include?(string.bytesize) when Integer raise ArgumentError, "#{name} must be #{length}-bytes long" unless string.bytesize == length else raise TypeError, "bad length parameter: #{length.inspect} (#{length.class}" unless length.nil? end string end end end end