Class: RStyx::Message::StyxMessage

Inherits:
Object
  • Object
show all
Defined in:
lib/rstyx/messages.rb

Overview

Base class of a Styx message.

Constant Summary collapse

MESSAGE_IDS =

A Hash indexed by the class of the message and its identifier number.

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fieldvals = {}) ⇒ StyxMessage

Create a new StyxMessage class. This takes a hash of field names and values, and this is put into a hash.

fieldvals

A hash of field values. These values need not be only the values of defined, for the message, but only the values actually defined may be directly accessed and will be serialized.



337
338
339
340
# File 'lib/rstyx/messages.rb', line 337

def initialize(fieldvals={})
  ident = MESSAGE_IDS[self.class]
  @fieldvals = {:ident=>ident}.merge(fieldvals)
end

Instance Attribute Details

#fieldvalsObject

A hash indexed by the field names giving the field values.



262
263
264
# File 'lib/rstyx/messages.rb', line 262

def fieldvals
  @fieldvals
end

Class Method Details

.add_field(name, type) ⇒ Object

Add a field to the StyxMessage. Used by subclasses to define the message field. The name should be a Symbol that gives the name of the field (preferably the canonical name given in the Inferno manual page intro(5)), and the type may be:

  1. Any valid format string used by String#unpack or Array#pack.

  2. Cstr, which is a UTF-8 string, which will be serialized as a two-byte unsigned length (in bytes) followed by the string’s data itself, and deserialized from this representation into a standard Ruby string.

  3. CstrList, which deserializes into an array of Ruby strings, and is serialized into a two-byte unsigned count of strings followed by each of the strings itself, as in Cstr.

  4. Bstr, which is a binary string. It will be serialized to a four-byte unsigned length (in bytes) followed by the string’s data itself, and deserialized from this representation into a standard Ruby string.

  5. Qid, which deserializes into a Qid object instance and is serialized into a 13-byte binary representation.

  6. QidList, which deserializes into an array of Qid objects and is serialized into a two-byte unsigned count of Qid objects followed by the serialized representations of each of the Qid objects.

  7. ULongLong, which deserializes into a Ruby Fixnum and is serialized into a 64-bit little-endian value.

  8. Stat, which deserializes into a Stat object instance and is serialized into the stat format described in the Inferno man page stat(5). See the Stat class for more details.

This method will cause the (sub)class which uses it to have its inherited copy of StyxMessage#fields to receive the name and type declaration, and it will create attribute reader and writer methods of the form name and name= to be added to the class.



304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/rstyx/messages.rb', line 304

def self.add_field(name, type)
  self.fields << [name, type]

  # Create accessor methods for the field
  define_method(name) do
    instance_variable_get("@fieldvals")[name]
  end

  define_method(name.to_s + "=") do |val|
    fname = instance_variable_get("@fieldvals")
    fname[name] = val
  end
end

.fieldsObject

