Class: RStyx::Message::StyxMessage
- Inherits:
-
Object
- Object
- RStyx::Message::StyxMessage
- Defined in:
- lib/rstyx/messages.rb
Overview
Base class of a Styx message.
Direct Known Subclasses
Rattach, Rauth, Rclunk, Rcreate, Rerror, Rflush, Ropen, Rread, Rremove, Rstat, Rversion, Rwalk, Rwrite, Rwstat, Tattach, Tauth, Tclunk, Tcreate, Terror, Tflush, Topen, Tread, Tremove, Tstat, Tversion, Twalk, Twrite, Twstat
Constant Summary collapse
- MESSAGE_IDS =
A Hash indexed by the class of the message and its identifier number.
{}
Instance Attribute Summary collapse
-
#fieldvals ⇒ Object
A hash indexed by the field names giving the field values.
Class Method Summary collapse
-
.add_field(name, type) ⇒ Object
Add a field to the StyxMessage.
-
.fields ⇒ Object
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).
-
.from_bytes(str) ⇒ Object
Deserialize a byte string into a StyxMessage subclass of some kind.
Instance Method Summary collapse
-
#ident ⇒ Object
Return the identifier of the message (code).
-
#initialize(fieldvals = {}) ⇒ StyxMessage
constructor
Create a new StyxMessage class.
-
#tag ⇒ Object
Return the tag of the message.
-
#tag=(t) ⇒ Object
Set the tag of the message.
-
#to_bytes ⇒ Object
Serialize a Styx message subclass instance into a byte string.
-
#to_s ⇒ Object
Convert a Styx message into a human-readable string.
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
#fieldvals ⇒ Object
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:
-
Any valid format string used by String#unpack or Array#pack.
-
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.
-
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.
-
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.
-
Qid, which deserializes into a Qid object instance and is serialized into a 13-byte binary representation.
-
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.
-
ULongLong, which deserializes into a Ruby Fixnum and is serialized into a 64-bit little-endian value.
-
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 |
.fields ⇒ Object
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
#ident ⇒ Object
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 |
#tag ⇒ Object
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_bytes ⇒ Object
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_s ⇒ Object
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 |