From ad10e78306cfc4136b5dd762feb11ced2a7bdc6e Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Sun, 20 May 2012 17:36:18 -0700 Subject: [PATCH] Major refactor of packet builder I decided to ditch the Packet class name and go with a Form module that contains a BaseForm which child classes inherit from. I've created a few tests so you can see how the BaseForm class works and how packets can be read and sent over a socket to an X11 server --- lib/X11/form.rb | 208 +++++++++++++++++++++++++++++++++++++ lib/X11/packet.rb | 102 ------------------ lib/X11/packets/display.rb | 84 --------------- test/form_test.rb | 66 ++++++++++++ test/packet_test.rb | 50 --------- 5 files changed, 274 insertions(+), 236 deletions(-) create mode 100644 lib/X11/form.rb delete mode 100644 lib/X11/packet.rb delete mode 100644 lib/X11/packets/display.rb create mode 100644 test/form_test.rb delete mode 100644 test/packet_test.rb diff --git a/lib/X11/form.rb b/lib/X11/form.rb new file mode 100644 index 0000000..5516941 --- /dev/null +++ b/lib/X11/form.rb @@ -0,0 +1,208 @@ +module X11 + module Form + # A form object is an X11 packet definition. We use forms to encode + # and decode X11 packets as we send and receive them over a socket. + # + # We can create a packet definition as follows: + # + # class Point < BaseForm + # field :x, Int8 + # field :y, Int8 + # end + # + # p = Point.new(10,20) + # p.x => 10 + # p.y => 20 + # p.to_packet => "\n\x14" + # + # You can also read from a socket: + # + # Point.from_packet(socket) => # + # + class BaseForm + include X11::Type + + # initialize field accessors + def initialize(*params) + self.class.fields.each do |f| + param = params.shift + instance_variable_set("@#{f.name}", param) + end + end + + def to_packet + # fetch class level instance variable holding defined fields + structs = self.class.instance_variable_get("@structs") + + packet = structs.map do |s| + # fetch value of field set in initialization + value = instance_variable_get("@#{s.name}") + + case s.type + when :field + if value.is_a?(BaseForm) + value.to_packet + else + s.type_klass.pack(value) + end + when :unused + "\x00" * s.size + when :length + s.type_klass.pack(value.size) + when :string + s.type_klass.pack(value) + when :list + value.collect do |obj| + obj.to_packet + end + end + end.join + end + + class << self + def from_packet(socket) + # fetch class level instance variable holding defined fields + + form = new + lengths = {} + + @structs.each do |s| + case s.type + when :field + val = if s.type_klass.superclass == BaseForm + s.type_klass.from_packet(socket) + else + s.type_klass.unpack( socket.read(s.type_klass.size) ) + end + form.instance_variable_set("@#{s.name}", val) + when :unused + socket.read(s.size) + when :length + size = s.type_klass.unpack( socket.read(s.type_klass.size) ) + lengths[s.name] = size + when :string + val = s.type_klass.unpack(socket, lengths[s.name]) + form.instance_variable_set("@#{s.name}", val) + when :list + val = lengths[s.name].times.collect do + s.type_klass.from_packet(socket) + end + form.instance_variable_set("@#{s.name}", val) + end + end + + return form + end + + def field(*args) + name, type_klass, type = args + class_eval { attr_accessor name } + + s = OpenStruct.new + s.name = name + s.type = (type == nil ? :field : type) + s.type_klass = type_klass + + @structs ||= [] + @structs << s + end + + def unused(size) + s = OpenStruct.new + s.size = size + s.type = :unused + + @structs ||= [] + @structs << s + end + + def fields + @structs.dup.delete_if{|s| s.type == :unused or s.type == :length} + end + end + end + + ## + ## X11 Packet Defintions + ## + + class ClientHandshake < BaseForm + field :byte_order, Uint8 + unused 1 + field :protocol_major_version, Uint16 + field :protocol_minor_version, Uint16 + field :auth_proto_name, Uint16, :length + field :auth_proto_data, Uint16, :length + unused 2 + field :auth_proto_name, String8, :string + field :auth_proto_data, String8, :string + end + + class FormatInfo < BaseForm + field :depth, Uint8 + field :bits_per_pixel, Uint8 + field :scanline_pad, Uint8 + unused 5 + end + + class VisualInfo < BaseForm + field :visual_id, VisualID + field :qlass, Uint8 + field :bits_per_rgb_value, Uint8 + field :colormap_entries, Uint16 + field :red_mask, Uint32 + field :green_mask, Uint32 + field :blue_mask, Uint32 + unused 4 + end + + class DepthInfo < BaseForm + field :depth, Uint8 + unused 1 + field :visuals, Uint16, :length + unused 4 + field :visuals, VisualInfo, :list + end + + class ScreenInfo < BaseForm + field :root, Window + field :default_colormap, Colormap + field :white_pixel, Colornum + field :black_pixel, Colornum + field :current_input_masks, Mask + field :width_in_pixels, Uint16 + field :height_in_pixels, Uint16 + field :width_in_millimeters, Uint16 + field :height_in_millimeters, Uint16 + field :min_installed_maps, Uint16 + field :max_installed_maps, Uint16 + field :root_visual, VisualID + field :backing_stores, Uint8 + field :save_unders, Bool + field :root_depth, Uint8 + field :depths, Uint8,:length + field :depths, DepthInfo, :list + end + + class DisplayInfo < BaseForm + field :release_number, Uint32 + field :resource_id_base, Uint32 + field :resource_id_mask, Uint32 + field :motion_buffer_size, Uint32 + field :vendor, Uint16, :length + field :maximum_request_length, Uint16 + field :screens, Uint8, :length + field :formats, Uint8, :length + field :image_byte_order, Signifigance + field :bitmap_bit_order, Signifigance + field :bitmap_format_scanline_unit, Uint8 + field :bitmap_format_scanline_pad, Uint8 + field :min_keycode, KeyCode + field :max_keycode, KeyCode + unused 4 + field :vendor, String8, :string + field :formats, FormatInfo, :list + field :screens, ScreenInfo, :list + end + end +end diff --git a/lib/X11/packet.rb b/lib/X11/packet.rb deleted file mode 100644 index c1a504a..0000000 --- a/lib/X11/packet.rb +++ /dev/null @@ -1,102 +0,0 @@ -module X11 - module Packet - - class BasePacket - include X11::Type - - class << self - def create(*parameters) - lengths = lengths_for(parameters) - - packet = @structs.map do |s| - case s.type - when :field - s.type_klass.pack(parameters.shift) - when :unused - "\x00" * s.size - when :length - s.type_klass.pack(lengths[s.name]) - when :string - s.type_klass.pack(parameters.shift) - when :list - parameters.shift.each do |obj| - s.type_klass.create(*obj) - end - end - end - - ((@opcode ? [X11::Type::Int8.pack(@opcode)] : []) + packet).join - end - - def read(socket) - lengths = {} - values = {} - - @structs.each do |s| - case s.type - when :field - values[s.name] = s.type_klass.unpack( socket.read(s.type_klass.size) ) - when :unused - socket.read(s.size) - when :length - size = s.type_klass.unpack( socket.read(s.type_klass.size) ) - lengths[s.name] = size - when :string - values[s.name] = s.type_klass.unpack(socket, lengths[s.name]) - when :list - values[s.name] = lengths[s.name].times.collect do - s.type_klass.read(socket) - end - end - end - - OpenStruct.new(values) - end - - def field(*args) - name, type_klass, type = args - s = Struct.new(:name, :type_klass, :type).new - s.name = name - s.type = (type == nil ? :field : type) - s.type_klass = type_klass - - @structs ||= [] - @structs << s - end - - def unused(size) - s = Struct.new(:size, :type).new - s.size = size - s.type = :unused - - @structs ||= [] - @structs << s - end - - def opcode(value) - @opcode = value - end - - private - - def lengths_for(args) - args = args.dup - lengths = {} - - fields.each do |s| - value = args.shift - lengths[s.name] = value.size if s.type == :list or s.type == :string - end - - return lengths - end - - def fields - @structs.dup.delete_if do |s| - s.type == :unused or s.type == :length - end - end - end - end - end -end diff --git a/lib/X11/packets/display.rb b/lib/X11/packets/display.rb deleted file mode 100644 index 2ad1ec8..0000000 --- a/lib/X11/packets/display.rb +++ /dev/null @@ -1,84 +0,0 @@ -module X11 - module Packet - - class ClientHandshake < BasePacket - field :byte_order, Uint8 - unused 1 - field :protocol_major_version, Uint16 - field :protocol_minor_version, Uint16 - field :auth_proto_name, Uint16, :length - field :auth_proto_data, Uint16, :length - unused 2 - field :auth_proto_name, String8, :string - field :auth_proto_data, String8, :string - end - - class FormatInfo < BasePacket - field :depth, Uint8 - field :bits_per_pixel, Uint8 - field :scanline_pad, Uint8 - unused 5 - end - - class VisualInfo < BasePacket - field :visual_id, VisualID - field :qlass, Uint8 - field :bits_per_rgb_value, Uint8 - field :colormap_entries, Uint16 - field :red_mask, Uint32 - field :green_mask, Uint32 - field :blue_mask, Uint32 - unused 4 - end - - class DepthInfo < BasePacket - field :depth, Uint8 - unused 1 - field :visuals, Uint16, :length - unused 4 - field :visuals, VisualInfo, :list - end - - class ScreenInfo < BasePacket - field :root, Window - field :default_colormap, Colormap - field :white_pixel, Colornum - field :black_pixel, Colornum - field :current_input_masks, EventMask - field :width_in_pixels, Uint16 - field :height_in_pixels, Uint16 - field :width_in_millimeters, Uint16 - field :height_in_millimeters, Uint16 - field :min_installed_maps, Uint16 - field :max_installed_maps, Uint16 - field :root_visual, VisualID - field :backing_stores, Uint8 - field :save_unders, Bool - field :root_depth, Uint8 - field :depths, Uint8,:length - field :depths, DepthInfo, :list - end - - class DisplayInfo < BasePacket - field :release_number, Uint32 - field :resource_id_base, Uint32 - field :resource_id_mask, Uint32 - field :motion_buffer_size, Uint32 - field :vendor, Uint16, :length - field :maximum_request_length, Uint16 - field :screens, Uint8, :length - field :formats, Uint8, :length - field :image_byte_order, Signifigance - field :bitmap_bit_order, Signifigance - field :bitmap_format_scanline_unit, Uint8 - field :bitmap_format_scanline_pad, Uint8 - field :min_keycode, KeyCode - field :max_keycode, KeyCode - unused 4 - field :vendor, String8, :string - field :formats, FormatInfo, :list - field :screens, ScreenInfo, :list - end - - end -end diff --git a/test/form_test.rb b/test/form_test.rb new file mode 100644 index 0000000..a480e4c --- /dev/null +++ b/test/form_test.rb @@ -0,0 +1,66 @@ +require File.expand_path('../helper', __FILE__) + +class MockSocket + def initialize(packet) + @packet = packet + end + + def read(amount) + @packet.slice!(0..amount-1) + end +end + +class Point < X11::Form::BaseForm + field :x, Int8 + field :y, Int8 +end + +class Child < X11::Form::BaseForm + field :name, Uint16, :length + field :name, String8, :string +end + +class Parent < X11::Form::BaseForm + field :value, Uint8 + field :point, Point + + field :name, Uint16, :length + field :name, String8, :string + + field :children, Uint16, :length + field :children, Child, :list +end + +describe X11::Form::BaseForm do + it "setters and getters on form should work" do + + # we can create partial form objects + # without specifying all parameters. + parent = Parent.new(1337) + + # To fill in the rest of the parameters + # we use attr_accessors + parent.name = "Parent Form" + parent.point = Point.new(0,0) + + parent.point.must_be_instance_of Point + parent.name.must_equal "Parent Form" + + parent.children = [] + parent.children << Child.new + parent.children << Child.new + + assert_equal parent.children.size, 2 + end + + it "should encode/decode a packet" do + parent = Parent.new(255,Point.new(23,17), "Parent Form", []) + socket = MockSocket.new(parent.to_packet) + + decoded = Parent.from_packet(socket) + decoded.value.must_equal 255 + decoded.name.must_equal "Parent Form" + decoded.point.x.must_equal 23 + decoded.point.y.must_equal 17 + end +end diff --git a/test/packet_test.rb b/test/packet_test.rb deleted file mode 100644 index 9abc9ce..0000000 --- a/test/packet_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class MockSocket - def initialize(packet) - @packet = packet - end - - def read(amount) - @packet.slice!(0..amount-1) - end -end - -class Child < X11::Packet::BasePacket - field :name, Uint16, :length - field :name, String8, :string -end - -class Parent < X11::Packet::BasePacket - field :a, Uint8 - field :b, Uint16 - field :c, Uint32 - - field :d, Uint16, :length - field :d, String8, :string - - field :children, Uint16, :length - field :children, Child, :list -end - -describe X11::Packet::BasePacket do - it "should create and read a packet" do - children = [] - children << Child.create("#1") - children << Child.create("#2") - children << Child.create("#3") - - packet = Parent.create(1,2,3,"Hello World", children) - socket = MockSocket.new(packet) - reader = Parent.read(socket) - - reader.a.must_equal 1 - reader.b.must_equal 2 - reader.c.must_equal 3 - reader.d.must_equal "Hello World" - - reader.children.shift.name.must_equal("#1") - reader.children.shift.name.must_equal("#2") - reader.children.shift.name.must_equal("#3") - end -end