libsl 0.0.2

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.
data/lib/types.rb ADDED
@@ -0,0 +1,490 @@
1
+
2
+ module LibSL
3
+ class LLPacketNumber
4
+ def self.decode(data)
5
+ data = data.unpack('C1a*')
6
+ num = data.shift()
7
+ unless num < 0xff then
8
+ data = data[0].unpack('C1a*')
9
+ num = (num << 8) + data.shift()
10
+ end
11
+ unless num < 0xffff then
12
+ data = data[0].unpack('C2a*')
13
+ num = (num << 16) + (data.shift() << 8) + data.shift()
14
+ end
15
+ if [*data].length > 0 then
16
+ data = data[0]
17
+ else
18
+ data = nil
19
+ end
20
+ return self.new(num), data
21
+ end
22
+
23
+ def encode()
24
+ case @frequency
25
+ when :high then data = [@number].pack('C')
26
+ when :medium then data = [@number].pack('n')
27
+ else data = [@number].pack('N')
28
+ end
29
+ return data
30
+ end
31
+
32
+ attr_accessor :number, :frequency
33
+ def initialize(number)
34
+ @number = number
35
+ if @number < 0xff then
36
+ @frequency = :high
37
+ elsif @number < 0xffff then
38
+ @frequency = :medium
39
+ elsif @number < 0xfffffffa then
40
+ @frequency = :low
41
+ else
42
+ @frequency = :fixed
43
+ end
44
+ end
45
+ end
46
+
47
+ class LLSequenceNumber
48
+ def self.decode(data)
49
+ data = data.unpack('N1a*')
50
+ return self.new(data[0]), data[1]
51
+ end
52
+
53
+ def encode()
54
+ [@number].pack('N')
55
+ end
56
+
57
+ attr_accessor :number
58
+ def initialize(num)
59
+ @number = num
60
+ end
61
+
62
+ def inc()
63
+ @number += 1
64
+ @number
65
+ end
66
+ end
67
+
68
+ class LLNull
69
+ def self.decode(data)
70
+ return self.new, data
71
+ end
72
+
73
+ def encode()
74
+ ""
75
+ end
76
+
77
+ def initialize(val)
78
+ end
79
+ end
80
+
81
+ class LLFixed
82
+ def self.decode(data, len)
83
+ data = data.unpack("a#{len}a*")
84
+ return self.new(data[0]), data[1]
85
+ end
86
+
87
+ def encode()
88
+ @data
89
+ end
90
+
91
+ attr_accessor :data
92
+ def initialize(data)
93
+ @data = data
94
+ end
95
+ end
96
+
97
+ class LLVariable1
98
+ def self.decode(data)
99
+ data = data.unpack('C1a*')
100
+ data = data[1].unpack("a#{data[0]}a*")
101
+ return self.new(data[0]), data[1]
102
+ end
103
+
104
+ def encode()
105
+ [@data.length, @data].pack('C1a*')
106
+ end
107
+
108
+ attr_accessor :data
109
+ def initialize(data)
110
+ @data = data
111
+ end
112
+ end
113
+
114
+ class LLVariable2 < LLVariable1
115
+ def self.decode(data)
116
+ data = data.unpack('n1a*')
117
+ data = data[1].unpack("a#{data[0]}a*")
118
+ return self.new(data[0]), data[1]
119
+ end
120
+
121
+ def encode()
122
+ [@data.length, @data].pack('n1a*')
123
+ end
124
+ end
125
+
126
+ class LLNumber
127
+ def self.decode(data, pattern, klass)
128
+ data = data.unpack(pattern)
129
+ return klass.new(data[0]), data[1]
130
+ end
131
+
132
+ def self.endianness()
133
+ little = "78563412"
134
+ big = "12345678"
135
+ case "%8x" % ([0x12345678].pack('l').unpack('N')[0])
136
+ when little then :little
137
+ when big then :big
138
+ else :other
139
+ end
140
+ end
141
+
142
+ def encode(pattern)
143
+ [@value].pack(pattern)
144
+ end
145
+
146
+ attr_accessor :value
147
+ def initialize(value)
148
+ @value = value
149
+ end
150
+ end
151
+
152
+ class LLU8 < LLNumber
153
+ def self.decode(data)
154
+ LLNumber.decode(data, 'C1a*', LLU8)
155
+ end
156
+
157
+ def encode()
158
+ raise ArgumentError, "Value out of range: #{@value}" if @value < 0 or @value > 0xff
159
+ super('C')
160
+ end
161
+ end
162
+
163
+ class LLU16 < LLNumber
164
+ def self.decode(data)
165
+ LLNumber.decode(data, 'v1a*', LLU16)
166
+ end
167
+
168
+ def encode()
169
+ raise ArgumentError, "Value out of range: #{@value}" if @value < 0 or @value > 0xffff
170
+ super('v1')
171
+ end
172
+ end
173
+
174
+ class LLU32 < LLNumber
175
+ def self.decode(data)
176
+ LLNumber.decode(data, 'V1a*', LLU32)
177
+ end
178
+
179
+ def encode()
180
+ raise ArgumentError, "Value out of range: #{@value}" if @value < 0 or @value > 0xffffffff
181
+ super('V1')
182
+ end
183
+ end
184
+
185
+ class LLU64 < LLNumber
186
+ def self.decode(data)
187
+ u64, data = LLNumber.decode(data, 'Q1a*', LLU64)
188
+ if self.endianness == :big
189
+ # Convert to little endian
190
+ u64.value = u64.value >> 56 |
191
+ (u64.value & 0xff000000000000) >> 40 |
192
+ (u64.value & 0xff0000000000) >> 24 |
193
+ (u64.value & 0xff00000000) >> 8 |
194
+ (u64.value & 0xff000000) << 8 |
195
+ (u64.value & 0xff0000) << 24 |
196
+ (u64.value & 0xff00) << 40 |
197
+ (u64.value & 0xff) << 56
198
+ end
199
+ return u64, data
200
+ end
201
+
202
+ def encode()
203
+ raise ArgumentError, "Value out of range: #{@value}" if @value < 0 or @value > 0xffffffffffffffff
204
+ ms = @value >> 32
205
+ ls = @value - (ms << 32)
206
+ [ls, ms].pack('V2')
207
+ end
208
+ end
209
+
210
+ class LLS8 < LLNumber
211
+ def self.decode(data)
212
+ LLNumber.decode(data, 'c1a*', LLS8)
213
+ end
214
+
215
+ def encode()
216
+ raise ArgumentError, "Value out of range: #{@value}" if @value < -0x80 or @value > 0x7f
217
+ super('c1')
218
+ end
219
+ end
220
+
221
+ class LLS16 < LLNumber
222
+ def self.decode(data)
223
+ s16, data = LLNumber.decode(data, 's1a*', LLS16)
224
+ if self.endianness == :big
225
+ # Convert to little endian
226
+ ms = s16.value >> 8
227
+ ls = s16 - (ms << 8)
228
+ s16.value = (ls << 8) + ms
229
+ end
230
+ return s16, data
231
+ end
232
+
233
+ def encode()
234
+ raise ArgumentError, "Value out of range: #{@value}" if @value < -0x8000 or @value > 0x7fff
235
+ data = super('s')
236
+ if LLNumber::endianness == :big
237
+ data = data.unpack('C2').reverse.pack('C2')
238
+ end
239
+ return data
240
+ end
241
+ end
242
+
243
+ class LLS32 < LLNumber
244
+ def self.decode(data)
245
+ s32, data = LLNumber.decode(data, 'l1a*', LLS32)
246
+ if self.endianness == :big
247
+ # Convert to little endian
248
+ s32.value = s32.value >> 24 |
249
+ (s32.value & 0xff0000) >> 8 |
250
+ (s32.value & 0xff00) << 8 |
251
+ (s32.value & 0xff) << 24
252
+ end
253
+ return s32, data
254
+ end
255
+
256
+ def encode()
257
+ raise ArgumentError, "Value out of range: #{@value}" if @value < -0x80000000 or @value > 0x7fffffff
258
+ data = super('l')
259
+ if LLNumber::endianness == :big
260
+ data = data.unpack('C4').reverse.pack('C4')
261
+ end
262
+ return data
263
+ end
264
+ end
265
+
266
+ class LLS64 < LLNumber
267
+ def self.decode(data)
268
+ s64, data = LLNumber.decode(data, 'q1a*', LLS64)
269
+ if self.endianness == :big
270
+ # Convert to little endian
271
+ s64.value = s64.value >> 56 |
272
+ (s64.value & 0xff000000000000) >> 40 |
273
+ (s64.value & 0xff0000000000) >> 24 |
274
+ (s64.value & 0xff00000000) >> 8 |
275
+ (s64.value & 0xff000000) << 8 |
276
+ (s64.value & 0xff0000) << 24 |
277
+ (s64.value & 0xff00) << 40 |
278
+ (s64.value & 0xff) << 56
279
+ end
280
+ return s64, data
281
+ end
282
+
283
+ def encode()
284
+ raise ArgumentError, "Value out of range: #{@value}" if @value < -0x8000000000000000 or @value > 0x7fffffffffffffff
285
+ data = super('q')
286
+ if LLNumber::endianness == :big
287
+ data = data.unpack('C8').reverse.pack('C8')
288
+ end
289
+ return data
290
+ end
291
+ end
292
+
293
+ class LLF32 < LLNumber
294
+ def self.decode(data)
295
+ data = data.unpack('ea*')
296
+ return self.new(data[0]), data[1]
297
+ end
298
+
299
+ def encode()
300
+ [@value].pack('e')
301
+ end
302
+ end
303
+
304
+ class LLF64 < LLNumber
305
+ def self.decode(data)
306
+ data = data.unpack('Ea*')
307
+ return self.new(data[0]), data[1]
308
+ end
309
+
310
+ def encode()
311
+ [@value].pack('E')
312
+ end
313
+ end
314
+
315
+ class LLVector3
316
+ def self.decode(data)
317
+ data = data.unpack('e3a*')
318
+ return self.new(data[0], data[1], data[2]), data[3]
319
+ end
320
+
321
+ def encode()
322
+ [@x, @y, @z].pack('e3')
323
+ end
324
+
325
+ attr_accessor :x, :y, :z
326
+ def initialize(x, y, z)
327
+ @x, @y, @z = x, y, z
328
+ end
329
+ end
330
+
331
+ class LLVector3d < LLVector3
332
+ def self.decode(data)
333
+ data = data.unpack('E3a*')
334
+ return self.new(data[0], data[1], data[2]), data[3]
335
+ end
336
+
337
+ def encode()
338
+ [@x, @y, @z].pack('E3')
339
+ end
340
+ end
341
+
342
+ class LLVector4
343
+ def self.decode(data)
344
+ data = data.unpack('e4a*')
345
+ return self.new(data[0], data[1], data[2], data[3]), data[4]
346
+ end
347
+
348
+ def encode()
349
+ [@a, @b, @c, @d].pack('e4')
350
+ end
351
+
352
+ attr_accessor :a, :b, :c, :d
353
+ def initialize(a, b, c, d)
354
+ @a, @b, @c, @d = a, b, c, d
355
+ end
356
+ end
357
+
358
+ class LLQuaternion
359
+ def self.decode(data)
360
+ data = data.unpack 'e3a*'
361
+ return self.new(data[0], data[1], data[2]), data[3]
362
+ end
363
+
364
+ def encode()
365
+ [@x, @y, @z].pack 'e3'
366
+ end
367
+
368
+ attr_accessor :x, :y, :z, :w
369
+ def initialize(x, y, z, w=nil)
370
+ @x, @y, @z = x, y, z
371
+ if w.nil? then
372
+ sum = 1 - @x**2 - @y**2 - @z**2
373
+ @w = if sum > 0 then Math.sqrt sum else 0 end
374
+ else
375
+ @w = w
376
+ end
377
+ end
378
+ end
379
+
380
+ class LLUUID
381
+ attr_reader :uuid
382
+
383
+ def self.decode(data)
384
+ data = data.unpack 'a16a*'
385
+ return self.new(data[0]), data[1]
386
+ end
387
+
388
+ def self.null()
389
+ return self.new("00000000-0000-0000-0000-000000000000")
390
+ end
391
+
392
+ def encode()
393
+ to_bytes
394
+ end
395
+
396
+ def initialize(uuid=nil)
397
+ if uuid.nil?
398
+ # Generate a random UUID
399
+ uuid = [
400
+ rand(0x100000000),
401
+ rand(0x100000000),
402
+ rand(0x100000000),
403
+ rand(0x100000000),
404
+ ].pack "N4"
405
+ uuid[6] &= 0b00001111
406
+ uuid[6] |= 48
407
+ uuid[8] &= 0b00111111
408
+ uuid[8] |= 0b10000000
409
+ end
410
+
411
+ raise "Invalid uuid format" unless uuid.is_a? String
412
+ @uuid = if uuid.length == 36 and uuid.count("-") == 4 then
413
+ # String representation (Guid)
414
+ uuid.delete("-").scan(/../).map{|s| s.to_i(16)}
415
+ else
416
+ # Unpack from binary string
417
+ uuid.unpack('C16')
418
+ end
419
+ end
420
+
421
+ def to_s()
422
+ sprintf('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', *(@uuid)).upcase
423
+ end
424
+
425
+ def to_bytes()
426
+ @uuid.pack('C16')
427
+ end
428
+ end
429
+
430
+ class LLBool
431
+ def self.decode(data)
432
+ data = data.unpack('C1a*')
433
+ return self.new(data[0] == 1), data[1]
434
+ end
435
+
436
+ def encode()
437
+ [@value ? 1 : 0].pack('C')
438
+ end
439
+
440
+ def eql?(val)
441
+ @value == val
442
+ end
443
+
444
+ def ==(val)
445
+ @value == val
446
+ end
447
+
448
+ attr_accessor :value
449
+ def initialize(value)
450
+ @value = value
451
+ end
452
+ end
453
+
454
+ class LLIPAddress
455
+ def self.decode(data)
456
+ data = data.unpack('C4a*')
457
+ return self.new(sprintf("%d.%d.%d.%d", data[0], data[1], data[2], data[3])), data[4]
458
+ end
459
+
460
+ def encode()
461
+ @ip.scan(/[0-9]+/).map {|s| s.to_i}.pack('C4')
462
+ end
463
+
464
+ attr_accessor :ip
465
+ def initialize(ip)
466
+ raise ArgumentError, "ip must be a string (e.g. 1.2.3.4)" unless ip.is_a? String
467
+ @ip = ip
468
+ end
469
+
470
+ def to_s()
471
+ @ip.to_s
472
+ end
473
+ end
474
+
475
+ class LLIPPort
476
+ def self.decode(data)
477
+ data = data.unpack('n1a*')
478
+ return self.new(data[0]), data[1]
479
+ end
480
+
481
+ def encode()
482
+ [@port].pack('n1')
483
+ end
484
+
485
+ attr_accessor :port
486
+ def initialize(port)
487
+ @port = port
488
+ end
489
+ end
490
+ end
data/libsl.gemspec ADDED
@@ -0,0 +1,30 @@
1
+
2
+ Gem::Specification.new do |s|
3
+ s.name = "libsl"
4
+ s.version = File.read('VERSION')
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = ["Lukas Botsch"]
7
+ s.email = ["lukas.botsch@gmail.com"]
8
+ s.homepage = "http://github.com/lbotsch/libsl-ruby"
9
+ s.summary = "Library for building SecondLife client applications"
10
+ s.description = "Build SecondLife clients using a simple DSL"
11
+ s.rubyforge_project = s.name
12
+
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+
15
+ # If you have runtime dependencies, add them here
16
+ s.add_runtime_dependency "eventmachine", ">= 0.12.10"
17
+
18
+ # If you have development dependencies, add them here
19
+ # s.add_development_dependency "another", "= 0.9"
20
+
21
+ # The list of files to be contained in the gem
22
+ s.files = `git ls-files`.split("\n").delete_if{|f| f =~ /^(example|scripts|\.)\//}
23
+ # s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
24
+ # s.extensions = `git ls-files ext/extconf.rb`.split("\n")
25
+
26
+ s.require_path = 'lib'
27
+
28
+ # For C extensions
29
+ # s.extensions = "ext/extconf.rb"
30
+ end
data/test/tc_packet.rb ADDED
@@ -0,0 +1,99 @@
1
+ require 'test/unit'
2
+ %w{types packet}.each{|f|
3
+ require File.join File.dirname(__FILE__), "..", "lib", f
4
+ }
5
+
6
+ module LibSL
7
+ class StubPacket < Packet
8
+ def self.packet_id()
9
+ 1
10
+ end
11
+
12
+ def build_structure()
13
+ @blocks = [
14
+ [:SomeBlock, Block.new([
15
+ [:Data, :LLU8]
16
+ ])],
17
+ [:FixedCollection, FixedBlockCollection.new(2, [
18
+ [:Data, :LLU8]
19
+ ])],
20
+ [:VariableCollection, VariableBlockCollection.new([
21
+ [:Data, :LLU8]
22
+ ])]
23
+ ]
24
+ end
25
+ end
26
+
27
+ MESSAGE_MAP = {1 => StubPacket}
28
+
29
+ class TestPacket < Test::Unit::TestCase
30
+ def test_decode()
31
+ packet = Packet.decode(
32
+ "\x40" + # Flags
33
+ "\x00\x00\x00\x01" + # Sequence Number
34
+ "\x00" + # Extra header length
35
+ "\x01" + # Message Number
36
+ "\xff" + # SomeBlock
37
+ "\xff\xff" + # FixedCollection
38
+ "\x02\xff\xff") # VariableCollection
39
+ assert_equal(StubPacket, packet.class)
40
+ assert_equal(true, packet.reliable_flag)
41
+ assert_equal(0xff, packet.SomeBlock.Data.value)
42
+ assert_equal(0xff, packet.FixedCollection[0].Data.value)
43
+ assert_equal(0xff, packet.FixedCollection[1].Data.value)
44
+ assert_equal(0xff, packet.VariableCollection[0].Data.value)
45
+ assert_equal(0xff, packet.VariableCollection[1].Data.value)
46
+ end
47
+
48
+ def test_zero_decode()
49
+ packet = Packet.decode(
50
+ "\x80" + # Flags zero_coded
51
+ "\x00\x00\x00\x01" + # Sequence Number
52
+ "\x00" + # Extra header length
53
+ "\x01" + # Message Number
54
+ "\x00\x01" + # SomeBlock
55
+ "\x00\x02" + # FixedCollection
56
+ "\x02\x00\x02") # VariableCollection
57
+ assert_equal(StubPacket, packet.class)
58
+ assert_equal(true, packet.zero_coded_flag)
59
+ assert_equal(0x00, packet.SomeBlock.Data.value)
60
+ assert_equal(0x00, packet.FixedCollection[0].Data.value)
61
+ assert_equal(0x00, packet.FixedCollection[1].Data.value)
62
+ assert_equal(0x00, packet.VariableCollection[0].Data.value)
63
+ assert_equal(0x00, packet.VariableCollection[1].Data.value)
64
+ end
65
+
66
+ def test_encode()
67
+ packet = StubPacket.new
68
+ packet.reliable_flag = true
69
+ packet.sequence_number = 1
70
+ packet.SomeBlock.Data = LLU8.new(0xff)
71
+ packet.FixedCollection[0].Data = LLU8.new(0xff)
72
+ packet.FixedCollection[1].Data = LLU8.new(0xff)
73
+ packet.VariableCollection.add.Data = LLU8.new(0xff)
74
+ packet.VariableCollection.add.Data = LLU8.new(0xff)
75
+
76
+ data = packet.encode
77
+ assert_equal("\x40\x00\x00\x00\x01\x00\x01\xff\xff\xff\x02\xff\xff", data)
78
+ end
79
+
80
+ def test_new()
81
+ packet = StubPacket.new({
82
+ :SomeBlock => { :Data => LLU8.new(0xff) },
83
+ :FixedCollection => [
84
+ { :Data => LLU8.new(0xff) },
85
+ { :Data => LLU8.new(0xff) }
86
+ ],
87
+ :VariableCollection => [
88
+ { :Data => LLU8.new(0xff) },
89
+ { :Data => LLU8.new(0xff) }
90
+ ]
91
+ })
92
+ assert_equal(0xff, packet.SomeBlock.Data.value)
93
+ assert_equal(0xff, packet.FixedCollection[0].Data.value)
94
+ assert_equal(0xff, packet.FixedCollection[1].Data.value)
95
+ assert_equal(0xff, packet.VariableCollection[0].Data.value)
96
+ assert_equal(0xff, packet.VariableCollection[1].Data.value)
97
+ end
98
+ end
99
+ end