The fields of the Styx message, which consists of an array of arrays consisting of the field name and the field type (see StyxMessage#add_field for more details).



323
324
325
326
# File 'lib/rstyx/messages.rb', line 323

def self.fields
  # Default fields (excluding the size[4] field)
  @fields ||= [[:ident, 'C'], [:tag, 'v']]
end

.from_bytes(str) ⇒ Object

Deserialize a byte string into a StyxMessage subclass of some kind.

str

A byte string representing a Styx message

return value

The StyxMessage subclass instance represented by str

raises

StyxException if there was some error decoding str



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/rstyx/messages.rb', line 372

def self.from_bytes(str)
  origlength = str.length
  # get the length, identifier, and the rest of the string
  len, ident, str = str.unpack("VCa*")
  if len.nil? || len > origlength
    raise StyxException.new("message string too short: #{len} bytes expected, only #{origlength} available")
  end
  c = MESSAGE_IDS.index(ident)
  if c.nil?
    raise StyxException.new("Unknown message type identifier #{ident.inspect}")
  end
  obj = c.new
  c.fields.each do |name,type|
    if name == :ident
      next
    end
    val = nil
    case type
    when "Cstr"
      len, str = str.unpack("va*")
      val, str = str.unpack("a#{len}a*")
    when "CstrList"
      nstr, str = str.unpack("va*")
      val = []
      1.upto(nstr) do
        len, str = str.unpack("va*")
        xstr, str = str.unpack("a#{len}a*")
        val << xstr
      end
    when "Bstr"
      len, str = str.unpack("Va*")
      val, str = str.unpack("a#{len}a*")
    when "Qid"
      qid, str = str.unpack("a#{Qid::QID_LENGTH}a*")
      val = Qid.from_bytes(qid)
    when "QidList"
      nqid, str = str.unpack("va*")
      val = []
      1.upto(nqid) do
        qid,str = str.unpack("a#{Qid::QID_LENGTH}a*")
        val << Qid.from_bytes(qid)
      end
    when "Stat"
      # See the corresponding comments under to_bytes
      # (from when "Stat") for why we do it this way.
      slen1 = str.unpack("v")
      slen1, stat, str = str.unpack("va#{slen1}a*")
      val = Stat.from_bytes(stat)
    when "ULongLong"
      v1, v2, str = str.unpack("VVa*")
      # v1 - low word, v2 = high word
      val = v2 << 32 | v1
    else
      val, str = str.unpack(type + "a*")
    end
    obj.fieldvals[name] = val
  end
  return(obj)
end

Instance Method Details

#identObject

Return the identifier of the message (code). This cannot be changed.



346
347
348
# File 'lib/rstyx/messages.rb', line 346

def ident
  return(@fieldvals[:ident])
end

#tagObject

Return the tag of the message.



353
354
355
# File 'lib/rstyx/messages.rb', line 353

def tag
  return(@fieldvals[:tag])
end

#tag=(t) ⇒ Object

Set the tag of the message.



360
361
362
# File 'lib/rstyx/messages.rb', line 360

def tag=(t)
  return(@fieldvals[:tag] = t)
end

#to_bytesObject

Serialize a Styx message subclass instance into a byte string.

returns

The serialized String representation of the Styx message subclass instance.



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/rstyx/messages.rb', line 438

def to_bytes
  str = ""
  self.class.fields.each do |name,type|
    case type
    when "Cstr"
      str << [@fieldvals[name].length, @fieldvals[name]].pack("va*")
    when "CstrList"
      strlist = @fieldvals[name]
      str << [strlist.length].pack("v")
      strlist.each do |s|
        str << [s.length, s].pack("va*")
      end
    when "Bstr"
      str << [@fieldvals[name].length, @fieldvals[name]].pack("Va*")
    when "Qid"
      str << @fieldvals[name].to_bytes
    when "QidList"
      qlist = @fieldvals[name]
      str << [qlist.length].pack("v")
      qlist.each do |q|
        str << q.to_bytes
      end
    when "ULongLong"
      # low dword
      str << [@fieldvals[name] & 0xffffffff].pack("V")
      # high dword
      str << [(@fieldvals[name] >> 32) & 0xffffffff].pack("V")
    when "Stat"
      # From the Inferno stat(5) man page:
      #
      #   To make the contents of a directory, such as returned
      #   by read(5), easy to  parse, each directory entry
      #   begins with a size field.  For consistency, the entries
      #   in Twstat and Rstat  messages  also contain their
      #   size, which means the size appears twice.
      #
      # And so this is why we prefix the serialized version of
      # the stat message with the size here, and when deserializing
      # we do the same thing.
      #
      statstr = @fieldvals[name].to_bytes
      str << [statstr.length, statstr].pack("va*")
    else
      # format string for Array#pack
      str << [@fieldvals[name]].pack(type)
    end
  end
  # add length
  str = [str.length + 4].pack("V") + str
  return(str)
end

#to_sObject

Convert a Styx message into a human-readable string.

returns

The Styx message instance converted to a string.



494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/rstyx/messages.rb', line 494

def to_s
  # First, start with the Styx message class name
  str = "(" + self.class.to_s.split("::")[-1]
  self.class.fields.each do |name, type|
    # Ignore ident (redundant, as it is already expressed in
    # the class name)
    if name == :ident
      next
    end
    str << " " + name.inspect + "=>" + @fieldvals[name].to_s.inspect
  end
  str << ")"
end