Module: Muxr::Protocol

Defined in:
lib/muxr/protocol.rb

Overview

Length-prefixed framing for client <-> server messages over a Unix socket.

Wire format per message:

[1 byte type][4 bytes BE length][length bytes payload]

Types are single ASCII letters so they’re easy to recognise in tcpdump or hex dumps:

H  hello   (client -> server, payload: "ROWS COLS")
I  input   (client -> server, payload: raw STDIN bytes)
R  resize  (client -> server, payload: "ROWS COLS")
B  bye     (either way, payload: optional reason string)
O  output  (server -> client, payload: raw bytes to write to STDOUT)

Constant Summary collapse

HELLO =
"H".freeze
INPUT =
"I".freeze
RESIZE =
"R".freeze
BYE =
"B".freeze
OUTPUT =
"O".freeze
HEADER_SIZE =
5

Class Method Summary collapse

Class Method Details

.decode_size(payload) ⇒ Object

Returns [rows, cols] or nil if malformed.



64
65
66
67
68
69
70
71
# File 'lib/muxr/protocol.rb', line 64

def self.decode_size(payload)
  parts = payload.to_s.strip.split(/\s+/)
  return nil unless parts.length == 2
  r = Integer(parts[0]) rescue nil
  c = Integer(parts[1]) rescue nil
  return nil unless r && c
  [r, c]
end

.encode_size(rows, cols) ⇒ Object

Encodes a “ROWS COLS” string for HELLO / RESIZE payloads.



59
60
61
# File 'lib/muxr/protocol.rb', line 59

def self.encode_size(rows, cols)
  "#{rows.to_i} #{cols.to_i}"
end

.frame(type, payload = "") ⇒ Object

Builds the on-the-wire bytes for a single frame without writing them. Lets callers append to an outgoing buffer (for non-blocking flushes later) instead of doing a synchronous io.write.

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
# File 'lib/muxr/protocol.rb', line 48

def self.frame(type, payload = "")
  raise ArgumentError, "type must be a single byte" unless type.is_a?(String) && type.bytesize == 1
  bytes = payload.to_s.b
  buf = +"".b
  buf << type.b
  buf << [bytes.bytesize].pack("N")
  buf << bytes
  buf
end

.read(io) ⇒ Object

Reads exactly one framed message from io. Returns [type, payload] or nil on EOF / truncated frame. Blocks until the full message arrives.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/muxr/protocol.rb', line 25

def self.read(io)
  header = read_exact(io, HEADER_SIZE)
  return nil unless header
  type = header[0]
  length = header.byteslice(1, 4).unpack1("N")
  payload =
    if length.zero?
      ""
    else
      read_exact(io, length)
    end
  return nil unless payload
  [type, payload]
end

.write(io, type, payload = "") ⇒ Object

Writes one framed message. payload is treated as raw bytes (binary).



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

def self.write(io, type, payload = "")
  io.write(frame(type, payload))
end