Created
January 9, 2015 16:05
-
-
Save bradland/50fc4cb66c2fe2a0dac7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ByteBuffer | |
attr_reader :bytes | |
# A ByteBuffer accepts encoded string buffers and unpacks them to an | |
# internal array of bytes stored using fixnum so that binary operations can | |
# be easily performed. | |
def initialize(buffer, mode) | |
case mode | |
when 'a' # arbitrary string; converts to ascii char code (this is faster than String#ord) | |
@bytes = buffer.unpack('C*') | |
when 'B' # MSB-first binary string (a string of 1s and 0s) | |
@bytes = [buffer].pack('B*').unpack('C*') | |
when 'C' # array of Fixnum values | |
@bytes = buffer | |
when 'H' # Hex encoded string | |
@bytes = [buffer].pack('H*').unpack('C*') | |
when 'm' # Base64 encoded string | |
@bytes = buffer.unpack('m0').first.unpack('C*') | |
else | |
raise ArgumentError, "Invalid input mode #{mode}" | |
end | |
end | |
# Returns the XOR sum of self and ByteBuffer argument | |
def ^(byte_buffer) | |
# A basic XOR cipher performs an XOR action byte by byte on binary chunks. | |
# In the case of two ByteBuffers, the first operand would be the data, and | |
# the second the key. If our data is longer than our key, we simply repeat | |
# the cipher bytes. If our key is longer than the data, then we'll only | |
# use as many bytes as we need. | |
key_length = byte_buffer.size | |
key_bytes = byte_buffer.bytes | |
bytes_out = [] | |
# This returns an array of Fixnum values | |
@bytes.each_with_index { |byte, i| bytes_out << (byte ^ key_bytes[i % key_length]) } | |
# Which we'll return as a ByteBuffer | |
ByteBuffer.new(bytes_out, 'C') | |
end | |
# Returns (slowly) the Hamming distance between self and ByteBuffer argument | |
def distance(byte_buffer) | |
(self ^ byte_buffer).to_binary.count("1") | |
end | |
# Returns true of string is ASCII printable without garbage | |
def is_printable? | |
@buffer =~ /[^[:print:]]/ | |
end | |
# Returns the size of the byte buffer | |
def size | |
@size ||= @bytes.size | |
end | |
# Returns bytes encoded in base64 string | |
def to_base64 | |
@to_base64 ||= [@bytes.pack('C*')].pack('m0') | |
end | |
# Returns bytes encoded in a binary string | |
def to_binary | |
@to_binary ||= @bytes.pack("C*").unpack("B*").first | |
end | |
# Returns bytes encoded in a hex string | |
def to_hex | |
@to_hex ||= @bytes.pack('C*').unpack('H*').first | |
end | |
# Returns a string with ASCII non-printable chars as [NAME] identifiers | |
def to_clear_string | |
return false if @bytes.max > ASCII_TABLE.length | |
@to_string ||= @bytes.map { |dec| ASCII_TABLE[dec] }.join | |
end | |
# Returns a string (might contain junk!) | |
def to_string | |
@to_string ||= @bytes.pack('C*').to_s | |
end | |
end | |
class ByteBufferError < StandardError | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment