Module: Takagi::Network::Framing::Tcp

Defined in:
lib/takagi/network/framing/tcp.rb

Overview

RFC 8323 CoAP over TCP framing with variable-length encoding

Class Method Summary collapse

Class Method Details

.decode(data) ⇒ Message::Inbound

Decode framed TCP message

Parameters:

  • data (String)

    Framed binary data

Returns:



22
23
24
# File 'lib/takagi/network/framing/tcp.rb', line 22

def decode(data)
  Message::Inbound.new(data, transport: :tcp)
end

.encode(message) ⇒ String

Encode message with RFC 8323 framing

Parameters:

Returns:

  • (String)

    Framed binary data



12
13
14
15
16
17
# File 'lib/takagi/network/framing/tcp.rb', line 12

def encode(message)
  # Build base message
  packet = build_base_message(message)
  # Add RFC 8323 framing
  frame(packet)
end

.read_from_socket(socket, logger: nil) ⇒ String?

Read a complete message from socket

Parameters:

  • socket (TCPSocket)

    Socket to read from

  • logger (Logger) (defaults to: nil)

    Optional logger for debugging

Returns:

  • (String, nil)

    Complete framed message or nil



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/takagi/network/framing/tcp.rb', line 30

def read_from_socket(socket, logger: nil)
  first_byte_data = socket.read(1)
  return nil if first_byte_data.nil? || first_byte_data.empty?

  first_byte = first_byte_data.unpack1('C')
  len_nibble = (first_byte >> 4) & 0x0F
  tkl = first_byte & 0x0F

  logger&.debug "read_from_socket: first_byte=0x#{first_byte.to_s(16)}, len_nibble=#{len_nibble}, tkl=#{tkl}"

  length = read_length(socket, len_nibble, logger: logger)
  return nil unless length

  logger&.debug "read_from_socket: message length=#{length} bytes"

  # Read: Code (1) + Token (tkl) + Options + Payload (length)
  bytes_to_read = 1 + tkl + length
  data = +''.b
  remaining = bytes_to_read

  while remaining.positive?
    begin
      chunk = socket.readpartial(remaining)
    rescue IO::WaitReadable
      IO.select([socket])
      retry
    rescue EOFError
      logger&.error "read_from_socket: Incomplete message (expected #{bytes_to_read}, got #{data.bytesize})"
      return nil
    end

    data << chunk
    remaining -= chunk.bytesize
  end

  logger&.debug "read_from_socket: Successfully read #{bytes_to_read} bytes"

  first_byte_data + data
rescue IOError, Errno::ECONNRESET => e
  logger&.debug "read_from_socket: Socket error (#{e.class}: #{e.message})"
  nil
end