Class: Arpie::ProtocolChain

Inherits:
Object
  • Object
show all
Defined in:
lib/arpie/protocol.rb

Overview

A ProtocolChain wraps one or more Protocols to provide a parser list, into which io data can be fed and parsed packets received; and vice versa.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*protocols) ⇒ ProtocolChain

Create a new Chain. Supply an Array of Protocol instances, where the leftmost is the innermost.

Example:

MarshalProtocol.new, SizedProtocol.new

would wrap marshalled data inside SizedProtocol.



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/arpie/protocol.rb', line 53

def initialize *protocols
  protocols.size > 0 or raise ArgumentError, "Specify at least one protocol."
  protocols[-1].class::CAN_SEPARATE_MESSAGES or
    raise ArgumentError,
      "The outermost protocol needs to be able to " +
      "separate messages in a stream (#{protocols.inspect} does not)."

  @endpoint_class = Arpie::Endpoint

  @chain = protocols
  @buffer = ""
  @messages = []
end

Instance Attribute Details

#bufferObject (readonly)

String holding all read, but yet unparsed bytes.



38
39
40
# File 'lib/arpie/protocol.rb', line 38

def buffer
  @buffer
end

#chainObject (readonly)

Array of Protocols.



35
36
37
# File 'lib/arpie/protocol.rb', line 35

def chain
  @chain
end

#endpoint_classObject

The endpoint class of this Protocol. Defaults to Arpie::Endpoint



45
46
47
# File 'lib/arpie/protocol.rb', line 45

def endpoint_class
  @endpoint_class
end

#messagesObject (readonly)

A buffer holding all parsed, but unreturned messages.



41
42
43
# File 'lib/arpie/protocol.rb', line 41

def messages
  @messages
end

Instance Method Details

#from(binary) ⇒ Object

Convert the given binary to message format by passing it through all protocols in the chain. May raise EStreamError or EIncomplete, in the case that binary does not satisfy one of the protocols.

Returns an array of messages, even if only one message was contained.



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/arpie/protocol.rb', line 82

def from binary
  r, w = IO.pipe
  w.write(binary)
  w.close
  results = []
  results << read_message(r) until false rescue begin
    r.close
    return results
  end
  raise "Interal error: should not reach this."
end

#read_message(io) ⇒ Object

Read a message from io. Block until all protocols agree that a message has been received.

Returns the message.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/arpie/protocol.rb', line 98

def read_message io
  return @messages.shift if @messages.size > 0

  messages = [@buffer]
  chain = @chain.reverse
  p_index = 0

  while p_index < chain.size do p = chain[p_index]
    cut_to_index = nil
    messages_for_next = []

    messages.each do |message|
      cut_to_index = p.from(message) do |object|
        messages_for_next << object
      end rescue case $!

        when YieldResult
          messages_for_next.concat($!.result)
          next

        when ESwallow
          messages.delete(message)
          messages_for_next = messages
          p_index -= 1
          break

        when EIncomplete
          # All protocols above the io one need to wait for each
          # one above to yield more messages.
          if p_index > 0
            # Unwind to the parent protocol and let it read in some
            # more messages ..
            messages_for_next = messages
            messages_for_next.shift
            p_index -= 1
            break

          # The first protocol manages +io+.
          else
            select([io])
            @buffer << io.readpartial(MTU) rescue raise $!.class,
              "#{$!.to_s}; unparseable bytes remaining in buffer: #{@buffer.size}"
            retry
          end

        when ETryAgain
          retry

        else
          raise
      end # rescue case
    end # messages.each

     raise "BUG: #{p.class.to_s}#from did not yield a message." if
       messages_for_next.size == 0

    messages = messages_for_next

    if p_index == 0
      if cut_to_index.nil? || cut_to_index < 0
        raise "Protocol '#{p.class.to_s}'implementation faulty: " +
          "from did return an invalid cut index: #{cut_to_index.inspect}."
      else
        @buffer[0, cut_to_index] = ""
      end
    end

    p_index += 1
  end # chain loop

  message = messages.shift
  @messages = messages
  message
end

#resetObject



178
179
180
# File 'lib/arpie/protocol.rb', line 178

def reset
  @buffer = ""
end

#to(message) ⇒ Object

Convert the given message to wire format by passing it through all protocols in the chain.



69
70
71
72
73
# File 'lib/arpie/protocol.rb', line 69

def to message
  ret = @chain.inject(message) {|msg, p|
    p.to(msg)
  }
end

#write_message(io, message) ⇒ Object

Write message to io.



174
175
176
# File 'lib/arpie/protocol.rb', line 174

def write_message io, message
  io.write(to message)
end