lib/pwnlib/tubes/serialtube.rb
# encoding: ASCII-8BIT
# frozen_string_literal: true
require 'rubyserial'
require 'pwnlib/tubes/tube'
module Pwnlib
module Tubes
# @!macro [new] raise_eof
# @raise [Pwnlib::Errors::EndOfTubeError]
# If the request is not satisfied when all data is received.
# Serial Connections
class SerialTube < Tube
# Instantiate a {Pwnlib::Tubes::SerialTube} object.
#
# @param [String] port
# A device name for rubyserial to open, e.g. /dev/ttypUSB0
# @param [Integer] baudrate
# Baud rate.
# @param [Boolean] convert_newlines
# If +true+, convert any +context.newline+s to +"\\r\\n"+ before
# sending to remote. Has no effect on bytes received.
# @param [Integer] bytesize
# Serial character byte size. The '8' in '8N1'.
# @param [Symbol] parity
# Serial character parity. The 'N' in '8N1'.
def initialize(port = nil, baudrate: 115_200,
convert_newlines: true,
bytesize: 8, parity: :none)
super()
# go hunting for a port
port ||= Dir.glob('/dev/tty.usbserial*').first
port ||= '/dev/ttyUSB0'
@convert_newlines = convert_newlines
@conn = Serial.new(port, baudrate, bytesize, parity)
@serial_timer = Timer.new
end
# Closes the active connection
def close
@conn.close if @conn && !@conn.closed?
@conn = nil
end
# Implementation of the methods required for tube
private
# Gets bytes over the serial connection until some bytes are received, or
# +@timeout+ has passed. It is an error for it to return no data in less
# than +@timeout+ seconds. It is ok for it to return some data in less
# time.
#
# @param [Integer] numbytes
# An upper limit on the number of bytes to get.
#
# @return [String]
# A string containing read bytes.
#
# @!macro raise_eof
def recv_raw(numbytes)
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
@serial_timer.countdown do
data = ''
begin
while @serial_timer.active?
data += @conn.read(numbytes - data.length)
break unless data.empty?
sleep 0.1
end
# XXX(JonathanBeverley): should we reverse @convert_newlines here?
return data
rescue RubySerial::Error
close
raise ::Pwnlib::Errors::EndOfTubeError
end
end
end
# Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs.
#
# @param [String] data
# A string of the bytes to send.
#
# @return [Integer]
# The number of bytes successfully written.
#
# @!macro raise_eof
def send_raw(data)
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
data.gsub!(context.newline, "\r\n") if @convert_newlines
begin
@conn.write(data)
rescue RubySerial::Error
close
raise ::Pwnlib::Errors::EndOfTubeError
end
end
# Sets the +timeout+ to use for subsequent +recv_raw+ calls.
#
# @param [Float] timeout
def timeout_raw=(timeout)
@serial_timer.timeout = timeout
end
end
end
end