Class: Omnizip::Parity::Models::FileDescriptionPacket

Inherits:
Packet
  • Object
show all
Defined in:
lib/omnizip/parity/models/file_description_packet.rb

Overview

PAR2 File Description packet

Contains metadata about a protected file including hashes and filename.

Body structure:

  • file_id (16 bytes): MD5 hash identifying the file
  • file_hash (16 bytes): MD5 hash of complete file content
  • file_hash_16k (16 bytes): MD5 hash of first 16KB
  • length (8 bytes, Q<): File size in bytes
  • filename (variable): Null-terminated filename, padded to multiple of 4

Constant Summary collapse

PACKET_TYPE =

Packet type identifier

"PAR 2.0\x00FileDesc"

Constants inherited from Packet

Packet::PACKET_SIGNATURE

Instance Attribute Summary

Attributes inherited from Packet

#body_data

Instance Method Summary collapse

Methods inherited from Packet

#calculate_hash, #packet_type, read_from, #valid_hash?, #write_to

Constructor Details

#initialize(**attributes) ⇒ FileDescriptionPacket

Initialize file description packet

Parameters:

  • attributes (Hash)

    Packet attributes



38
39
40
41
# File 'lib/omnizip/parity/models/file_description_packet.rb', line 38

def initialize(**attributes)
  super
  self.type = PACKET_TYPE
end

Instance Method Details

#basenameString

Get basename of file

Returns:

  • (String)

    File basename



125
126
127
# File 'lib/omnizip/parity/models/file_description_packet.rb', line 125

def basename
  File.basename(filename)
end

#build_bodyString

Build body data from attributes

Constructs binary body data with proper padding

Returns:

  • (String)

    Binary body data



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
# File 'lib/omnizip/parity/models/file_description_packet.rb', line 91

def build_body
  data = +""

  # Validate file_id, file_hash, file_hash_16k are 16 bytes
  [file_id, file_hash, file_hash_16k].each_with_index do |hash, idx|
    field_name = %w[file_id file_hash file_hash_16k][idx]
    if hash.bytesize != 16
      raise ArgumentError,
            "#{field_name} must be 16 bytes, got #{hash.bytesize}"
    end
  end

  # Write hashes
  data << file_id
  data << file_hash
  data << file_hash_16k

  # Write length (8 bytes, little-endian)
  data << [length].pack("Q<")

  # Write filename (null-terminated, padded to multiple of 4)
  data << filename
  data << "\x00"

  # Add padding to make total length multiple of 4
  padding = (4 - ((filename.bytesize + 1) % 4)) % 4
  data << ("\x00" * padding) if padding.positive?

  self.body_data = data
end

#parse_bodyObject

Parse body data into attributes

Body format:

  • file_id: 16 bytes
  • file_hash: 16 bytes
  • file_hash_16k: 16 bytes
  • length: 8 bytes (Q<)
  • filename: null-terminated, padded to multiple of 4


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/omnizip/parity/models/file_description_packet.rb', line 51

def parse_body
  return if body_data.nil? || body_data.empty?

  # Validate minimum size (16+16+16+8 = 56 bytes minimum)
  if body_data.bytesize < 56
    warn "FileDescriptionPacket body too short: #{body_data.bytesize} bytes"
    return
  end

  pos = 0

  # Read file_id (16 bytes)
  self.file_id = body_data[pos, 16]
  pos += 16

  # Read file_hash (16 bytes)
  self.file_hash = body_data[pos, 16]
  pos += 16

  # Read file_hash_16k (16 bytes)
  self.file_hash_16k = body_data[pos, 16]
  pos += 16

  # Read length (8 bytes, little-endian unsigned 64-bit)
  length_data = body_data[pos, 8]
  return if length_data.nil? || length_data.bytesize < 8

  self.length = length_data.unpack1("Q<")
  pos += 8

  # Read filename (null-terminated, remaining bytes)
  filename_data = body_data[pos..]
  self.filename = filename_data&.unpack1("Z*") || ""
end