datawire_mdk 2.0.5
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.
- checksums.yaml +7 -0
- data/lib/datawire-quark-core.rb +1213 -0
- data/lib/datawire_mdk_md.rb +54752 -0
- data/lib/mdk.rb +962 -0
- data/lib/mdk_discovery.rb +1518 -0
- data/lib/mdk_discovery/protocol.rb +818 -0
- data/lib/mdk_discovery/synapse.rb +267 -0
- data/lib/mdk_introspection.rb +281 -0
- data/lib/mdk_introspection/aws.rb +101 -0
- data/lib/mdk_introspection/kubernetes.rb +125 -0
- data/lib/mdk_protocol.rb +1255 -0
- data/lib/mdk_runtime.rb +2135 -0
- data/lib/mdk_runtime/actors.rb +457 -0
- data/lib/mdk_runtime/files.rb +575 -0
- data/lib/mdk_runtime/promise.rb +814 -0
- data/lib/mdk_tracing.rb +369 -0
- data/lib/mdk_tracing/api.rb +188 -0
- data/lib/mdk_tracing/protocol.rb +850 -0
- data/lib/mdk_util.rb +141 -0
- data/lib/quark.rb +3684 -0
- data/lib/quark/behaviors.rb +494 -0
- data/lib/quark/concurrent.rb +1250 -0
- data/lib/quark/error.rb +84 -0
- data/lib/quark/logging.rb +278 -0
- data/lib/quark/mock.rb +1223 -0
- data/lib/quark/os.rb +286 -0
- data/lib/quark/reflect.rb +489 -0
- data/lib/quark/spi.rb +130 -0
- data/lib/quark/spi_api.rb +489 -0
- data/lib/quark/spi_api_tracing.rb +1426 -0
- data/lib/quark/test.rb +766 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b898e0f72fd506802a43a6ad9972043b933c2092
|
4
|
+
data.tar.gz: 126e7d6be42af520468d4477cca660a0a11d4160
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f29db7b0ab84f7c5b02d3e827b9c1a752a9870d082d1fe93360d9205e43fa82152a5e8d0641e8eabf44999c935c9d661dbd74b2807a8feafb11d775c07ab3fb
|
7
|
+
data.tar.gz: cba352b068fdb7558d57ef6f40b43c49abf443e305dc40dae8a10b9fdbb3216e614bb252ee4ba558f81c0ba67106643920e74e4c3578fe1dc8c30ceb4e4e9901
|
@@ -0,0 +1,1213 @@
|
|
1
|
+
module DatawireQuarkCore
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
require 'concurrent'
|
8
|
+
require 'celluloid/current'
|
9
|
+
require 'reel'
|
10
|
+
require 'logging'
|
11
|
+
require 'event_emitter'
|
12
|
+
require 'securerandom'
|
13
|
+
|
14
|
+
module GettersSetters
|
15
|
+
# Generate Java/Quark-style getters and setters for
|
16
|
+
# instance variables.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
#
|
20
|
+
# class Foo
|
21
|
+
# extend GettersSetters
|
22
|
+
#
|
23
|
+
# getters :fooBar, :bazQux
|
24
|
+
# setters :fooBar
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# The above equivalent to:
|
28
|
+
#
|
29
|
+
# class Foo
|
30
|
+
# extend GettersAndSetters
|
31
|
+
#
|
32
|
+
# def getFooBar
|
33
|
+
# @fooBar
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def getBazQux
|
37
|
+
# @bazQux
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def setFooBar(value)
|
41
|
+
# @fooBar = value
|
42
|
+
#
|
43
|
+
# nil
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
def getters(*names)
|
48
|
+
names.each do |name|
|
49
|
+
define_method('get' + capitalize(name)) do
|
50
|
+
instance_variable_get("@#{name}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def setters(*names)
|
56
|
+
names.each do |name|
|
57
|
+
define_method('set' + capitalize(name)) do |value|
|
58
|
+
instance_variable_set("@#{name}", value)
|
59
|
+
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def capitalize(string)
|
68
|
+
string[0].upcase + string[1..-1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module Static
|
73
|
+
Unassigned = Class.new
|
74
|
+
|
75
|
+
def unlazy_statics
|
76
|
+
names = _lazy_statics
|
77
|
+
# puts "unlazying #{self.name} fields #{names}"
|
78
|
+
names.each do |name|
|
79
|
+
self.send(name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
def _lazy_statics
|
83
|
+
lazy = :@_lazy_statics
|
84
|
+
if not self.instance_variable_defined? lazy
|
85
|
+
# puts "Bootstrap #{self.name}"
|
86
|
+
l = self.instance_variable_set(lazy, {:__owner__ => self.name})
|
87
|
+
else
|
88
|
+
l = self.instance_variable_get(lazy)
|
89
|
+
end
|
90
|
+
if not l.has_key? self.name
|
91
|
+
o = l[:__owner__]
|
92
|
+
# puts "Adding slot for #{self.name} to lazy of #{o}"
|
93
|
+
l[self.name] = []
|
94
|
+
end
|
95
|
+
l[self.name]
|
96
|
+
end
|
97
|
+
def static(pairs)
|
98
|
+
pairs.each do |name, default|
|
99
|
+
_lazy_statics << name
|
100
|
+
self.instance_variable_set("@#{name}", Unassigned)
|
101
|
+
|
102
|
+
define_singleton_method(name) do
|
103
|
+
value = self.instance_variable_get("@#{name}")
|
104
|
+
|
105
|
+
if value == Unassigned
|
106
|
+
value = default.call
|
107
|
+
self.instance_variable_set("@#{name}", value)
|
108
|
+
end
|
109
|
+
|
110
|
+
value
|
111
|
+
end
|
112
|
+
|
113
|
+
define_singleton_method("#{name}=") do |value|
|
114
|
+
self.instance_variable_set("@#{name}", value)
|
115
|
+
end
|
116
|
+
|
117
|
+
define_method(name) do
|
118
|
+
self.class.send(name)
|
119
|
+
end
|
120
|
+
|
121
|
+
define_method("#{name}=") do |value|
|
122
|
+
self.class.send("#{name}=", value)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.print(message)
|
129
|
+
Kernel.print message == nil ? 'null' : message, "\n"
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.urlencode(hash)
|
133
|
+
URI.encode_www_form hash
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.split(string, separator)
|
137
|
+
return ['', ''] if string == separator
|
138
|
+
result = string.split(separator)
|
139
|
+
result = result + [''] if string.end_with? separator
|
140
|
+
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.getFileContents(path, result)
|
145
|
+
begin
|
146
|
+
result.value = File.read(path, {"encoding": "UTF-8"})
|
147
|
+
result.finish(nil)
|
148
|
+
rescue => ex
|
149
|
+
result.finish(::Quark.quark.os.OSError.new(ex.message))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self._getClass obj
|
154
|
+
clz = __getClass obj
|
155
|
+
# puts "runtime _getClass for #{obj} is #{clz}"
|
156
|
+
clz
|
157
|
+
end
|
158
|
+
def self.__getClass obj
|
159
|
+
return nil if obj.nil?
|
160
|
+
return "quark.bool" if (obj == true) or (obj == false)
|
161
|
+
return "quark.String" if obj.is_a? String
|
162
|
+
return "quark.int" if obj.is_a? Fixnum
|
163
|
+
return "quark.float" if obj.is_a? Float
|
164
|
+
return "quark.List<quark.Object>" if obj.is_a? Array
|
165
|
+
return "quark.Map<quark.Object,quark.Object>" if obj.is_a? Hash
|
166
|
+
if obj.respond_to? "_getClass" then
|
167
|
+
return obj._getClass
|
168
|
+
else
|
169
|
+
return "quark.Object"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class QuarkObject
|
174
|
+
|
175
|
+
def to_s
|
176
|
+
return self.toString() if self.respond_to? :toString
|
177
|
+
return super()
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
class List < Array
|
183
|
+
def to_s
|
184
|
+
'[' + map(&:to_s).join(', ') + ']'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class JSONObject
|
189
|
+
attr_accessor :value
|
190
|
+
|
191
|
+
def initialize(value=nil)
|
192
|
+
@value = value
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.parse(json)
|
196
|
+
new JSON.parse("[#{json}]")[0]
|
197
|
+
end
|
198
|
+
|
199
|
+
def ==(other)
|
200
|
+
not other.nil? and value == other.value
|
201
|
+
end
|
202
|
+
|
203
|
+
def toString
|
204
|
+
value.to_json
|
205
|
+
end
|
206
|
+
|
207
|
+
def size
|
208
|
+
isList || isObject ? value.size : 1
|
209
|
+
end
|
210
|
+
|
211
|
+
def getType
|
212
|
+
case @value
|
213
|
+
when Hash
|
214
|
+
'object'
|
215
|
+
when Array
|
216
|
+
'list'
|
217
|
+
when String
|
218
|
+
'string'
|
219
|
+
when Numeric
|
220
|
+
'number'
|
221
|
+
when true, false
|
222
|
+
'bool'
|
223
|
+
when nil
|
224
|
+
'null'
|
225
|
+
else
|
226
|
+
raise RuntimeError, "Unknown JSONObject type #{@value.inspect}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def set(value)
|
231
|
+
@value = value
|
232
|
+
|
233
|
+
self
|
234
|
+
end
|
235
|
+
|
236
|
+
# Object
|
237
|
+
|
238
|
+
def isObject
|
239
|
+
value.is_a? Hash
|
240
|
+
end
|
241
|
+
|
242
|
+
def setObject
|
243
|
+
@value = {}
|
244
|
+
|
245
|
+
self
|
246
|
+
end
|
247
|
+
|
248
|
+
def getObjectItem(key)
|
249
|
+
return undefined unless isObject and value.key? key
|
250
|
+
|
251
|
+
self.class.new value[key]
|
252
|
+
end
|
253
|
+
|
254
|
+
def setObjectItem(key, item)
|
255
|
+
setObject unless isObject
|
256
|
+
value[key] = item.value
|
257
|
+
|
258
|
+
self
|
259
|
+
end
|
260
|
+
|
261
|
+
def keys
|
262
|
+
return undefined unless isObject
|
263
|
+
|
264
|
+
List.new value.keys
|
265
|
+
end
|
266
|
+
|
267
|
+
# List
|
268
|
+
|
269
|
+
def isList
|
270
|
+
value.is_a? Array
|
271
|
+
end
|
272
|
+
|
273
|
+
def setList
|
274
|
+
@value = List.new
|
275
|
+
|
276
|
+
self
|
277
|
+
end
|
278
|
+
|
279
|
+
def getListItem(index)
|
280
|
+
return undefined unless isList and (0...size).include? index
|
281
|
+
|
282
|
+
self.class.new value[index]
|
283
|
+
end
|
284
|
+
|
285
|
+
def setListItem(index, item)
|
286
|
+
setList unless isList
|
287
|
+
value[index] = item.value
|
288
|
+
|
289
|
+
self
|
290
|
+
end
|
291
|
+
|
292
|
+
# String
|
293
|
+
|
294
|
+
def isString
|
295
|
+
value.is_a? String
|
296
|
+
end
|
297
|
+
|
298
|
+
def getString
|
299
|
+
return nil unless isString
|
300
|
+
|
301
|
+
value
|
302
|
+
end
|
303
|
+
|
304
|
+
alias_method :setString, :set
|
305
|
+
|
306
|
+
# Number
|
307
|
+
|
308
|
+
def isNumber
|
309
|
+
value.is_a? Numeric
|
310
|
+
end
|
311
|
+
|
312
|
+
def getNumber
|
313
|
+
return nil unless isNumber
|
314
|
+
|
315
|
+
value
|
316
|
+
end
|
317
|
+
|
318
|
+
alias_method :setNumber, :set
|
319
|
+
|
320
|
+
# Bool
|
321
|
+
|
322
|
+
def getBool
|
323
|
+
return nil unless isBool
|
324
|
+
|
325
|
+
value
|
326
|
+
end
|
327
|
+
|
328
|
+
def isBool
|
329
|
+
[true, false].include? value
|
330
|
+
end
|
331
|
+
|
332
|
+
alias_method :setBool, :set
|
333
|
+
|
334
|
+
# Null
|
335
|
+
|
336
|
+
def isNull
|
337
|
+
value == nil
|
338
|
+
end
|
339
|
+
|
340
|
+
def setNull
|
341
|
+
@value = nil
|
342
|
+
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
# Undefined
|
347
|
+
|
348
|
+
UNDEFINED = Class.new
|
349
|
+
|
350
|
+
def isUndefined
|
351
|
+
UNDEFINED.equal? value
|
352
|
+
end
|
353
|
+
|
354
|
+
def isDefined
|
355
|
+
not isUndefined
|
356
|
+
end
|
357
|
+
|
358
|
+
def undefined
|
359
|
+
self.class.new UNDEFINED
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
module HTTP
|
364
|
+
class Base
|
365
|
+
def setHeader(key, value)
|
366
|
+
@headers[key.downcase] = value
|
367
|
+
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
|
371
|
+
def getHeader(key)
|
372
|
+
@headers[key.downcase]
|
373
|
+
end
|
374
|
+
|
375
|
+
def getHeaders
|
376
|
+
@headers.keys
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
class Request < Base
|
381
|
+
extend GettersSetters
|
382
|
+
|
383
|
+
getters :body, :method, :url
|
384
|
+
setters :body, :method
|
385
|
+
|
386
|
+
def initialize(url)
|
387
|
+
@url = url
|
388
|
+
@method = 'GET'
|
389
|
+
@body = nil
|
390
|
+
@headers = {}
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
class Response < Base
|
395
|
+
extend GettersSetters
|
396
|
+
|
397
|
+
getters :code, :body
|
398
|
+
setters :code, :body
|
399
|
+
|
400
|
+
def initialize
|
401
|
+
@code = 500
|
402
|
+
@body = ''
|
403
|
+
@headers = {}
|
404
|
+
@responded = false
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
class Sources
|
410
|
+
def initialize()
|
411
|
+
@sources = Concurrent::Map.new
|
412
|
+
@seq = Concurrent::AtomicFixnum.new
|
413
|
+
@log = Logger.new "quark.runtime.sources"
|
414
|
+
end
|
415
|
+
|
416
|
+
def add (topic)
|
417
|
+
key = "%s-%s" % [ @seq.increment, topic ]
|
418
|
+
@sources.put_if_absent(key, Time.new)
|
419
|
+
key
|
420
|
+
end
|
421
|
+
|
422
|
+
def remove (key)
|
423
|
+
@sources.delete(key)
|
424
|
+
end
|
425
|
+
|
426
|
+
def empty?
|
427
|
+
@sources.empty?
|
428
|
+
end
|
429
|
+
|
430
|
+
def explain
|
431
|
+
@sources.each_key { |k|
|
432
|
+
@log.trace "Waiting for %s from %s" % [k, @sources[k]]
|
433
|
+
}
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
if not Logging.initialized?
|
439
|
+
Logging.init %w(trace debug info warn error fatal)
|
440
|
+
end
|
441
|
+
class QuarkLayout < ::Logging::Layout
|
442
|
+
def initialize
|
443
|
+
@namecache = {}
|
444
|
+
end
|
445
|
+
def format( event )
|
446
|
+
level = ::Logging::LNAMES[event.level].upcase
|
447
|
+
topic = topic_of(event)
|
448
|
+
obj = format_obj(event.data)
|
449
|
+
sprintf("%s %s %s\n", level, topic, obj)
|
450
|
+
end
|
451
|
+
def topic_of(event)
|
452
|
+
name = event.logger
|
453
|
+
topic = @namecache[name]
|
454
|
+
if topic.nil?
|
455
|
+
@namecache[name] = topic = name.gsub("::",".")
|
456
|
+
end
|
457
|
+
topic
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def self.configureLogging(appender, level)
|
462
|
+
root = Logging.logger["quark"]
|
463
|
+
if appender.name == ":STDOUT"
|
464
|
+
app = Logging.appenders.stdout
|
465
|
+
elsif appender.name == ":STDERR"
|
466
|
+
app = Logging.appenders.stderr
|
467
|
+
else
|
468
|
+
app = Logging.appenders.file(appender.name)
|
469
|
+
end
|
470
|
+
app.layout = QuarkLayout.new
|
471
|
+
root.appenders = app
|
472
|
+
root.level = level
|
473
|
+
end
|
474
|
+
|
475
|
+
class Logger
|
476
|
+
extend Forwardable
|
477
|
+
@@init = false
|
478
|
+
def initialize(topic)
|
479
|
+
@log = Logging.logger["quark::" + topic.gsub(".","::")]
|
480
|
+
end
|
481
|
+
if Logging.level_num(:trace).nil?
|
482
|
+
def_delegator :@log, :debug, :trace
|
483
|
+
else
|
484
|
+
def_delegator :@log, :trace, :trace
|
485
|
+
end
|
486
|
+
def_delegators :@log, :debug, :info, :warn, :error
|
487
|
+
end
|
488
|
+
|
489
|
+
class Eventor
|
490
|
+
def initialize(runtime)
|
491
|
+
@executor = Concurrent::SingleThreadExecutor.new
|
492
|
+
@timers = Concurrent::TimerSet.new(:executor => @executor)
|
493
|
+
@sources = Sources.new
|
494
|
+
@runtime = runtime
|
495
|
+
at_exit { wait_for_sources }
|
496
|
+
@log = Logger.new "quark.runtime"
|
497
|
+
end
|
498
|
+
|
499
|
+
attr_reader :runtime
|
500
|
+
|
501
|
+
def add(source)
|
502
|
+
@sources.add source
|
503
|
+
end
|
504
|
+
|
505
|
+
def event(final:nil, &block)
|
506
|
+
@executor.post { execute(final, block) }
|
507
|
+
end
|
508
|
+
|
509
|
+
def execute(final, block)
|
510
|
+
begin
|
511
|
+
block.call()
|
512
|
+
rescue => ex
|
513
|
+
puts "aieee", ex, ex.backtrace
|
514
|
+
@log.error ex
|
515
|
+
ensure
|
516
|
+
@sources.remove final if final
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def schedule(delay, &block)
|
521
|
+
@timers.post(delay, &block)
|
522
|
+
end
|
523
|
+
|
524
|
+
def wait_for_sources
|
525
|
+
last = Time.new - 10
|
526
|
+
delta = 1
|
527
|
+
while not @sources.empty?
|
528
|
+
now = Time.new
|
529
|
+
if now - last > delta
|
530
|
+
@sources.explain
|
531
|
+
last = now
|
532
|
+
if delta < 60
|
533
|
+
delta = delta * 1.41
|
534
|
+
end
|
535
|
+
end
|
536
|
+
sleep 0.1
|
537
|
+
end
|
538
|
+
rescue Interrupt
|
539
|
+
#@log.warn "Interrupted"
|
540
|
+
end
|
541
|
+
|
542
|
+
end
|
543
|
+
|
544
|
+
class Runtime
|
545
|
+
def initialize()
|
546
|
+
@events = Eventor.new self
|
547
|
+
@log = Logger.new "quark.runtime"
|
548
|
+
@servers = Servers.new(@events)
|
549
|
+
end
|
550
|
+
def schedule(task, delay)
|
551
|
+
src = @events.add "timer"
|
552
|
+
@events.schedule(delay) { @events.event(final:src) { task.onExecute self } }
|
553
|
+
end
|
554
|
+
def request(request, handler)
|
555
|
+
src = @events.add "http request"
|
556
|
+
t = Thread.new do
|
557
|
+
begin
|
558
|
+
url = request.getUrl
|
559
|
+
@events.event { handler.onHTTPInit url }
|
560
|
+
headers = {}
|
561
|
+
request.getHeaders.each { |k| headers[k] = request.getHeader k }
|
562
|
+
uri = URI(url)
|
563
|
+
req = Net::HTTPGenericRequest.new(request.getMethod.upcase, 1, 1, uri, headers)
|
564
|
+
req.body = request.getBody
|
565
|
+
res = Net::HTTP.start(uri.host, uri.port,
|
566
|
+
:use_ssl => uri.scheme == 'https') do | http |
|
567
|
+
http.request(req)
|
568
|
+
end
|
569
|
+
response = HTTP::Response.new
|
570
|
+
response.setCode(res.code.to_i)
|
571
|
+
response.setBody(res.body)
|
572
|
+
@events.event { handler.onHTTPResponse request, response }
|
573
|
+
rescue Exception => e
|
574
|
+
#@log.warn "EXCEPTION: #{e.inspect}"
|
575
|
+
#@log.warn "MESSAGE: #{e.message}"
|
576
|
+
@events.event { handler.onHTTPError request, ::Quark.quark.HTTPError.new(e.message) }
|
577
|
+
ensure
|
578
|
+
@events.event(final:src) { handler.onHTTPFinal request }
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
def serveHTTP(url, servlet)
|
584
|
+
@servers.add(HTTPAdapter.new(url, servlet, @events))
|
585
|
+
end
|
586
|
+
|
587
|
+
def serveWS(url, servlet)
|
588
|
+
@servers.add(WSAdapter.new(url, servlet, @events))
|
589
|
+
end
|
590
|
+
|
591
|
+
def respond(request, response)
|
592
|
+
if request.rs == response
|
593
|
+
request.respond :http_response
|
594
|
+
else
|
595
|
+
request.fail! 500, "servlet failed to pair up request and response\r\n"
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def open(url, handler)
|
600
|
+
begin
|
601
|
+
src = @events.add "ws client"
|
602
|
+
client = WebsocketClient.new(url)
|
603
|
+
sock = WebsocketAdapter.new(client)
|
604
|
+
events = @events
|
605
|
+
events.event { handler.onWSInit(sock) }
|
606
|
+
client.on_client(:open) do |wsevt|
|
607
|
+
# puts "open"
|
608
|
+
sock.opened = true
|
609
|
+
events.event { handler.onWSConnected(sock) }
|
610
|
+
end
|
611
|
+
client.on_client(:message) do |wsevt|
|
612
|
+
# puts "message"
|
613
|
+
case wsevt.data
|
614
|
+
when Array then
|
615
|
+
buffer = Buffer.new(wsevt.data.pack("C*"))
|
616
|
+
events.event { handler.onWSBinary(sock, buffer) }
|
617
|
+
when String then
|
618
|
+
events.event { handler.onWSMessage(sock, wsevt.data) }
|
619
|
+
end
|
620
|
+
end
|
621
|
+
client.on_client(:close) do |wsevt|
|
622
|
+
# puts "close"
|
623
|
+
if sock.opened
|
624
|
+
events.event { handler.onWSClosed(sock) }
|
625
|
+
end
|
626
|
+
events.event(final:src) { handler.onWSFinal(sock) }
|
627
|
+
end
|
628
|
+
client.on_client(:error) do |wsevt|
|
629
|
+
# puts self
|
630
|
+
events.event { handler.onWSError(sock, ::Quark.quark.WSError.new(wsevt.message)) }
|
631
|
+
end
|
632
|
+
client.issues.on(:start_failed) do |err|
|
633
|
+
events.event { handler.onWSError(sock, ::Quark.quark.WSError.new(err.to_s)) }
|
634
|
+
events.event(final:src) { handler.onWSFinal(sock) }
|
635
|
+
end
|
636
|
+
Thread.new { client.run }
|
637
|
+
rescue ::Exception => err
|
638
|
+
puts "AIEEE", err, err.backtrace
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def fail(message)
|
643
|
+
@log.error message
|
644
|
+
exit! 1
|
645
|
+
end
|
646
|
+
|
647
|
+
def logger(topic)
|
648
|
+
return Logger.new topic
|
649
|
+
end
|
650
|
+
|
651
|
+
def codec
|
652
|
+
return Codec.new
|
653
|
+
end
|
654
|
+
|
655
|
+
def now
|
656
|
+
(Time.now.to_f * 1000).round
|
657
|
+
end
|
658
|
+
|
659
|
+
def sleep(seconds)
|
660
|
+
Kernel.sleep seconds
|
661
|
+
end
|
662
|
+
|
663
|
+
def uuid
|
664
|
+
SecureRandom.uuid
|
665
|
+
end
|
666
|
+
|
667
|
+
def callSafely(unary_callable, default)
|
668
|
+
begin
|
669
|
+
::Quark.quark.callUnaryCallable(unary_callable, nil)
|
670
|
+
rescue
|
671
|
+
@log.error("Exception while calling safely: #{$!}")
|
672
|
+
default
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
class WebsocketAdapter
|
678
|
+
attr_accessor :opened
|
679
|
+
def initialize(client)
|
680
|
+
@client = client
|
681
|
+
@opened = false
|
682
|
+
end
|
683
|
+
|
684
|
+
def send (message)
|
685
|
+
@client.text message
|
686
|
+
end
|
687
|
+
|
688
|
+
def sendBinary (message)
|
689
|
+
@client.binary message.data
|
690
|
+
end
|
691
|
+
|
692
|
+
def close
|
693
|
+
@client.close
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
class WebscketIssues
|
698
|
+
include EventEmitter
|
699
|
+
end
|
700
|
+
class WebsocketClient
|
701
|
+
extend Forwardable
|
702
|
+
def initialize(url)
|
703
|
+
@url = url
|
704
|
+
@client = ::WebSocket::Driver.client(self)
|
705
|
+
@issues = WebscketIssues.new
|
706
|
+
end
|
707
|
+
attr_reader :url
|
708
|
+
|
709
|
+
|
710
|
+
def issues
|
711
|
+
@issues
|
712
|
+
end
|
713
|
+
|
714
|
+
def run
|
715
|
+
begin
|
716
|
+
uri = URI(url)
|
717
|
+
port = uri.port || (uri.scheme == "ws" ? 80 : 443)
|
718
|
+
tcp = Celluloid::IO::TCPSocket.new(uri.host, port)
|
719
|
+
if uri.scheme == "wss"
|
720
|
+
@socket = Celluloid::IO::SSLSocket.new(tcp)
|
721
|
+
@socket.connect
|
722
|
+
else
|
723
|
+
@socket = tcp
|
724
|
+
end
|
725
|
+
@client.start
|
726
|
+
rescue ::Exception => err
|
727
|
+
@issues.emit(:start_failed, err)
|
728
|
+
else
|
729
|
+
loop do
|
730
|
+
begin
|
731
|
+
@client.parse(@socket.readpartial(1024))
|
732
|
+
rescue EOFError
|
733
|
+
break
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
def_delegators :@client, :text, :binary, :ping, :close, :protocol
|
740
|
+
def_delegator :@client, :on, :on_client
|
741
|
+
|
742
|
+
def write(buffer)
|
743
|
+
@socket.write buffer
|
744
|
+
end
|
745
|
+
|
746
|
+
end
|
747
|
+
|
748
|
+
class IncomingRequest < HTTP::Request
|
749
|
+
attr_accessor :rs
|
750
|
+
attr_accessor :fut
|
751
|
+
attr_accessor :request
|
752
|
+
attr_accessor :svr
|
753
|
+
attr_accessor :action
|
754
|
+
attr_accessor :ws_handler
|
755
|
+
|
756
|
+
def fail! (code, body)
|
757
|
+
@rs.setCode(code)
|
758
|
+
@rs.setBody(body)
|
759
|
+
respond :http_response
|
760
|
+
end
|
761
|
+
|
762
|
+
def respond(action)
|
763
|
+
@svr.respond(self, action)
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
class Servers
|
768
|
+
include Celluloid
|
769
|
+
def initialize(events)
|
770
|
+
@servers = {}
|
771
|
+
@events= events
|
772
|
+
end
|
773
|
+
def add(adapter)
|
774
|
+
if not adapter.scheme_supported?
|
775
|
+
error = "${adapter.uri.scheme} is not supported"
|
776
|
+
@events.event { adapter.servlet.onServletError(adapter.url, ::Quark.quark.ServletError.new(error)) }
|
777
|
+
return
|
778
|
+
end
|
779
|
+
if adapter.secure?
|
780
|
+
error = "${adapter.uri.scheme} is not yet supported"
|
781
|
+
@events.event { adapter.servlet.onServletError(adapter.url, ::Quark.quark.ServletError.new(error)) }
|
782
|
+
return
|
783
|
+
end
|
784
|
+
server = @servers[adapter.key]
|
785
|
+
if server.nil?
|
786
|
+
if adapter.secure?
|
787
|
+
server = HTTPSServer.new(adapter.uri.hostname, adapter.uri.port, @events)
|
788
|
+
else
|
789
|
+
server = HTTPServer.new(adapter.uri.hostname, adapter.uri.port, @events)
|
790
|
+
end
|
791
|
+
# in case the port was 0 this will mutate the #effective_url, and #key of adapter
|
792
|
+
adapter.uri.port = server.local_address.ip_port
|
793
|
+
@servers[adapter.key] = server
|
794
|
+
end
|
795
|
+
server.add(adapter)
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
class Adapter
|
800
|
+
def initialize(url, servlet, events)
|
801
|
+
@url = url
|
802
|
+
@uri = URI(url)
|
803
|
+
@servlet = servlet
|
804
|
+
@events = events
|
805
|
+
end
|
806
|
+
attr_reader :servlet, :uri, :url
|
807
|
+
attr_accessor :source
|
808
|
+
|
809
|
+
def scheme_supported?
|
810
|
+
self.schemes.values.include? @uri.scheme
|
811
|
+
end
|
812
|
+
|
813
|
+
def secure?
|
814
|
+
self.schemes[:secure] == @uri.scheme
|
815
|
+
end
|
816
|
+
|
817
|
+
def key
|
818
|
+
"#{@uri.host}:#{@uri.port}"
|
819
|
+
end
|
820
|
+
|
821
|
+
def effective_url
|
822
|
+
@uri.to_s
|
823
|
+
end
|
824
|
+
|
825
|
+
end
|
826
|
+
|
827
|
+
class HTTPAdapter < Adapter
|
828
|
+
def schemes
|
829
|
+
{plain: "http", secure: "https"}
|
830
|
+
end
|
831
|
+
|
832
|
+
def process_request(rq)
|
833
|
+
if rq.request.websocket?
|
834
|
+
rq.fail! 400, "http here, move along\r\n"
|
835
|
+
else
|
836
|
+
@events.event { servlet.onHTTPRequest(rq, rq.rs) }
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
def process_response(rq)
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
class ServerWebsocketAdapter
|
845
|
+
def initialize(sock)
|
846
|
+
@sock = sock
|
847
|
+
end
|
848
|
+
def send (message)
|
849
|
+
@sock.write message
|
850
|
+
end
|
851
|
+
|
852
|
+
def sendBinary (message)
|
853
|
+
@sock.write message.data.unpack("C*")
|
854
|
+
end
|
855
|
+
|
856
|
+
def close
|
857
|
+
@sock.close
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
class WSAdapter < Adapter
|
862
|
+
def schemes
|
863
|
+
{plain: "ws", secure: "wss"}
|
864
|
+
end
|
865
|
+
|
866
|
+
def process_request(rq)
|
867
|
+
if rq.request.websocket?
|
868
|
+
@events.event { rq.ws_handler = servlet.onWSConnect(rq); rq.respond :detach }
|
869
|
+
else
|
870
|
+
rq.fail! 400, "websockets here, move along\r\n"
|
871
|
+
end
|
872
|
+
end
|
873
|
+
def process_response(rq)
|
874
|
+
if rq.ws_handler.nil?
|
875
|
+
rq.fail 403, "Forbidden\r\n"
|
876
|
+
else
|
877
|
+
websocket = rq.request.websocket
|
878
|
+
sock = ServerWebsocketAdapter.new(websocket)
|
879
|
+
handler = rq.ws_handler
|
880
|
+
src = @events.add ("server websocket")
|
881
|
+
events = @events
|
882
|
+
@events.event { handler.onWSInit(sock) }
|
883
|
+
@events.event { handler.onWSConnected(sock) }
|
884
|
+
websocket.on_close do |wsevent|
|
885
|
+
events.event { handler.onWSClosed(sock) }
|
886
|
+
events.event(final:src) { handler.onWSFinal(sock) }
|
887
|
+
end
|
888
|
+
websocket.on_error do |wsevt|
|
889
|
+
events.event { handler.onWSError(sock, ::Quark.quark.WSError.new(wsevt.message)) }
|
890
|
+
end
|
891
|
+
Thread.new do
|
892
|
+
while not websocket.closed?
|
893
|
+
data = websocket.read
|
894
|
+
case data
|
895
|
+
when Array then
|
896
|
+
buffer = Buffer.new(data.pack("C*"))
|
897
|
+
events.event { handler.onWSBinary(sock, buffer) }
|
898
|
+
when String then
|
899
|
+
events.event { handler.onWSMessage(sock, data) }
|
900
|
+
end
|
901
|
+
end
|
902
|
+
end
|
903
|
+
end
|
904
|
+
rescue => ex
|
905
|
+
puts "aieieie", ex
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
module MyServer
|
910
|
+
def initialize(host = "127.0.0.1", port = 3000, events)
|
911
|
+
super(host, port, &method(:on_connection))
|
912
|
+
@events = events
|
913
|
+
@paths = {}
|
914
|
+
@log = Logger.new "quark.runtime.server"
|
915
|
+
end
|
916
|
+
|
917
|
+
|
918
|
+
def local_address
|
919
|
+
Addrinfo.new(@server.getsockname)
|
920
|
+
end
|
921
|
+
|
922
|
+
def add(adapter)
|
923
|
+
old = @paths[adapter.uri.path]
|
924
|
+
if not adapter.nil?
|
925
|
+
@paths[adapter.uri.path] = adapter
|
926
|
+
adapter.source = @events.add(adapter.effective_url)
|
927
|
+
@events.event { adapter.servlet.onServletInit(adapter.effective_url, @events.runtime) }
|
928
|
+
end
|
929
|
+
if not old.nil?
|
930
|
+
# XXX: servlet is not quiesced
|
931
|
+
@events.event(final: adapter.source) { adapter.servlet.onServletFinal(adapter.effective_url) }
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
def on_connection(connection)
|
936
|
+
connection.each_request do |request|
|
937
|
+
begin
|
938
|
+
rq = IncomingRequest.new(request.url)
|
939
|
+
rq.setBody(request.body)
|
940
|
+
request.headers.each {|key, value| rq.setHeader(key, value)}
|
941
|
+
rq.setMethod(request.method)
|
942
|
+
rq.request = request
|
943
|
+
rq.rs = HTTP::Response.new
|
944
|
+
rq.fut = Celluloid::Condition.new
|
945
|
+
rq.svr = self
|
946
|
+
rq.action = :wait
|
947
|
+
adapter = @paths[request.uri.path]
|
948
|
+
if adapter.nil?
|
949
|
+
rq.fail! 404, "not found\r\n"
|
950
|
+
else
|
951
|
+
adapter.process_request(rq)
|
952
|
+
end
|
953
|
+
if rq.action == :wait
|
954
|
+
rq.fut.wait
|
955
|
+
end
|
956
|
+
adapter.process_response(rq)
|
957
|
+
case rq.action
|
958
|
+
when :http_response
|
959
|
+
http_response(rq)
|
960
|
+
when :detach
|
961
|
+
connection.detach
|
962
|
+
else
|
963
|
+
@log.error "Unknown action #{rq.action} for HTTP request"
|
964
|
+
rq.fail! 500, "quark runtime is confused, unknown http request action\r\n"
|
965
|
+
http_response(rq)
|
966
|
+
end
|
967
|
+
rescue => ex
|
968
|
+
@log.error ex
|
969
|
+
end
|
970
|
+
end
|
971
|
+
end
|
972
|
+
|
973
|
+
def http_response(rq)
|
974
|
+
rs = rq.rs
|
975
|
+
headers = {}
|
976
|
+
rs.getHeaders.each { |k| headers[k] = rs.getHeader k }
|
977
|
+
response = Reel::Response::new(rs.getCode, headers, rs.getBody)
|
978
|
+
rq.request.respond response
|
979
|
+
end
|
980
|
+
|
981
|
+
def respond(rq, action)
|
982
|
+
waiting = rq.action == :wait
|
983
|
+
rq.action = action
|
984
|
+
if waiting
|
985
|
+
# condition is race-free only in the context of the originating actor
|
986
|
+
rq.fut.signal
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|
990
|
+
|
991
|
+
class HTTPServer < Reel::Server::HTTP
|
992
|
+
include MyServer
|
993
|
+
end
|
994
|
+
|
995
|
+
class HTTPSServer < Reel::Server::HTTPS
|
996
|
+
include MyServer
|
997
|
+
end
|
998
|
+
|
999
|
+
|
1000
|
+
HTTPRequest = HTTP::Request
|
1001
|
+
|
1002
|
+
class Servlet
|
1003
|
+
def onServletInit(url, rt)
|
1004
|
+
end
|
1005
|
+
def onServletError(url, error)
|
1006
|
+
end
|
1007
|
+
def onServletEnd(url)
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
class HTTPServlet < Servlet
|
1012
|
+
def onHTTPRequest(request, response)
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
class WSServlet < Servlet
|
1017
|
+
def onWSConnect(request)
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def self.url_get(url)
|
1022
|
+
Net::HTTP.get(URI(url))
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
def self.default_codec
|
1026
|
+
Codec.new
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
def self.cast(value, &block)
|
1030
|
+
# For now there is no easy way to check in Ruby that Quark class C is
|
1031
|
+
# subclass of of Quark class B, so don't check anything until that's fixed.
|
1032
|
+
# The correct way to do so would be via reflect.Class.hasInstance, probably,
|
1033
|
+
# but that doesn't support interfaces yet.
|
1034
|
+
value
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
class Mutex
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
class Lock < Mutex
|
1041
|
+
def initialize
|
1042
|
+
@lock = ::Thread::Mutex.new
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def acquire
|
1046
|
+
if @lock.owned?
|
1047
|
+
fail "Illegal re-acquisition of a quark lock"
|
1048
|
+
end
|
1049
|
+
@lock.lock
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
def release
|
1053
|
+
if !@lock.owned?
|
1054
|
+
fail "Illegal release of a not-acquired quark lock"
|
1055
|
+
end
|
1056
|
+
@lock.unlock
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
private
|
1060
|
+
|
1061
|
+
def fail(reason)
|
1062
|
+
@log.fatal reason
|
1063
|
+
exit! 1 # XXX: we should go via Quark runtime, but need to develop a method to query it, Context is not yet exposed to the native code...
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
class Condition < Lock
|
1068
|
+
def initialize
|
1069
|
+
super
|
1070
|
+
@condition = ::Thread::ConditionVariable.new
|
1071
|
+
end
|
1072
|
+
def waitWakeup(timeout)
|
1073
|
+
if !@lock.owned?
|
1074
|
+
fail "Illegal waitWakeup of a not-acquired quark Condition"
|
1075
|
+
end
|
1076
|
+
@condition.wait(@lock, timeout / 1000.0)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def wakeup
|
1080
|
+
if !@lock.owned?
|
1081
|
+
fail "Illegal wakeup of a not-acquired quark Condition"
|
1082
|
+
end
|
1083
|
+
@condition.signal
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
class TLS
|
1088
|
+
UNINITIALIZED = []
|
1089
|
+
private_constant :UNINITIALIZED
|
1090
|
+
def initialize(initializer)
|
1091
|
+
@initializer = initializer
|
1092
|
+
@var = Concurrent::ThreadLocalVar.new UNINITIALIZED
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def getValue
|
1096
|
+
value = @var.value
|
1097
|
+
if UNINITIALIZED.equal?(value)
|
1098
|
+
@var.value = value = @initializer.getValue
|
1099
|
+
end
|
1100
|
+
value
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
def setValue(value)
|
1104
|
+
@var.value = value
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
class Codec
|
1109
|
+
ZERO = "\0".encode Encoding::ASCII_8BIT
|
1110
|
+
def buffer(size)
|
1111
|
+
Buffer.new ZERO * size
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def toHexdump(buffer, index, size, spaceScale)
|
1115
|
+
hex = buffer.data[index...index+size].unpack("H*")[0]
|
1116
|
+
(0...hex.size).each_slice(2**(spaceScale+1)).map {|i|
|
1117
|
+
hex[i[0]..i[-1]]
|
1118
|
+
}.join " "
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
def toBase64(buffer, index, size)
|
1122
|
+
Base64.encode64(buffer.data[index...index+size]).strip
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
def fromHexdump(value)
|
1126
|
+
value = value.split(" ").join
|
1127
|
+
if value[0...2].downcase == "0x"
|
1128
|
+
value[0...2] = ""
|
1129
|
+
end
|
1130
|
+
Buffer.new ((0...value.length).each_slice(2).map { |i|
|
1131
|
+
value[i[0]..i[1]].to_i(16)
|
1132
|
+
}.pack("C*"))
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
def fromBase64(value)
|
1136
|
+
Buffer.new Base64.decode64 value
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
class Buffer
|
1141
|
+
BIN = Encoding::ASCII_8BIT
|
1142
|
+
UTF8 = Encoding::UTF_8
|
1143
|
+
module BE
|
1144
|
+
SHORT = "s>"
|
1145
|
+
INT = "l>"
|
1146
|
+
LONG = "q>"
|
1147
|
+
FLOAT = "G"
|
1148
|
+
end
|
1149
|
+
module LE
|
1150
|
+
SHORT = "s<"
|
1151
|
+
INT = "l<"
|
1152
|
+
LONG = "q<"
|
1153
|
+
FLOAT = "E"
|
1154
|
+
end
|
1155
|
+
attr_reader :data
|
1156
|
+
def initialize(data)
|
1157
|
+
@data = data.dup.force_encoding BIN
|
1158
|
+
@ord = BE
|
1159
|
+
end
|
1160
|
+
def capacity
|
1161
|
+
@data.length
|
1162
|
+
end
|
1163
|
+
def putStringUTF8(index, value)
|
1164
|
+
value = value.dup.force_encoding BIN
|
1165
|
+
len = value.length
|
1166
|
+
@data[index...index+len] = value
|
1167
|
+
len
|
1168
|
+
end
|
1169
|
+
def getStringUTF8(index, length)
|
1170
|
+
@data[index...index+length].force_encoding UTF8
|
1171
|
+
end
|
1172
|
+
def getByte(index)
|
1173
|
+
@data[index].bytes[0]
|
1174
|
+
end
|
1175
|
+
def putByte(index, value)
|
1176
|
+
@data[index] = [value].pack("C")
|
1177
|
+
end
|
1178
|
+
def littleEndian
|
1179
|
+
@ord = LE
|
1180
|
+
self
|
1181
|
+
end
|
1182
|
+
def getShort(index)
|
1183
|
+
@data[index..-1].unpack(@ord::SHORT)[0]
|
1184
|
+
end
|
1185
|
+
def putShort(index, value)
|
1186
|
+
@data[index...index+2] = [value].pack(@ord::SHORT)
|
1187
|
+
end
|
1188
|
+
def getInt(index)
|
1189
|
+
@data[index..-1].unpack(@ord::INT)[0]
|
1190
|
+
end
|
1191
|
+
def putInt(index, value)
|
1192
|
+
@data[index...index+4] = [value].pack(@ord::INT)
|
1193
|
+
end
|
1194
|
+
def getLong(index)
|
1195
|
+
@data[index..-1].unpack(@ord::LONG)[0]
|
1196
|
+
end
|
1197
|
+
def putLong(index, value)
|
1198
|
+
@data[index...index+8] = [value].pack(@ord::LONG)
|
1199
|
+
end
|
1200
|
+
def getFloat(index)
|
1201
|
+
@data[index..-1].unpack(@ord::FLOAT)[0]
|
1202
|
+
end
|
1203
|
+
def putFloat(index, value)
|
1204
|
+
@data[index...index+8] = [value].pack(@ord::FLOAT)
|
1205
|
+
end
|
1206
|
+
def getSlice(index, length)
|
1207
|
+
return Buffer.new(@data[index...index+length])
|
1208
|
+
end
|
1209
|
+
def inspect
|
1210
|
+
"Buffer(%s)" % Codec.new.toHexdump(self, 0, @data.length, 3)
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
end
|