Class: Arpie::ProtocolChain

Inherits:
Object
  • Object
show all
Includes:
Arpie
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.

Constant Summary

Constants included from Arpie

MTU

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Arpie

#bogon!, #incomplete!

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.



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/arpie/protocol.rb', line 35

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.



20
21
22
# File 'lib/arpie/protocol.rb', line 20

def buffer
  @buffer
end

#chainObject (readonly)

Array of Protocols.



17
18
19
# File 'lib/arpie/protocol.rb', line 17

def chain
  @chain
end

#endpoint_classObject

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



27
28
29
# File 'lib/arpie/protocol.rb', line 27

def endpoint_class
  @endpoint_class
end

#messagesObject (readonly)

A buffer holding all parsed, but unreturned messages.



23
24
25
# File 'lib/arpie/protocol.rb', line 23

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.



73
74
75
76
77
78
79
80
81
82
83
# File 'lib/arpie/protocol.rb', line 73

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.



89
90
91
92
93
94
95
96
97
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
# File 'lib/arpie/protocol.rb', line 89

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_with_index do |message, m_index|
      cut_to_index = p.from(message) do |object|
        messages_for_next << object
      end rescue case $!
        when YieldResult
          messages_for_next.concat($!.result)
          next

        when EIncomplete
          if messages.size - 1 - m_index > 0
            next
          else
            raise
          end

        else
          raise
      end
    end rescue case $!
      when EIncomplete
        if p_index == 0
          select([io])
          @buffer << io.readpartial(MTU) rescue raise $!.class,
            "#{$!.to_s}; unparseable bytes remaining in buffer: #{@buffer.size}"
          retry

        else
          p_index = 0
          messages_for_next = []
          messages = [@buffer]
          next # of loop protocol chain
        end

      else
        raise
    end

    if messages_for_next.size == 0
      # Get back to IO level and retry reading more crud
      messages_for_next = [@buffer]
      p_index = -1
    end

    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 # loop chain

  message = messages.shift
  @messages = messages
  message
end

#resetObject



169
170
171
# File 'lib/arpie/protocol.rb', line 169

def reset
  @buffer = ""
end

#to(message) ⇒ Object

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



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

def to message
  messages = [message]
  @chain.each {|p|
    for_next = []
    messages.each {|msg|
      p.to(msg) do |a|
        for_next << a
      end
    }

    messages = for_next
  }
  messages
end

#write_message(io, *messages) ⇒ Object

Write message to io.



164
165
166
167
# File 'lib/arpie/protocol.rb', line 164

def write_message io, *messages
  binary = messages.map {|m| to(m)}
  io.write(binary)
end