lib/net/ssh/buffer.rb in net-ssh-5.0.0.beta1 vs lib/net/ssh/buffer.rb in net-ssh-5.0.0.beta2
- old
+ new
@@ -2,258 +2,259 @@
require 'net/ssh/transport/openssl'
require 'net/ssh/authentication/certificate'
require 'net/ssh/authentication/ed25519_loader'
-module Net; module SSH
+module Net
+ module SSH
- # Net::SSH::Buffer is a flexible class for building and parsing binary
- # data packets. It provides a stream-like interface for sequentially
- # reading data items from the buffer, as well as a useful helper method
- # for building binary packets given a signature.
- #
- # Writing to a buffer always appends to the end, regardless of where the
- # read cursor is. Reading, on the other hand, always begins at the first
- # byte of the buffer and increments the read cursor, with subsequent reads
- # taking up where the last left off.
- #
- # As a consumer of the Net::SSH library, you will rarely come into contact
- # with these buffer objects directly, but it could happen. Also, if you
- # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
- # class can be quite handy.
- class Buffer
- # This is a convenience method for creating and populating a new buffer
- # from a single command. The arguments must be even in length, with the
- # first of each pair of arguments being a symbol naming the type of the
- # data that follows. If the type is :raw, the value is written directly
- # to the hash.
+ # Net::SSH::Buffer is a flexible class for building and parsing binary
+ # data packets. It provides a stream-like interface for sequentially
+ # reading data items from the buffer, as well as a useful helper method
+ # for building binary packets given a signature.
#
- # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
- # #-> "\1\0\0\0\5hello\1\2\3\4"
+ # Writing to a buffer always appends to the end, regardless of where the
+ # read cursor is. Reading, on the other hand, always begins at the first
+ # byte of the buffer and increments the read cursor, with subsequent reads
+ # taking up where the last left off.
#
- # The supported data types are:
- #
- # * :raw => write the next value verbatim (#write)
- # * :int64 => write an 8-byte integer (#write_int64)
- # * :long => write a 4-byte integer (#write_long)
- # * :byte => write a single byte (#write_byte)
- # * :string => write a 4-byte length followed by character data (#write_string)
- # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
- # * :bool => write a single byte, interpreted as a boolean (#write_bool)
- # * :bignum => write an SSH-encoded bignum (#write_bignum)
- # * :key => write an SSH-encoded key value (#write_key)
- #
- # Any of these, except for :raw, accepts an Array argument, to make it
- # easier to write multiple values of the same type in a briefer manner.
- def self.from(*args)
- raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
+ # As a consumer of the Net::SSH library, you will rarely come into contact
+ # with these buffer objects directly, but it could happen. Also, if you
+ # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
+ # class can be quite handy.
+ class Buffer
+ # This is a convenience method for creating and populating a new buffer
+ # from a single command. The arguments must be even in length, with the
+ # first of each pair of arguments being a symbol naming the type of the
+ # data that follows. If the type is :raw, the value is written directly
+ # to the hash.
+ #
+ # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
+ # #-> "\1\0\0\0\5hello\1\2\3\4"
+ #
+ # The supported data types are:
+ #
+ # * :raw => write the next value verbatim (#write)
+ # * :int64 => write an 8-byte integer (#write_int64)
+ # * :long => write a 4-byte integer (#write_long)
+ # * :byte => write a single byte (#write_byte)
+ # * :string => write a 4-byte length followed by character data (#write_string)
+ # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
+ # * :bool => write a single byte, interpreted as a boolean (#write_bool)
+ # * :bignum => write an SSH-encoded bignum (#write_bignum)
+ # * :key => write an SSH-encoded key value (#write_key)
+ #
+ # Any of these, except for :raw, accepts an Array argument, to make it
+ # easier to write multiple values of the same type in a briefer manner.
+ def self.from(*args)
+ raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
- buffer = new
- 0.step(args.length-1, 2) do |index|
- type = args[index]
- value = args[index+1]
- if type == :raw
- buffer.append(value.to_s)
- elsif Array === value
- buffer.send("write_#{type}", *value)
- else
- buffer.send("write_#{type}", value)
+ buffer = new
+ 0.step(args.length - 1, 2) do |index|
+ type = args[index]
+ value = args[index + 1]
+ if type == :raw
+ buffer.append(value.to_s)
+ elsif Array === value
+ buffer.send("write_#{type}", *value)
+ else
+ buffer.send("write_#{type}", value)
+ end
end
+
+ buffer
end
- buffer
- end
+ # exposes the raw content of the buffer
+ attr_reader :content
- # exposes the raw content of the buffer
- attr_reader :content
+ # the current position of the pointer in the buffer
+ attr_accessor :position
- # the current position of the pointer in the buffer
- attr_accessor :position
+ # Creates a new buffer, initialized to the given content. The position
+ # is initialized to the beginning of the buffer.
+ def initialize(content="")
+ @content = content.to_s
+ @position = 0
+ end
- # Creates a new buffer, initialized to the given content. The position
- # is initialized to the beginning of the buffer.
- def initialize(content="")
- @content = content.to_s
- @position = 0
- end
+ # Returns the length of the buffer's content.
+ def length
+ @content.length
+ end
- # Returns the length of the buffer's content.
- def length
- @content.length
- end
+ # Returns the number of bytes available to be read (e.g., how many bytes
+ # remain between the current position and the end of the buffer).
+ def available
+ length - position
+ end
- # Returns the number of bytes available to be read (e.g., how many bytes
- # remain between the current position and the end of the buffer).
- def available
- length - position
- end
+ # Returns a copy of the buffer's content.
+ def to_s
+ (@content || "").dup
+ end
- # Returns a copy of the buffer's content.
- def to_s
- (@content || "").dup
- end
+ # Compares the contents of the two buffers, returning +true+ only if they
+ # are identical in size and content.
+ def ==(buffer)
+ to_s == buffer.to_s
+ end
- # Compares the contents of the two buffers, returning +true+ only if they
- # are identical in size and content.
- def ==(buffer)
- to_s == buffer.to_s
- end
+ # Returns +true+ if the buffer contains no data (e.g., it is of zero length).
+ def empty?
+ @content.empty?
+ end
- # Returns +true+ if the buffer contains no data (e.g., it is of zero length).
- def empty?
- @content.empty?
- end
+ # Resets the pointer to the start of the buffer. Subsequent reads will
+ # begin at position 0.
+ def reset!
+ @position = 0
+ end
- # Resets the pointer to the start of the buffer. Subsequent reads will
- # begin at position 0.
- def reset!
- @position = 0
- end
+ # Returns true if the pointer is at the end of the buffer. Subsequent
+ # reads will return nil, in this case.
+ def eof?
+ @position >= length
+ end
- # Returns true if the pointer is at the end of the buffer. Subsequent
- # reads will return nil, in this case.
- def eof?
- @position >= length
- end
+ # Resets the buffer, making it empty. Also, resets the read position to
+ # 0.
+ def clear!
+ @content = ""
+ @position = 0
+ end
- # Resets the buffer, making it empty. Also, resets the read position to
- # 0.
- def clear!
- @content = ""
- @position = 0
- end
+ # Consumes n bytes from the buffer, where n is the current position
+ # unless otherwise specified. This is useful for removing data from the
+ # buffer that has previously been read, when you are expecting more data
+ # to be appended. It helps to keep the size of buffers down when they
+ # would otherwise tend to grow without bound.
+ #
+ # Returns the buffer object itself.
+ def consume!(n=position)
+ if n >= length
+ # optimize for a fairly common case
+ clear!
+ elsif n > 0
+ @content = @content[n..-1] || ""
+ @position -= n
+ @position = 0 if @position < 0
+ end
+ self
+ end
- # Consumes n bytes from the buffer, where n is the current position
- # unless otherwise specified. This is useful for removing data from the
- # buffer that has previously been read, when you are expecting more data
- # to be appended. It helps to keep the size of buffers down when they
- # would otherwise tend to grow without bound.
- #
- # Returns the buffer object itself.
- def consume!(n=position)
- if n >= length
- # optimize for a fairly common case
- clear!
- elsif n > 0
- @content = @content[n..-1] || ""
- @position -= n
- @position = 0 if @position < 0
+ # Appends the given text to the end of the buffer. Does not alter the
+ # read position. Returns the buffer object itself.
+ def append(text)
+ @content << text
+ self
end
- self
- end
- # Appends the given text to the end of the buffer. Does not alter the
- # read position. Returns the buffer object itself.
- def append(text)
- @content << text
- self
- end
+ # Returns all text from the current pointer to the end of the buffer as
+ # a new Net::SSH::Buffer object.
+ def remainder_as_buffer
+ Buffer.new(@content[@position..-1])
+ end
- # Returns all text from the current pointer to the end of the buffer as
- # a new Net::SSH::Buffer object.
- def remainder_as_buffer
- Buffer.new(@content[@position..-1])
- end
+ # Reads all data up to and including the given pattern, which may be a
+ # String, Fixnum, or Regexp and is interpreted exactly as String#index
+ # does. Returns nil if nothing matches. Increments the position to point
+ # immediately after the pattern, if it does match. Returns all data up to
+ # and including the text that matched the pattern.
+ def read_to(pattern)
+ index = @content.index(pattern, @position) or return nil
+ length = case pattern
+ when String then pattern.length
+ when Integer then 1
+ when Regexp then $&.length
+ end
+ index && read(index + length)
+ end
- # Reads all data up to and including the given pattern, which may be a
- # String, Fixnum, or Regexp and is interpreted exactly as String#index
- # does. Returns nil if nothing matches. Increments the position to point
- # immediately after the pattern, if it does match. Returns all data up to
- # and including the text that matched the pattern.
- def read_to(pattern)
- index = @content.index(pattern, @position) or return nil
- length = case pattern
- when String then pattern.length
- when Integer then 1
- when Regexp then $&.length
+ # Reads and returns the next +count+ bytes from the buffer, starting from
+ # the read position. If +count+ is +nil+, this will return all remaining
+ # text in the buffer. This method will increment the pointer.
+ def read(count=nil)
+ count ||= length
+ count = length - @position if @position + count > length
+ @position += count
+ @content[@position - count, count]
end
- index && read(index+length)
- end
- # Reads and returns the next +count+ bytes from the buffer, starting from
- # the read position. If +count+ is +nil+, this will return all remaining
- # text in the buffer. This method will increment the pointer.
- def read(count=nil)
- count ||= length
- count = length - @position if @position + count > length
- @position += count
- @content[@position-count, count]
- end
+ # Reads (as #read) and returns the given number of bytes from the buffer,
+ # and then consumes (as #consume!) all data up to the new read position.
+ def read!(count=nil)
+ data = read(count)
+ consume!
+ data
+ end
- # Reads (as #read) and returns the given number of bytes from the buffer,
- # and then consumes (as #consume!) all data up to the new read position.
- def read!(count=nil)
- data = read(count)
- consume!
- data
- end
+ # Calls block(self) until the buffer is empty, and returns all results.
+ def read_all(&block)
+ Enumerator.new { |e| e << yield(self) until eof? }.to_a
+ end
- # Calls block(self) until the buffer is empty, and returns all results.
- def read_all(&block)
- Enumerator.new { |e| e << yield(self) until eof? }.to_a
- end
+ # Return the next 8 bytes as a 64-bit integer (in network byte order).
+ # Returns nil if there are less than 8 bytes remaining to be read in the
+ # buffer.
+ def read_int64
+ hi = read_long or return nil
+ lo = read_long or return nil
+ return (hi << 32) + lo
+ end
- # Return the next 8 bytes as a 64-bit integer (in network byte order).
- # Returns nil if there are less than 8 bytes remaining to be read in the
- # buffer.
- def read_int64
- hi = read_long or return nil
- lo = read_long or return nil
- return (hi << 32) + lo
- end
+ # Return the next four bytes as a long integer (in network byte order).
+ # Returns nil if there are less than 4 bytes remaining to be read in the
+ # buffer.
+ def read_long
+ b = read(4) or return nil
+ b.unpack("N").first
+ end
- # Return the next four bytes as a long integer (in network byte order).
- # Returns nil if there are less than 4 bytes remaining to be read in the
- # buffer.
- def read_long
- b = read(4) or return nil
- b.unpack("N").first
- end
+ # Read and return the next byte in the buffer. Returns nil if called at
+ # the end of the buffer.
+ def read_byte
+ b = read(1) or return nil
+ b.getbyte(0)
+ end
- # Read and return the next byte in the buffer. Returns nil if called at
- # the end of the buffer.
- def read_byte
- b = read(1) or return nil
- b.getbyte(0)
- end
+ # Read and return an SSH2-encoded string. The string starts with a long
+ # integer that describes the number of bytes remaining in the string.
+ # Returns nil if there are not enough bytes to satisfy the request.
+ def read_string
+ length = read_long or return nil
+ read(length)
+ end
- # Read and return an SSH2-encoded string. The string starts with a long
- # integer that describes the number of bytes remaining in the string.
- # Returns nil if there are not enough bytes to satisfy the request.
- def read_string
- length = read_long or return nil
- read(length)
- end
+ # Read a single byte and convert it into a boolean, using 'C' rules
+ # (i.e., zero is false, non-zero is true).
+ def read_bool
+ b = read_byte or return nil
+ b != 0
+ end
- # Read a single byte and convert it into a boolean, using 'C' rules
- # (i.e., zero is false, non-zero is true).
- def read_bool
- b = read_byte or return nil
- b != 0
- end
+ # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
+ # essentially just a string, which is reinterpreted to be a bignum in
+ # binary format.
+ def read_bignum
+ data = read_string
+ return unless data
+ OpenSSL::BN.new(data, 2)
+ end
- # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
- # essentially just a string, which is reinterpreted to be a bignum in
- # binary format.
- def read_bignum
- data = read_string
- return unless data
- OpenSSL::BN.new(data, 2)
- end
+ # Read a key from the buffer. The key will start with a string
+ # describing its type. The remainder of the key is defined by the
+ # type that was read.
+ def read_key
+ type = read_string
+ return (type ? read_keyblob(type) : nil)
+ end
- # Read a key from the buffer. The key will start with a string
- # describing its type. The remainder of the key is defined by the
- # type that was read.
- def read_key
- type = read_string
- return (type ? read_keyblob(type) : nil)
- end
-
- # Read a keyblob of the given type from the buffer, and return it as
- # a key. Only RSA, DSA, and ECDSA keys are supported.
- def read_keyblob(type)
- case type
+ # Read a keyblob of the given type from the buffer, and return it as
+ # a key. Only RSA, DSA, and ECDSA keys are supported.
+ def read_keyblob(type)
+ case type
when /^(.*)-cert-v01@openssh\.com$/
key = Net::SSH::Authentication::Certificate.read_certblob(self, $1)
when /^ssh-dss$/
key = OpenSSL::PKey::DSA.new
if key.respond_to?(:set_pqg)
@@ -291,106 +292,107 @@
raise NotImplementedError, "unsupported key type `#{type}'"
end
end
else
raise NotImplementedError, "unsupported key type `#{type}'"
+ end
+
+ return key
end
- return key
- end
+ # Reads the next string from the buffer, and returns a new Buffer
+ # object that wraps it.
+ def read_buffer
+ Buffer.new(read_string)
+ end
- # Reads the next string from the buffer, and returns a new Buffer
- # object that wraps it.
- def read_buffer
- Buffer.new(read_string)
- end
+ # Writes the given data literally into the string. Does not alter the
+ # read position. Returns the buffer object.
+ def write(*data)
+ data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
+ self
+ end
- # Writes the given data literally into the string. Does not alter the
- # read position. Returns the buffer object.
- def write(*data)
- data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
- self
- end
+ # Optimized version of write where the caller gives up ownership of string
+ # to the method. This way we can mutate the string.
+ def write_moved(string)
+ @content << string.force_encoding('BINARY')
+ self
+ end
- # Optimized version of write where the caller gives up ownership of string
- # to the method. This way we can mutate the string.
- def write_moved(string)
- @content << string.force_encoding('BINARY')
- self
- end
+ # Writes each argument to the buffer as a network-byte-order-encoded
+ # 64-bit integer (8 bytes). Does not alter the read position. Returns the
+ # buffer object.
+ def write_int64(*n)
+ n.each do |i|
+ hi = (i >> 32) & 0xFFFFFFFF
+ lo = i & 0xFFFFFFFF
+ @content << [hi, lo].pack("N2")
+ end
+ self
+ end
- # Writes each argument to the buffer as a network-byte-order-encoded
- # 64-bit integer (8 bytes). Does not alter the read position. Returns the
- # buffer object.
- def write_int64(*n)
- n.each do |i|
- hi = (i >> 32) & 0xFFFFFFFF
- lo = i & 0xFFFFFFFF
- @content << [hi, lo].pack("N2")
+ # Writes each argument to the buffer as a network-byte-order-encoded
+ # long (4-byte) integer. Does not alter the read position. Returns the
+ # buffer object.
+ def write_long(*n)
+ @content << n.pack("N*")
+ self
end
- self
- end
- # Writes each argument to the buffer as a network-byte-order-encoded
- # long (4-byte) integer. Does not alter the read position. Returns the
- # buffer object.
- def write_long(*n)
- @content << n.pack("N*")
- self
- end
+ # Writes each argument to the buffer as a byte. Does not alter the read
+ # position. Returns the buffer object.
+ def write_byte(*n)
+ n.each { |b| @content << b.chr }
+ self
+ end
- # Writes each argument to the buffer as a byte. Does not alter the read
- # position. Returns the buffer object.
- def write_byte(*n)
- n.each { |b| @content << b.chr }
- self
- end
+ # Writes each argument to the buffer as an SSH2-encoded string. Each
+ # string is prefixed by its length, encoded as a 4-byte long integer.
+ # Does not alter the read position. Returns the buffer object.
+ def write_string(*text)
+ text.each do |string|
+ s = string.to_s
+ write_long(s.bytesize)
+ write(s)
+ end
+ self
+ end
- # Writes each argument to the buffer as an SSH2-encoded string. Each
- # string is prefixed by its length, encoded as a 4-byte long integer.
- # Does not alter the read position. Returns the buffer object.
- def write_string(*text)
- text.each do |string|
- s = string.to_s
- write_long(s.bytesize)
- write(s)
+ # Writes each argument to the buffer as an SSH2-encoded string. Each
+ # string is prefixed by its length, encoded as a 4-byte long integer.
+ # Does not alter the read position. Returns the buffer object.
+ # Might alter arguments see write_moved
+ def write_mstring(*text)
+ text.each do |string|
+ s = string.to_s
+ write_long(s.bytesize)
+ write_moved(s)
+ end
+ self
end
- self
- end
- # Writes each argument to the buffer as an SSH2-encoded string. Each
- # string is prefixed by its length, encoded as a 4-byte long integer.
- # Does not alter the read position. Returns the buffer object.
- # Might alter arguments see write_moved
- def write_mstring(*text)
- text.each do |string|
- s = string.to_s
- write_long(s.bytesize)
- write_moved(s)
+ # Writes each argument to the buffer as a (C-style) boolean, with 1
+ # meaning true, and 0 meaning false. Does not alter the read position.
+ # Returns the buffer object.
+ def write_bool(*b)
+ b.each { |v| @content << (v ? "\1" : "\0") }
+ self
end
- self
- end
- # Writes each argument to the buffer as a (C-style) boolean, with 1
- # meaning true, and 0 meaning false. Does not alter the read position.
- # Returns the buffer object.
- def write_bool(*b)
- b.each { |v| @content << (v ? "\1" : "\0") }
- self
- end
+ # Writes each argument to the buffer as a bignum (SSH2-style). No
+ # checking is done to ensure that the arguments are, in fact, bignums.
+ # Does not alter the read position. Returns the buffer object.
+ def write_bignum(*n)
+ @content << n.map { |b| b.to_ssh }.join
+ self
+ end
- # Writes each argument to the buffer as a bignum (SSH2-style). No
- # checking is done to ensure that the arguments are, in fact, bignums.
- # Does not alter the read position. Returns the buffer object.
- def write_bignum(*n)
- @content << n.map { |b| b.to_ssh }.join
- self
+ # Writes the given arguments to the buffer as SSH2-encoded keys. Does not
+ # alter the read position. Returns the buffer object.
+ def write_key(*key)
+ key.each { |k| append(k.to_blob) }
+ self
+ end
end
-
- # Writes the given arguments to the buffer as SSH2-encoded keys. Does not
- # alter the read position. Returns the buffer object.
- def write_key(*key)
- key.each { |k| append(k.to_blob) }
- self
- end
end
-end; end;
+end;