rstyx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1429 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Copyright (C) 2005 Rafael Sevilla
4
+ # This file is part of RStyx
5
+ #
6
+ # RStyx is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as
8
+ # published by the Free Software Foundation; either version 2.1
9
+ # of the License, or (at your option) any later version.
10
+ #
11
+ # RStyx is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with RStyx; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19
+ # 02111-1307 USA.
20
+ #
21
+ # Styx Message classes and utility functions
22
+ #
23
+ # Author:: Rafael R. Sevilla (mailto:dido@imperium.ph)
24
+ # Copyright:: Copyright (c) 2005 Rafael R. Sevilla
25
+ # License:: GNU Lesser General Public License
26
+ #
27
+ # $Id: messages.rb,v 1.24 2005/09/30 05:35:42 dido Exp $
28
+ #
29
+
30
+ require 'rstyx/errors'
31
+
32
+ module RStyx
33
+
34
+ module Message
35
+ ##
36
+ # Utility class
37
+ #
38
+ class Util
39
+ ##
40
+ # Extract a Ruby string given a byte string, with an offset pointing
41
+ # to the start of the Styx string to extract. Returns the Styx string
42
+ # plus the offset of the first character just after the end of the
43
+ # string thus extracted.
44
+ #
45
+ # +str+:: [String] the byte string to extract from
46
+ # +offset+:: [Fixnum] an offset into str for the Styx string to be
47
+ # extracted
48
+ # return value:: [Array]
49
+ # 0:: [String] the Ruby string
50
+ # 1:: [Fixnum] the offset of the first character after
51
+ #
52
+ # raises:: StyxException, if no length was found or if the length
53
+ # of the string is different from its declared length.
54
+ #
55
+ def Util.strextract(str, offset)
56
+ length = (str[offset..(offset + 1)].unpack("v"))[0]
57
+ if length.nil?
58
+ raise StyxException.new("invalid string, no length found")
59
+ end
60
+
61
+ offset += 2
62
+ nstr = (str[offset..(offset + length - 1)])
63
+ if (nstr.length != length)
64
+ raise StyxException.new("invalid string")
65
+ end
66
+ return([nstr, offset + length])
67
+ end
68
+
69
+ ##
70
+ # Pack a string into the canonical format of number of bytes as short
71
+ # unsigned int followed by the string itself
72
+ #
73
+ # +str+:: [String] the Ruby string to pack
74
+ # return value:: [String] the corresponding Styx string
75
+ #
76
+ def Util.strpack(str)
77
+ return([str.length, str].pack("vA*"))
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Class representing the server's view of a file.
83
+ #
84
+ class Qid
85
+ attr_accessor :qtype, :version, :path
86
+
87
+ QID_LENGTH = 13 # size of a Qid
88
+
89
+ ##
90
+ # Create a new Qid object.
91
+ #
92
+ # +type+:: [Fixnum] the type of file (directory, append only file, etc.)
93
+ # +version+:: [Fixnum] the version number of the file
94
+ # +path+:: [Fixnum] a 64-bit integer that should be unique among all
95
+ # files being served
96
+ #
97
+ def initialize(type, version, path)
98
+ @qtype = type
99
+ @version = version
100
+ @path = path
101
+ end
102
+
103
+ ##
104
+ # Get the byte string representation of the Qid
105
+ #
106
+ # return value:: [String] the byte string representation of the Qid
107
+ #
108
+ def to_bytes
109
+ pathlo = @path & 0xffffffff
110
+ pathhi = (@path >> 32) & 0xffffffff
111
+ return([@qtype, @version, pathlo, pathhi].pack("CVVV"))
112
+ end
113
+
114
+ ##
115
+ # Decode a serialized Qid from its byte string representation
116
+ #
117
+ # +msgbytes+:: [String] the byte string representation of the qid
118
+ # return value:: [Qid] the Qid represented by the byte string.
119
+ # raises:: StyxException if the string cannot be decoded as a Qid
120
+ #
121
+ def Qid.decode(msgbytes)
122
+ qtype, version, pathlo, pathhi = msgbytes.unpack("CVVV")
123
+ if qtype.nil? || version.nil? || pathlo.nil? || pathhi.nil?
124
+ raise StyxException.new("QID failed decode")
125
+ end
126
+ # recombine in little-endian mode
127
+ path = pathlo | (pathhi << 32)
128
+ return(Qid.new(qtype, version, path))
129
+ end
130
+
131
+ ##
132
+ # Dump a Qid
133
+ #
134
+ # return value:: [String] a textual representation of the Qid
135
+ #
136
+ def to_s
137
+ return(sprintf("qid(%s, %s, %s)", @qtype, @version, @path))
138
+ end
139
+ end
140
+
141
+ ##
142
+ # Class representing an entry in a directory (e.g. the result of a
143
+ # stat message)
144
+ #
145
+ class DirEntry
146
+ attr_accessor :size, :dtype, :dev, :qid, :mode, :atime, :mtime
147
+ attr_accessor :length, :name, :uid, :gid, :muid
148
+
149
+ ##
150
+ # Serialize a DirEntry. This also calculates the size of the
151
+ # DirEntry in bytes.
152
+ #
153
+ # return value:: [String] the serialized version of the DirEntry
154
+ #
155
+ def to_bytes
156
+ str = [@dtype, @dev].pack("vV")
157
+ str << @qid.to_bytes
158
+ lengthlo = @length & 0xffffffff
159
+ lengthhi = (@length >> 32) & 0xffffffff
160
+ str << [@mode, @atime, @mtime, lengthlo, lengthhi].pack("VVVVV")
161
+ str << Util.strpack(@name)
162
+ str << Util.strpack(@uid)
163
+ str << Util.strpack(@gid)
164
+ str << Util.strpack(@muid)
165
+ @size = str.length
166
+ return([size].pack("v") + str)
167
+ end
168
+
169
+ ##
170
+ # Unserialize a DirEntry
171
+ #
172
+ # +bytes+:: [String] serialized string representation of a DirEntry
173
+ # return value:: [DirEntry] the DirEntry corresponding to the
174
+ # passed string
175
+ # raises:: StyxException if +bytes+ cannot be properly decoded as
176
+ # a DirEntry
177
+ #
178
+ def DirEntry.decode(bytes)
179
+ # From Inferno stat(5)
180
+ #
181
+ # 0-1 = size
182
+ # 2-3 = type
183
+ # 4-7 = dev
184
+ # 8 = Qid.type
185
+ # 9-12 = Qid.vers
186
+ # 13-20 = Qid.path
187
+ # 21-24 = mode
188
+ # 25-28 = atime
189
+ # 29-32 = mtime
190
+ # 33-40 = length
191
+ # 41-42 = name length
192
+ # 43 to 42 + namelen = name
193
+ # 43 + namelen to 44 + namelen = uid length
194
+ # 45 + namelen to 44 + namelen + uidlen = uid
195
+ # 45 + namelen + uidlen
196
+ de = DirEntry.new
197
+ de.size, de.dtype, de.dev = (bytes[0..7]).unpack("vvV")
198
+ if de.size.nil? || de.dtype.nil? || de.dev.nil?
199
+ raise RStyx::StyxException.new("failed to decode DirEntry")
200
+ end
201
+ de.qid = Qid.decode(bytes[8..20])
202
+ de.mode, de.atime, de.mtime, lengthlo, lengthhi =
203
+ (bytes[21..40]).unpack("VVVVV")
204
+
205
+ if de.mode.nil? || de.atime.nil? || de.mtime.nil? ||
206
+ lengthlo.nil? || lengthhi.nil?
207
+ raise RStyx::StyxException.new("failed to decode DirEntry")
208
+ end
209
+ # combine in little-endian
210
+ de.length = lengthlo | (lengthhi << 32)
211
+ de.name, offset = Util.strextract(bytes, 41)
212
+ de.uid, offset = Util.strextract(bytes, offset)
213
+ de.gid, offset = Util.strextract(bytes, offset)
214
+ de.muid, offset = Util.strextract(bytes, offset)
215
+ return(de)
216
+ end
217
+
218
+ ##
219
+ # convert a DirEntry to a human-readable string
220
+ #
221
+ # return value:: a string representation of the DirEntry
222
+ #
223
+ def to_s
224
+ s = sprintf("DirEntry(%s, %s, %s, ", @size, @dtype, @dev)
225
+ s << sprintf("%s, %s, %s, %s, ", @qid.to_s, @mode, @atime, @mtime)
226
+ s << sprintf("%s, %s, %s, ", @length, @name, @uid)
227
+ s << sprintf("%s, %s)", @gid, @muid)
228
+ return(s)
229
+ end
230
+ end
231
+
232
+ ##
233
+ # Abstract class representing a message in the Styx protocol.
234
+ # The basic attributes are the length, type, tag, messagebody,
235
+ # and (printable) body.
236
+ #
237
+ class StyxMessage
238
+ attr_accessor :length, :mtype, :tag, :messagebody, :body
239
+
240
+ ##
241
+ # Convert the message to a string of bytes for transmission.
242
+ #
243
+ # +tag+:: [Fixnum] The tag value to use for the message. If
244
+ # not specified, leave the tag value as is.
245
+ # return value:: [String] The marshalled byte string of the message
246
+ #
247
+ def to_bytes(tag=nil)
248
+ unless tag.nil?
249
+ @tag = tag
250
+ end
251
+ val = [@length, @mtype, @tag].pack("VCv") + @messagebody
252
+ return(val)
253
+ end
254
+
255
+ ##
256
+ # Dump the Styx message to a string. The StyxMessage subclass
257
+ # must define the @body variable to something meaningful for
258
+ # this to work.
259
+ #
260
+ # return value:: human-readable string representing the Styx message
261
+ #
262
+ def to_s
263
+ return(sprintf("%s: %s, %s, %s, %s",
264
+ self.class.name, @length, @mtype, @tag, @body))
265
+ end
266
+
267
+ TVERSION = 100
268
+ RVERSION = 101
269
+
270
+ TAUTH = 102
271
+ RAUTH = 103
272
+
273
+ TATTACH = 104
274
+ RATTACH = 105
275
+
276
+ # illegal
277
+ TERROR = 106
278
+ RERROR = 107
279
+
280
+ TFLUSH = 108
281
+ RFLUSH = 109
282
+
283
+ TWALK = 110
284
+ RWALK = 111
285
+
286
+ TOPEN = 112
287
+ ROPEN = 113
288
+
289
+ TCREATE = 114
290
+ RCREATE = 115
291
+
292
+ TREAD = 116
293
+ RREAD = 117
294
+
295
+ TWRITE = 118
296
+ RWRITE = 119
297
+
298
+ TCLUNK = 120
299
+ RCLUNK = 121
300
+
301
+ TREMOVE = 122
302
+ RREMOVE = 123
303
+
304
+ TSTAT = 124
305
+ RSTAT = 125
306
+
307
+ TWSTAT = 126
308
+ RWSTAT = 127
309
+
310
+ ##
311
+ # Decode an arbitrary Styx message, returning the appropriate
312
+ # subclass instance.
313
+ #
314
+ # +message+:: [String] The Styx message in serialized form
315
+ # return value:: [StyxMessage] instance of StyxMessage subclass
316
+ # corresponding to the message.
317
+ # raises:: StyxException if a Styx message could not be decoded
318
+ #
319
+ def StyxMessage.decode(message)
320
+ length, type, tag = message[0..6].unpack("VCv")
321
+ if length.nil? || type.nil? || tag.nil?
322
+ raise RStyx::StyxException.new("invalid Styx message")
323
+ end
324
+ msgclass = case type
325
+ when TVERSION then Tversion
326
+ when RVERSION then Rversion
327
+ when TAUTH then Tauth
328
+ when RAUTH then Rauth
329
+ when TATTACH then Tattach
330
+ when RATTACH then Rattach
331
+ when RERROR then Rerror
332
+ when TFLUSH then Tflush
333
+ when RFLUSH then Rflush
334
+ when TWALK then Twalk
335
+ when RWALK then Rwalk
336
+ when TOPEN then Topen
337
+ when ROPEN then Ropen
338
+ when TCREATE then Tcreate
339
+ when RCREATE then Rcreate
340
+ when TREAD then Tread
341
+ when RREAD then Rread
342
+ when TWRITE then Twrite
343
+ when RWRITE then Rwrite
344
+ when TCLUNK then Tclunk
345
+ when RCLUNK then Rclunk
346
+ when TREMOVE then Tremove
347
+ when RREMOVE then Rremove
348
+ when TSTAT then Tstat
349
+ when RSTAT then Rstat
350
+ when TWSTAT then Twstat
351
+ when RWSTAT then Rwstat
352
+ else raise StyxException.new("invalid message type #{type}")
353
+ end
354
+ msgobj = msgclass.decode(message)
355
+ msgobj.length = length
356
+ msgobj.mtype = type
357
+ msgobj.tag = tag
358
+ return(msgobj)
359
+ end
360
+
361
+ end
362
+
363
+ ##
364
+ # Message sent to negotiate the protocol version and message size.
365
+ # This should be the first message sent on the Styx connection.
366
+ #
367
+ class Tversion < StyxMessage
368
+ attr_accessor :msize, :version
369
+
370
+ ##
371
+ # Construct a new Tversion message
372
+ #
373
+ # +msize+:: [Fixnum] maximum message size
374
+ # +version+:: [String] the version number we use
375
+ #
376
+ def initialize(msize=8216, version="9P2000")
377
+ @msize = msize
378
+ @version = version
379
+ @length = 13 + @version.length
380
+ @mtype = TVERSION
381
+ @messagebody = [@msize].pack("V") + Util.strpack(@version)
382
+ @body = sprintf("%s, %s", @msize, @version)
383
+ end
384
+
385
+ ##
386
+ # Decode a Tversion message. Do not call this method directly.
387
+ #
388
+ # +message+:: [String] raw message string
389
+ # return value:: [Tversion] Tversion instance
390
+ # raises:: StyxException if the message could not be decoded
391
+ #
392
+ def Tversion.decode(message)
393
+ msize = message[7..10].unpack("V")[0]
394
+ if msize.nil?
395
+ raise StyxException.new("Invalid Tversion message")
396
+ end
397
+ version, offset = Util.strextract(message, 11)
398
+ return(Tversion.new(msize, version))
399
+ end
400
+
401
+ end
402
+
403
+ ##
404
+ # Response by Styx server to Tversion message sent by client when
405
+ # a connection is initiated.
406
+ #
407
+ class Rversion < StyxMessage
408
+ attr_accessor :msize, :version
409
+
410
+ ##
411
+ # Construct a new Rversion message
412
+ #
413
+ # +msize+:: [Fixnum] maximum message size
414
+ # +version+:: [String] the version number we use
415
+ #
416
+ def initialize(msize=8216, version="9P2000")
417
+ @msize = msize
418
+ @version = version
419
+ @length = 13 + @version.length
420
+ @mtype = RVERSION
421
+ @messagebody = [@msize].pack("V") + Util.strpack(@version)
422
+ @body = sprintf("%s, %s", @msize, @version)
423
+ end
424
+
425
+ ##
426
+ # Decode an Rversion message. Do not call this method directly.
427
+ #
428
+ # +message+:: [String] raw message string
429
+ # return value:: [Rversion] Rversion instance
430
+ # raises:: StyxException if the message could not be decoded.
431
+ #
432
+ def Rversion.decode(message)
433
+ msize = message[7..10].unpack("V")[0]
434
+ if msize.nil?
435
+ raise StyxException.new("Invalid Rversion message")
436
+ end
437
+ version, offset = Util.strextract(message, 11)
438
+ return(Rversion.new(msize, version))
439
+ end
440
+
441
+ end
442
+
443
+ ##
444
+ # Message sent to authenticate a user to the server
445
+ #
446
+ class Tauth < StyxMessage
447
+ attr_accessor :afid, :uname, :aname
448
+
449
+ NOFID = 0xffffffff
450
+
451
+ ##
452
+ # Construct a new Tauth message.
453
+ #
454
+ # +uname+:: [String] user name to attach as
455
+ # +afid+:: [Fixnum] authenticated fid from auth protocol
456
+ # +aname+:: [String] file tree to access
457
+ #
458
+ def initialize(uname, afid, aname)
459
+ @afid = afid
460
+ @uname = uname
461
+ @aname = aname
462
+ @length = 15 + @uname.length + @aname.length
463
+ @mtype = TAUTH
464
+ @messagebody = [@afid].pack("V")
465
+ @messagebody << Util.strpack(uname)
466
+ @messagebody << Util.strpack(aname)
467
+ @body = sprintf("%s, %s, %s", @afid, @uname, @aname)
468
+ end
469
+
470
+ ##
471
+ # Decode a Tauth message.
472
+ #
473
+ # +message+:: [String] the message to decode
474
+ # return:: [Tauth] the Tauth object
475
+ #
476
+ def Tauth.decode(message)
477
+ afid = message[7..10].unpack("V")[0]
478
+ if afid.nil?
479
+ raise RStyx::StyxException.new("invalid Tauth message")
480
+ end
481
+ uname, offset = Util.strextract(message, 11)
482
+ aname, offset = Util.strextract(message, offset)
483
+ return(Tauth.new(uname, afid, aname))
484
+ end
485
+
486
+ end
487
+
488
+ ##
489
+ # Server response to Rauth message, giving qid of authenticator qid.
490
+ #
491
+ class Rauth < StyxMessage
492
+ attr_accessor :aqid
493
+
494
+ ##
495
+ # Construct a new Rauth message
496
+ #
497
+ # +qid+:: [Qid] The qid of the authenticator file
498
+ #
499
+ def initialize(qid)
500
+ @aqid = qid
501
+ @length = 20 # exact length
502
+ @mtype = RAUTH
503
+ @messagebody = @aqid.to_bytes
504
+ end
505
+
506
+ ##
507
+ # Decode an Rauth message.
508
+ #
509
+ # +message+:: [String] the message to decode
510
+ # return:: [Rauth] the Rauth object
511
+ #
512
+ def Rauth.decode(message)
513
+ qid = Qid.decode(message[7..19])
514
+ return(Rauth.new(qid))
515
+ end
516
+ end
517
+
518
+ ##
519
+ # Message returned to indicate an error condition
520
+ #
521
+ class Rerror < StyxMessage
522
+ attr_accessor :ename
523
+
524
+ ##
525
+ # Construct a new Rerror message given the error name
526
+ #
527
+ # +ename+:: [String] error name for error object
528
+ #
529
+ def initialize(ename)
530
+ @ename = ename
531
+ @length = 9 + @ename.length
532
+ @mtype = RERROR
533
+ @body = ename
534
+ @messagebody = Util.strpack(@ename)
535
+ end
536
+
537
+ ##
538
+ # Decode an Rerror message. Do not use this method directly.
539
+ # Use only from within StyxMessage.decode.
540
+ #
541
+ # +message+:: [String] raw message string
542
+ # return:: [Rerror] Rerror instance
543
+ #
544
+ def Rerror.decode(message)
545
+ ename, offset = Util.strextract(message, 7)
546
+ return(Rerror.new(ename))
547
+ end
548
+
549
+ end
550
+
551
+ ##
552
+ # Message sent to abort a previous message.
553
+ #
554
+ class Tflush < StyxMessage
555
+ attr_accessor :oldtag
556
+
557
+ ##
558
+ # Construct a new Tflush message given the tag to flush
559
+ #
560
+ # +oldtag+:: the tag to flush
561
+ #
562
+ def initialize(oldtag)
563
+ @length = 9
564
+ @mtype = TFLUSH
565
+ @oldtag = oldtag
566
+ @messagebody = [@oldtag].pack("v")
567
+ @body = sprintf("%s", oldtag)
568
+ end
569
+
570
+ ##
571
+ # Decode a Tflush message. Do not use this method directly.
572
+ # Use only from within StyxMessage.decode.
573
+ #
574
+ # +message+:: [String] raw message string
575
+ # return:: [Tflush] Tflush instance represented by string
576
+ #
577
+ def Tflush.decode(message)
578
+ oldtag = message[7..8].unpack("v")[0]
579
+ if (oldtag.nil?)
580
+ raise RStyx::StyxException.new("invalid Tflush message")
581
+ end
582
+ return(Tflush.new(oldtag))
583
+ end
584
+
585
+ end
586
+
587
+ ##
588
+ # Server response to a Tflush message
589
+ #
590
+ class Rflush < StyxMessage
591
+ ##
592
+ # Make an Rflush message
593
+ #
594
+ def initialize
595
+ @mtype = RFLUSH
596
+ @length = 7
597
+ @messagebody = ""
598
+ @body = ""
599
+ end
600
+
601
+ ##
602
+ # Decode an Rflush message
603
+ #
604
+ # +message+:: [String] raw message string
605
+ # return:: [Rflush] Rflush instance represented by string
606
+ #
607
+ def Rflush.decode(message)
608
+ # This does absolutely nothing! An RFlush is nothing but a
609
+ # bare StyxMessage.
610
+ return(Rflush.new)
611
+ end
612
+ end
613
+
614
+ ##
615
+ # Message sent to attach to the server as a certain user
616
+ #
617
+ class Tattach < StyxMessage
618
+ attr_accessor :fid, :afid, :uname, :aname
619
+
620
+ NOFID = 0xffffffff
621
+
622
+ ##
623
+ # Construct a new Tattach message.
624
+ #
625
+ # +fid+:: [Fixnum] fid for the attach message
626
+ # +uname+:: [String] user name to attach as
627
+ # +afid+:: [Fixnum] authenticated fid from auth protocol
628
+ # +aname+:: [String] file tree to access
629
+ #
630
+ def initialize(fid, uname, afid=NOFID, aname="")
631
+ @fid = fid
632
+ @afid = afid
633
+ @uname = uname
634
+ @aname = aname
635
+ @length = 19 + @uname.length + @aname.length
636
+ @mtype = TATTACH
637
+ @messagebody = [@fid, @afid].pack("VV")
638
+ @messagebody << Util.strpack(uname)
639
+ @messagebody << Util.strpack(aname)
640
+ @body = sprintf("%s, %s, %s, %s", @fid, @afid, @uname, @aname)
641
+ end
642
+
643
+ ##
644
+ # Decode a Tattach message.
645
+ #
646
+ # +message+:: [String] the message to decode
647
+ # return:: [Tattach] the Tattach object
648
+ #
649
+ def Tattach.decode(message)
650
+ fid, afid = message[7..14].unpack("VV")
651
+ if fid.nil? || afid.nil?
652
+ raise RStyx::StyxException.new("invalid Tattach message")
653
+ end
654
+ uname, offset = Util.strextract(message, 15)
655
+ aname, offset = Util.strextract(message, offset)
656
+ return(Tattach.new(fid, uname, afid, aname))
657
+ end
658
+
659
+ end
660
+
661
+ ##
662
+ # Server response to Tattach message, giving qid of root
663
+ #
664
+ class Rattach < StyxMessage
665
+ attr_accessor :qid
666
+
667
+ ##
668
+ # Construct a new Rattach message
669
+ #
670
+ # +qid+:: [Qid] the qid of the root
671
+ #
672
+ def initialize(qid)
673
+ @qid = qid
674
+ @length = 20 # exact length
675
+ @mtype = RATTACH
676
+ @messagebody = @qid.to_bytes
677
+ @body = @qid.to_s
678
+ end
679
+
680
+ ##
681
+ # Decode an Rattach message.
682
+ #
683
+ # +message+:: [String] the message to decode
684
+ # return:: [Rattach] the Rattach object
685
+ #
686
+ def Rattach.decode(message)
687
+ qid = Qid.decode(message[7..19])
688
+ return(Rattach.new(qid))
689
+ end
690
+ end
691
+
692
+ ##
693
+ # Message sent to get a handle to a file on the server
694
+ #
695
+ class Twalk < StyxMessage
696
+ attr_accessor :fid, :newfid, :path, :path_elements
697
+ MAXWELEM = 16 # maximum of sixteen name elements
698
+ ##
699
+ # Construct a new Twalk message given a fid, the newfid to use,
700
+ # and the path on the server, separated by forward slashes ('/').
701
+ #
702
+ # +fid+:: [Fixnum] initial existing fid
703
+ # +newfid+:: [Fixnum] proposed new fid
704
+ # +path+:: [String] new path desired
705
+ #
706
+ def initialize(fid, newfid, path)
707
+ @fid = fid
708
+ @newfid = newfid
709
+ @mtype = TWALK
710
+ @path_elements = []
711
+ @length = 17 # updated by length of path elements
712
+ path_elem_str = ""
713
+ # Cycle through the path elements, adding them to a message string
714
+ path.split("/").each do |elem|
715
+ if elem.length <= 0
716
+ next
717
+ end
718
+ @length += elem.length + 2
719
+ path_elem_str << Util.strpack(elem)
720
+ @path_elements << elem
721
+ end
722
+ if @path_elements.length > MAXWELEM
723
+ raise StyxException.new("path exceeded MAXWELEM (#{MAXWELEM}) elements; break the walk into several messages")
724
+ end
725
+ @messagebody = [@fid, @newfid, @path_elements.length].pack("VVv") + path_elem_str
726
+ @body = sprintf("%s, %s, %s", @fid, @newfid, path)
727
+ end
728
+
729
+ ##
730
+ # Decode a Twalk message.
731
+ #
732
+ # +message+:: [String] the message to decode
733
+ # return:: [Twalk] the Twalk object
734
+ #
735
+ def Twalk.decode(message)
736
+ fid, newfid, nwname = message[7..16].unpack("VVv")
737
+ if fid.nil? || newfid.nil? || nwname.nil?
738
+ raise RStyx::StyxException.new("invalid Twalk message")
739
+ end
740
+ offset = 17
741
+ path_elements = []
742
+ 1.upto(nwname) do
743
+ wname, offset = Util.strextract(message, offset)
744
+ path_elements << wname
745
+ end
746
+ return(Twalk.new(fid, newfid, path_elements.join("/")))
747
+ end
748
+ end
749
+
750
+ ##
751
+ # Server reply to Twalk message
752
+ #
753
+ class Rwalk < StyxMessage
754
+ attr_accessor :qids
755
+
756
+ ##
757
+ # Construct a new Rwalk message, given a list of qid's
758
+ #
759
+ # +qids+:: [Array of Qid] array of Qid's in Rwalk
760
+ #
761
+ def initialize(qids)
762
+ @qids = qids
763
+ @mtype = RWALK
764
+ nwqid = @qids.length
765
+ @length = 9 # updated by qids that follow
766
+ @messagebody = [nwqid].pack("v")
767
+ @body = ""
768
+ i = 1
769
+
770
+ # add each qid into the messagebody
771
+ qids.each do |qid|
772
+ qidbytes = qid.to_bytes
773
+ @messagebody << qidbytes
774
+ @length += Qid::QID_LENGTH
775
+ @body << qid.to_s
776
+ if i < nwqid
777
+ @body << ", "
778
+ end
779
+ i += 1
780
+ end
781
+ @body << sprintf("%s, %s", @qids.length, @body)
782
+ end
783
+
784
+ ##
785
+ # Decode an Rwalk message.
786
+ #
787
+ # +message+:: [String] the message to decode
788
+ # return:: [Rwalk] the Rwalk object
789
+ #
790
+ def Rwalk.decode(message)
791
+ nwqid = message[7..8].unpack("v")[0]
792
+ if nwqid.nil?
793
+ raise RStyx::StyxException.new("invalid Rwalk message")
794
+ end
795
+ offset = 9
796
+ qids = []
797
+ 1.upto(nwqid) do |i|
798
+ qids << Qid.decode(message[offset..(offset + Qid::QID_LENGTH-1)])
799
+ offset += Qid::QID_LENGTH
800
+ end
801
+ return(Rwalk.new(qids))
802
+ end
803
+
804
+ end
805
+
806
+ ##
807
+ # Message sent to prepare an existing file for reading or writing.
808
+ #
809
+ class Topen < StyxMessage
810
+ attr_accessor :fid, :mode
811
+
812
+ # File access modes in Styx
813
+ OREAD = 0
814
+ OWRITE = 1
815
+ ORDWR = 2
816
+ OEXEC = 3
817
+ OTRUNC = 0x10
818
+ ORCLOSE = 0x40
819
+
820
+ ##
821
+ # Construct a new Topen message, given the fid and the mode
822
+ #
823
+ # +fid+:: [Fixnum] the fid to open
824
+ # +mode+:: [Fixnum] the mode
825
+ #
826
+ def initialize(fid, mode)
827
+ @length = 12
828
+ @mtype = TOPEN
829
+ @fid = fid
830
+ @mode = mode
831
+ # test the modes
832
+ if (@mode & 0xf > 3)
833
+ raise RStyx::StyxException.new("invalid file mode #{sprintf("%02x", @mode)}")
834
+ end
835
+
836
+ if (@mode & ~0x5f != 0)
837
+ raise RStyx::StyxException.new("invalid file mode #{sprintf("%02x", @mode)}")
838
+ end
839
+
840
+ if ((@mode & OTRUNC) != 0) && !((@mode & 0xf == OWRITE) ||
841
+ (@mode & 0xf == ORDWR))
842
+ raise RStyx::StyxException.new("Can only truncate file when opening in write mode")
843
+ end
844
+
845
+ @messagebody = [@fid, @mode].pack("VC")
846
+ @body = sprintf("%s, %s", @fid, @mode)
847
+ end
848
+
849
+ ##
850
+ # Decode a Topen message.
851
+ #
852
+ # +message+:: [String] the message to decode
853
+ # return:: [Topen] the Topen object
854
+ #
855
+ def Topen.decode(message)
856
+ fid, mode = message[7..11].unpack("VC")
857
+ if fid.nil? || mode.nil?
858
+ raise RStyx::StyxException.new("invalid Topen message")
859
+ end
860
+ return(Topen.new(fid, mode))
861
+ end
862
+
863
+ end
864
+
865
+ ##
866
+ # Server reply to Topen message
867
+ #
868
+ class Ropen < StyxMessage
869
+ attr_accessor :qid, :iounit
870
+
871
+ ##
872
+ # Construct a new Ropen message
873
+ #
874
+ # +qid+:: [Qid] Qid for the opened file
875
+ # +iounit+:: [Fixnum] the maximum number of bytes guaranteed to be
876
+ # read or written to the file without breaking the
877
+ # I/O transfer into multiple messages (0=no limit).
878
+ #
879
+ def initialize(qid, iounit=0)
880
+ @length = 24
881
+ @mtype = ROPEN
882
+ @qid = qid
883
+ @iounit = iounit
884
+ @messagebody = @qid.to_bytes + [@iounit].pack("V")
885
+ @body = @qid.to_s + ", " + sprintf("%s", @iounit)
886
+ end
887
+
888
+ ##
889
+ # Decode an Ropen message.
890
+ #
891
+ # +message+:: [String] the message to decode
892
+ # return:: [Ropen] the Ropen object
893
+ #
894
+ def Ropen.decode(message)
895
+ qid = Qid.decode(message[7..19])
896
+ iounit = message[20..23].unpack("V")[0]
897
+ if iounit.nil?
898
+ raise RStyx::StyxException.new("invalid Ropen message")
899
+ end
900
+ return(Ropen.new(qid, iounit))
901
+ end
902
+
903
+ end
904
+
905
+ ##
906
+ # Message sent to create a new file on the server.
907
+ #
908
+ class Tcreate < Topen
909
+ attr_accessor :name, :perm
910
+
911
+ DMDIR = 0x80000000 # logical-OR with perm to make directory
912
+ DMAPPEND = 0x40000000 # logical-OR with perm to make appendonly
913
+ DMEXCL = 0x20000000 # logical-OR with perm to make exclusive use
914
+
915
+ ##
916
+ # Construct a new Tcreate message, given the fid, name, permissions,
917
+ # and mode
918
+ #
919
+ # +fid+:: [Fixnum] the fid to open
920
+ # +name+:: [String] the name of the file to create
921
+ # +permissions+:: [Fixnum] the permissions for the new file
922
+ # +mode+:: [Fixnum] the mode (see Topen)
923
+ #
924
+ def initialize(fid, name, mode, perm=nil)
925
+ super(fid, mode)
926
+ @mtype = TCREATE
927
+ @name = name
928
+ @length = 18 + @name.length
929
+ # use 0644 as default permissions if no permissions mask specified
930
+ @perm = (perm.nil?) ? 0644 : perm
931
+ @messagebody = [@fid].pack("V") + Util.strpack(@name) +
932
+ [@perm, @mode].pack("VC")
933
+ @body = sprintf("%s, %s, %s, %s", @fid, @name, @perm, @mode)
934
+ end
935
+
936
+ ##
937
+ # Decode a Tcreate message.
938
+ #
939
+ # +message+:: [String] the message to decode
940
+ # return:: [Tcreate] the Tcreate object
941
+ #
942
+ def Tcreate.decode(message)
943
+ fid = message[7..10].unpack("V")[0]
944
+ if (fid.nil?)
945
+ raise RStyx::StyxException.new("invalid Tcreate message")
946
+ end
947
+ name, offset = Util.strextract(message, 11)
948
+ perm, mode = message[offset..(offset+5)].unpack("VC")
949
+ if (perm.nil? || mode.nil?)
950
+ raise RStyx::StyxException.new("invalid Tcreate message")
951
+ end
952
+ return(Tcreate.new(fid, name, mode, perm))
953
+ end
954
+
955
+ end
956
+
957
+ ##
958
+ # Server reply to Tcreate message. Except for the message tag, it
959
+ # is identical to the Ropen message, and is decoded the same way.
960
+ #
961
+ class Rcreate < Ropen
962
+ ##
963
+ # Construct a new Rcreate message
964
+ #
965
+ # +qid+:: [Qid] Qid for the opened file
966
+ # +iounit+:: [Fixnum] the maximum number of bytes guaranteed to be
967
+ # read or written to the file without breaking the
968
+ # I/O transfer into multiple messages (0=no limit).
969
+ #
970
+ def initialize(qid, iounit=0)
971
+ super(qid, iounit)
972
+ @mtype = RCREATE
973
+ end
974
+
975
+ ##
976
+ # Decode an Rcreate message.
977
+ #
978
+ # +message+:: [String] the message to decode
979
+ # return:: [Ropen] the Rcreate object
980
+ #
981
+ # FIXME: this is identical to Ropen.decode with the only difference
982
+ # being the Rcreate.new call at return. How do you obtain
983
+ # the defining class for a static method?
984
+ #
985
+ def Rcreate.decode(message)
986
+ qid = Qid.decode(message[7..19])
987
+ iounit = message[20..23].unpack("V")[0]
988
+ if iounit.nil?
989
+ raise RStyx::StyxException.new("invalid Ropen message")
990
+ end
991
+ return(Rcreate.new(qid, iounit))
992
+ end
993
+
994
+ end
995
+
996
+ ##
997
+ # Message sent to read data from a file given fid, offset, and length
998
+ # of data to be read.
999
+ #
1000
+ class Tread < StyxMessage
1001
+ attr_accessor :fid, :offset, :count
1002
+
1003
+ ##
1004
+ # Construct a new Tread message.
1005
+ #
1006
+ # +fid+:: [Fixnum] fid of file to read from
1007
+ # +offset+:: [Fixnum] offset to read from
1008
+ # +count+:: [Fixnum] number of bytes to read
1009
+ #
1010
+ def initialize(fid, offset, count)
1011
+ @length = 23
1012
+ @mtype = TREAD
1013
+ @fid = fid
1014
+ @offset = offset
1015
+ offsetlo = @offset & 0xffffffff
1016
+ offsethi = (@offset >> 32) & 0xffffffff
1017
+ @count = count
1018
+ @messagebody = [@fid, offsetlo, offsethi, @count].pack("VVVV")
1019
+ @body = sprintf("%s, %s, %s", @fid, @offset, @count)
1020
+ end
1021
+
1022
+ ##
1023
+ # Decode a Tread message.
1024
+ #
1025
+ # +message+:: [String] the message to decode
1026
+ # return:: [Tread] the Tread object
1027
+ #
1028
+ def Tread.decode(message)
1029
+ fid, offsetlo, offsethi, count = message[7..22].unpack("VVVV")
1030
+ if fid.nil? || offsetlo.nil? || offsethi.nil? || count.nil?
1031
+ raise StyxException.new("invalid Tread message")
1032
+ end
1033
+ offset = offsetlo | (offsethi << 32)
1034
+ return(Tread.new(fid, offset, count))
1035
+ end
1036
+
1037
+ end
1038
+
1039
+ ##
1040
+ # Server response to Tread message giving the data from the read.
1041
+ #
1042
+ class Rread < StyxMessage
1043
+ attr_accessor :count, :data
1044
+
1045
+ ##
1046
+ # Construct a new Rread message.
1047
+ #
1048
+ # +data+:: [String] the data read from the file
1049
+ #
1050
+ def initialize(data)
1051
+ @count = data.length
1052
+ @data = data
1053
+ @length = 11 + @count
1054
+ @mtype = RREAD
1055
+ @messagebody = [data.length].pack("V") + data
1056
+ # put only the first twenty bytes in the description
1057
+ @body = sprintf("%s, %s", @count, @data[0..19])
1058
+ end
1059
+
1060
+ ##
1061
+ # Decode an Rread message.
1062
+ #
1063
+ # +message+:: [String] the message to decode
1064
+ # return:: [Rread] the Rread object
1065
+ #
1066
+ def Rread.decode(message)
1067
+ count = message[7..10].unpack("V")[0]
1068
+ if (count.nil?)
1069
+ raise StyxException.new("invalid Rread message")
1070
+ end
1071
+ data = message.slice(11, count)
1072
+ if (data.length != count)
1073
+ raise StyxException.new("invalid Rread message, count and data length do not coincide")
1074
+ end
1075
+ return(Rread.new(data))
1076
+ end
1077
+
1078
+ end
1079
+
1080
+ ##
1081
+ # Message sent to write data to a file given fid, offset, and
1082
+ # the data to be written.
1083
+ #
1084
+ class Twrite < StyxMessage
1085
+ attr_accessor :fid, :offset, :data, :count
1086
+
1087
+ ##
1088
+ # Construct a new Twrite message.
1089
+ #
1090
+ # +fid+:: [Fixnum] fid of file to read from
1091
+ # +offset+:: [Fixnum] offset to read from
1092
+ # +data+:: [String] data to be written
1093
+ #
1094
+ def initialize(fid, offset, data)
1095
+ @count = data.length
1096
+ @data = data
1097
+ @length = @count + 23
1098
+ @mtype = TWRITE
1099
+ @fid = fid
1100
+ @offset = offset
1101
+ offsetlo = @offset & 0xffffffff
1102
+ offsethi = (@offset >> 32) & 0xffffffff
1103
+ @messagebody = [@fid, offsetlo, offsethi, @count].pack("VVVV") +
1104
+ @data
1105
+ @body = sprintf("%s, %s, %s, %s", @fid, @offset, @count, @data[0..19])
1106
+ end
1107
+
1108
+ ##
1109
+ # Decode a Twrite message.
1110
+ #
1111
+ # +message+:: [String] the message to decode
1112
+ # return:: [Rwrite] the Twrite object
1113
+ #
1114
+ def Twrite.decode(message)
1115
+ fid, offsetlo, offsethi, count = message[7..22].unpack("VVVV")
1116
+ if (fid.nil? || offsetlo.nil? || offsethi.nil? || count.nil?)
1117
+ raise StyxException.new("invalid Twrite message")
1118
+ end
1119
+ offset = offsetlo | (offsethi << 32)
1120
+ data = message.slice(23, count)
1121
+ if (data.length != count)
1122
+ raise StyxException.new("invalid Twrite message, count and data length do not coincide")
1123
+ end
1124
+ return(Twrite.new(fid, offset, data))
1125
+ end
1126
+
1127
+ end
1128
+
1129
+ ##
1130
+ # Server response to Twrite message.
1131
+ #
1132
+ class Rwrite < StyxMessage
1133
+ attr_accessor :count
1134
+
1135
+ ##
1136
+ # Construct a new Rwrite message.
1137
+ #
1138
+ # +count+:: [Fixnum] the number of bytes actually written.
1139
+ #
1140
+ def initialize(count)
1141
+ @count = count
1142
+ @length = 11
1143
+ @mtype = RWRITE
1144
+ @messagebody = [@count].pack("V")
1145
+ @body = sprintf("%s", @count)
1146
+ end
1147
+
1148
+ ##
1149
+ # Decode an Rwrite message.
1150
+ #
1151
+ # +message+:: [String] the message to decode
1152
+ # return:: [Rwrite] the Rwrite object
1153
+ #
1154
+ def Rwrite.decode(message)
1155
+ count = message[7..10].unpack("V")[0]
1156
+ if count.nil?
1157
+ raise StyxException.new("invalid Rwrite message")
1158
+ end
1159
+ return(Rwrite.new(count))
1160
+ end
1161
+
1162
+ end
1163
+
1164
+ ##
1165
+ # Message sent to forget about a fid, closing a file.
1166
+ #
1167
+ class Tclunk < StyxMessage
1168
+ attr_accessor :fid
1169
+
1170
+ ##
1171
+ # Construct a new Tclunk message given the fid to clunk.
1172
+ #
1173
+ # +fid+:: The fid to clunk
1174
+ #
1175
+ def initialize(fid)
1176
+ @length = 11
1177
+ @mtype = TCLUNK
1178
+ @fid = fid
1179
+ @messagebody = [@fid].pack("V")
1180
+ @body = sprintf("%s", fid)
1181
+ end
1182
+
1183
+ ##
1184
+ # Decode a Tclunk message. Do not use this method directly.
1185
+ # Use only from within StyxMessage.decode.
1186
+ #
1187
+ # +message+:: [String] raw message string
1188
+ # return:: [Tclunk] Tclunk instance represented by string
1189
+ #
1190
+ def Tclunk.decode(message)
1191
+ fid = message[7..10].unpack("V")[0]
1192
+ if (fid.nil?)
1193
+ raise RStyx::StyxException.new("invalid Tclunk message")
1194
+ end
1195
+ return(Tclunk.new(fid))
1196
+ end
1197
+
1198
+ end
1199
+
1200
+ ##
1201
+ # Server response to a Tclunk message
1202
+ #
1203
+ class Rclunk < StyxMessage
1204
+ ##
1205
+ # Make an Rclunk message
1206
+ #
1207
+ def initialize
1208
+ @mtype = RCLUNK
1209
+ @length = 7
1210
+ @messagebody = ""
1211
+ @body = ""
1212
+ end
1213
+
1214
+ ##
1215
+ # Decode an Rclunk message
1216
+ #
1217
+ # +message+:: [String] raw message string
1218
+ # return:: [Rclunk] Rclunk instance represented by string
1219
+ #
1220
+ def Rclunk.decode(message)
1221
+ # This does absolutely nothing! An Rclunk is nothing but a
1222
+ # bare StyxMessage.
1223
+ return(Rclunk.new)
1224
+ end
1225
+
1226
+ end
1227
+
1228
+ ##
1229
+ # Message sent to delete a file, given its fid.
1230
+ #
1231
+ class Tremove < StyxMessage
1232
+ attr_accessor :fid
1233
+
1234
+ ##
1235
+ # Construct a new Tremove message given the fid to clunk.
1236
+ #
1237
+ # +fid+:: The fid to remove
1238
+ #
1239
+ def initialize(fid)
1240
+ @length = 11
1241
+ @mtype = TREMOVE
1242
+ @fid = fid
1243
+ @messagebody = [@fid].pack("V")
1244
+ @body = sprintf("%s", fid)
1245
+ end
1246
+
1247
+ ##
1248
+ # Decode a Tremove message. Do not use this method directly.
1249
+ # Use only from within StyxMessage.decode.
1250
+ #
1251
+ # +message+:: [String] raw message string
1252
+ # return:: [Tremove] Tremove instance represented by string
1253
+ #
1254
+ def Tremove.decode(message)
1255
+ fid = message[7..10].unpack("V")[0]
1256
+ if (fid.nil?)
1257
+ raise RStyx::StyxException.new("invalid Tremove message")
1258
+ end
1259
+ return(Tremove.new(fid))
1260
+ end
1261
+
1262
+ end
1263
+
1264
+ ##
1265
+ # Server response to a Tremove message
1266
+ #
1267
+ class Rremove < StyxMessage
1268
+ ##
1269
+ # Make an Rremove message
1270
+ #
1271
+ def initialize
1272
+ @mtype = RREMOVE
1273
+ @length = 7
1274
+ @messagebody = ""
1275
+ @body = ""
1276
+ end
1277
+
1278
+ ##
1279
+ # Decode an Rremove message
1280
+ #
1281
+ # +message+:: [String] raw message string
1282
+ # return:: [Rremove] Rremove instance represented by string
1283
+ #
1284
+ def Rremove.decode(message)
1285
+ # This does absolutely nothing! An Rremove is nothing but a
1286
+ # bare StyxMessage.
1287
+ return(Rremove.new)
1288
+ end
1289
+
1290
+ end
1291
+
1292
+ ##
1293
+ # Message sent to obtain information about a file given its fid.
1294
+ #
1295
+ class Tstat < StyxMessage
1296
+ attr_accessor :fid
1297
+
1298
+ ##
1299
+ # Construct a new Tstat message given the fid to stat.
1300
+ #
1301
+ # +fid+:: The fid to stat
1302
+ #
1303
+ def initialize(fid)
1304
+ @length = 11
1305
+ @mtype = TSTAT
1306
+ @fid = fid
1307
+ @messagebody = [@fid].pack("V")
1308
+ @body = sprintf("%s", fid)
1309
+ end
1310
+
1311
+ ##
1312
+ # Decode a Tstat message. Do not use this method directly.
1313
+ # Use only from within StyxMessage.decode.
1314
+ #
1315
+ # message:: [String] raw message string
1316
+ # return:: [Tstat] Tstat instance represented by string
1317
+ #
1318
+ def Tstat.decode(message)
1319
+ fid = message[7..10].unpack("V")[0]
1320
+ if (fid.nil?)
1321
+ raise RStyx::StyxException.new("invalid Tclunk message")
1322
+ end
1323
+ return(Tstat.new(fid))
1324
+ end
1325
+
1326
+ end
1327
+
1328
+ ##
1329
+ # Server response to a Tstat message.
1330
+ #
1331
+ class Rstat < StyxMessage
1332
+ attr_accessor :stat
1333
+
1334
+ ##
1335
+ # Make an Rstat message given a DirEntry.
1336
+ #
1337
+ # +stat+:: [DirEntry] the file status information
1338
+ #
1339
+ def initialize(stat)
1340
+ @stat = stat
1341
+ @mtype = RSTAT
1342
+ @messagebody = @stat.to_bytes
1343
+ @length = 7 + @messagebody.length
1344
+ @body = sprintf("%s", @stat)
1345
+ end
1346
+
1347
+ ##
1348
+ # Decode an Rstat message. Do not use this method directly.
1349
+ # Use only from within StyxMessage.decode.
1350
+ #
1351
+ # +message+:: [String] raw message string
1352
+ # return:: [Tclunk] Tclunk instance represented by string
1353
+ #
1354
+ def Rstat.decode(message)
1355
+ len = message[0..3].unpack("V")[0]
1356
+ stat = DirEntry.decode(message[7..len])
1357
+ return(Rstat.new(stat))
1358
+ end
1359
+
1360
+ end
1361
+
1362
+ ##
1363
+ # Message sent to update the status of a file given a fid and a DirEntry
1364
+ #
1365
+ class Twstat < StyxMessage
1366
+ attr_accessor :fid, :stat
1367
+
1368
+ ##
1369
+ # Make a Twstat message given a fid and a DirEntry.
1370
+ #
1371
+ # +fid+:: [Fixnum] the fid of the file to change
1372
+ # +stat+:: [DirEntry] the new file status information
1373
+ #
1374
+ def initialize(fid, stat)
1375
+ @fid = fid
1376
+ @stat = stat
1377
+ @mtype = TWSTAT
1378
+ @messagebody = [@fid].pack("V") + @stat.to_bytes
1379
+ @length = 7 + @messagebody.length
1380
+ @body = sprintf("%s, %s", @fid, @stat)
1381
+ end
1382
+
1383
+ ##
1384
+ # Decode a Twstat message. Do not use this method directly.
1385
+ # Use only from within StyxMessage.decode.
1386
+ #
1387
+ # +message+:: [String] raw message string
1388
+ # return:: [Twstat] Twstat instance represented by string
1389
+ #
1390
+ def Twstat.decode(message)
1391
+ len = message[0..3].unpack("V")[0]
1392
+ fid = message[7..10].unpack("V")[0]
1393
+ stat = DirEntry.decode(message[11..len])
1394
+ return(Twstat.new(fid, stat))
1395
+ end
1396
+
1397
+ end
1398
+
1399
+ ##
1400
+ # Server response to a Twstat message
1401
+ #
1402
+ class Rwstat < StyxMessage
1403
+ ##
1404
+ # Make an Rwstat message
1405
+ #
1406
+ def initialize
1407
+ @mtype = RWSTAT
1408
+ @length = 7
1409
+ @messagebody = ""
1410
+ @body = ""
1411
+ end
1412
+
1413
+ ##
1414
+ # Decode an Rwstat message
1415
+ #
1416
+ # +message+:: [String] raw message string
1417
+ # return:: [Rwstat] Rwstat instance represented by string
1418
+ #
1419
+ def Rwstat.decode(message)
1420
+ # This does absolutely nothing! An Rwstat is nothing but a
1421
+ # bare StyxMessage.
1422
+ return(Rwstat.new)
1423
+ end
1424
+
1425
+ end
1426
+
1427
+ end # module Message
1428
+
1429
+ end # module RStyx