amq-protocol 2.3.3 → 2.8.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +85 -2
  3. data/LICENSE +1 -1
  4. data/README.md +2 -7
  5. data/lib/amq/bit_set.rb +2 -1
  6. data/lib/amq/endianness.rb +2 -0
  7. data/lib/amq/int_allocator.rb +3 -3
  8. data/lib/amq/pack.rb +33 -42
  9. data/lib/amq/protocol/channel_close.rb +24 -0
  10. data/lib/amq/protocol/client.rb +31 -40
  11. data/lib/amq/protocol/constants.rb +2 -0
  12. data/lib/amq/protocol/exceptions.rb +3 -1
  13. data/lib/amq/protocol/float_32bit.rb +2 -0
  14. data/lib/amq/protocol/frame.rb +16 -10
  15. data/lib/amq/protocol/table.rb +20 -17
  16. data/lib/amq/protocol/table_value_decoder.rb +49 -53
  17. data/lib/amq/protocol/table_value_encoder.rb +2 -1
  18. data/lib/amq/protocol/type_constants.rb +1 -0
  19. data/lib/amq/protocol/version.rb +1 -1
  20. data/lib/amq/protocol.rb +1 -0
  21. data/lib/amq/settings.rb +1 -0
  22. data/lib/amq/uri.rb +30 -17
  23. metadata +5 -45
  24. data/.github/ISSUE_TEMPLATE.md +0 -18
  25. data/.github/workflows/ci.yml +0 -31
  26. data/.gitignore +0 -18
  27. data/.gitmodules +0 -3
  28. data/.rspec +0 -1
  29. data/.travis.yml +0 -17
  30. data/Gemfile +0 -22
  31. data/Rakefile +0 -55
  32. data/amq-protocol.gemspec +0 -27
  33. data/benchmarks/int_allocator.rb +0 -34
  34. data/benchmarks/pure/body_framing_with_256k_payload.rb +0 -28
  35. data/benchmarks/pure/body_framing_with_2k_payload.rb +0 -28
  36. data/codegen/__init__.py +0 -0
  37. data/codegen/amqp_0.9.1_changes.json +0 -1
  38. data/codegen/codegen.py +0 -151
  39. data/codegen/codegen_helpers.py +0 -162
  40. data/codegen/protocol.rb.pytemplate +0 -320
  41. data/generate.rb +0 -24
  42. data/profiling/README.md +0 -9
  43. data/profiling/stackprof/body_framing_with_2k_payload.rb +0 -33
  44. data/spec/amq/bit_set_spec.rb +0 -227
  45. data/spec/amq/int_allocator_spec.rb +0 -113
  46. data/spec/amq/pack_spec.rb +0 -68
  47. data/spec/amq/protocol/basic_spec.rb +0 -325
  48. data/spec/amq/protocol/blank_body_encoding_spec.rb +0 -9
  49. data/spec/amq/protocol/channel_spec.rb +0 -127
  50. data/spec/amq/protocol/confirm_spec.rb +0 -41
  51. data/spec/amq/protocol/connection_spec.rb +0 -146
  52. data/spec/amq/protocol/constants_spec.rb +0 -10
  53. data/spec/amq/protocol/exchange_spec.rb +0 -106
  54. data/spec/amq/protocol/frame_spec.rb +0 -92
  55. data/spec/amq/protocol/method_spec.rb +0 -43
  56. data/spec/amq/protocol/queue_spec.rb +0 -126
  57. data/spec/amq/protocol/table_spec.rb +0 -259
  58. data/spec/amq/protocol/tx_spec.rb +0 -55
  59. data/spec/amq/protocol/value_decoder_spec.rb +0 -86
  60. data/spec/amq/protocol/value_encoder_spec.rb +0 -140
  61. data/spec/amq/protocol_spec.rb +0 -812
  62. data/spec/amq/settings_spec.rb +0 -22
  63. data/spec/amq/uri_parsing_spec.rb +0 -280
  64. data/spec/spec_helper.rb +0 -29
data/Rakefile DELETED
@@ -1,55 +0,0 @@
1
- require "bundler"
2
- Bundler.setup
3
-
4
- require "rake"
5
- require "rspec/core/rake_task"
6
-
7
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
8
- require "amq/protocol/version"
9
-
10
- task :gem => :build
11
- task :build do
12
- system "gem build amq-protocol.gemspec"
13
- end
14
-
15
- task :install => :build do
16
- system "gem install amq-protocol-#{AMQ::Protocol::VERSION}.gem"
17
- end
18
-
19
- def extension
20
- RUBY_PLATFORM =~ /darwin/ ? "bundle" : "so"
21
- end
22
-
23
- def compile!
24
- puts "Compiling native extensions..."
25
- Dir.chdir(Pathname(__FILE__).dirname + "ext/") do
26
- `bundle exec ruby extconf.rb`
27
- `make`
28
- `cp client.#{extension} ../lib/amq/protocol/native/`
29
- end
30
- end
31
-
32
- RSpec::Core::RakeTask.new("spec") do |spec|
33
- spec.pattern = "spec/**/*_spec.rb"
34
- end
35
-
36
- RSpec::Core::RakeTask.new("clean_spec") do |spec|
37
- spec.pattern = "spec/**/*_spec.rb"
38
- end
39
-
40
- task :compile do
41
- compile!
42
- end
43
-
44
- task :clean do
45
- puts "Cleaning out native extensions..."
46
- begin
47
- Dir.chdir(Pathname(__FILE__).dirname + "lib/amq-protocol/native") do
48
- `rm client.#{extension}`
49
- end
50
- rescue Exception => e
51
- puts e.message
52
- end
53
- end
54
-
55
- task :default => [:compile, :spec, :clean, :clean_spec]
data/amq-protocol.gemspec DELETED
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env gem build
2
- # encoding: utf-8
3
-
4
- require "base64"
5
-
6
- require File.expand_path("../lib/amq/protocol/version", __FILE__)
7
-
8
- Gem::Specification.new do |s|
9
- s.name = "amq-protocol"
10
- s.version = AMQ::Protocol::VERSION
11
- s.authors = ["Jakub Stastny", "Michael S. Klishin", "Theo Hultberg", "Mark Abramov"]
12
- s.homepage = "http://github.com/ruby-amqp/amq-protocol"
13
- s.summary = "AMQP 0.9.1 encoding & decoding library."
14
- s.description = <<-DESC
15
- amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not a
16
- client: the library only handles serialization and deserialization.
17
- DESC
18
- s.email = ["michael.s.klishin@gmail.com"]
19
- s.licenses = ["MIT"]
20
- s.required_ruby_version = Gem::Requirement.new(">= 2.2")
21
-
22
- # files
23
- s.files = `git ls-files`.split("\n").reject { |file| file =~ /^vendor\// }
24
- s.require_paths = ["lib"]
25
-
26
- s.extra_rdoc_files = ["README.md"] + Dir.glob("doc/*")
27
- end
@@ -1,34 +0,0 @@
1
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
2
-
3
- require 'amq/int_allocator'
4
- require "benchmark"
5
-
6
- allocator = AMQ::IntAllocator.new(1,65535)
7
- mutex = Mutex.new
8
-
9
- Benchmark.bm do |x|
10
-
11
-
12
- x.report("allocate") do
13
- allocator = AMQ::IntAllocator.new(1,65535)
14
- 1.upto(65534) do |i|
15
- mutex.synchronize do
16
- n = allocator.allocate
17
- raise 'it be broke' unless n == i
18
- end
19
- end
20
- end
21
-
22
- x.report("allocate_with_release") do
23
- allocator = AMQ::IntAllocator.new(1,65535)
24
- 1.upto(65534) do |i|
25
- mutex.synchronize do
26
- n = allocator.allocate
27
- if i % 5 == 0
28
- allocator.release(n)
29
- end
30
- end
31
- end
32
- end
33
-
34
- end
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
-
4
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib"))
5
-
6
- require "amq/protocol/client"
7
- require "benchmark"
8
-
9
- FRAME_SIZE = 128 * 1024
10
-
11
- puts
12
- puts "-" * 80
13
- puts "Benchmarking on #{RUBY_DESCRIPTION}"
14
-
15
- n = 250_000
16
-
17
- # warm up the JIT, etc
18
- puts "Doing a warmup run..."
19
- 15_000.times { AMQ::Protocol::Method.encode_body("a" * 256 * 1024, 1, FRAME_SIZE) }
20
-
21
- t = Benchmark.realtime do
22
- n.times { AMQ::Protocol::Method.encode_body("a" * 256 * 1024, 1, FRAME_SIZE) }
23
- end
24
- r = (n.to_f/t.to_f)
25
-
26
- puts "AMQ::Protocol::Method.encode_body rate: #{(r / 1000).round(2)} KGHz"
27
- puts
28
- puts "-" * 80
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
-
4
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib"))
5
-
6
- require "amq/protocol/client"
7
- require "benchmark"
8
-
9
- FRAME_SIZE = 128 * 1024
10
-
11
- puts
12
- puts "-" * 80
13
- puts "Benchmarking on #{RUBY_DESCRIPTION}"
14
-
15
- n = 250_000
16
-
17
- # warm up the JIT, etc
18
- puts "Doing a warmup run..."
19
- 15_000.times { AMQ::Protocol::Method.encode_body("ab" * 1024, 1, FRAME_SIZE) }
20
-
21
- t = Benchmark.realtime do
22
- n.times { AMQ::Protocol::Method.encode_body("ab" * 1024, 1, FRAME_SIZE) }
23
- end
24
- r = (n.to_f/t.to_f)
25
-
26
- puts "AMQ::Protocol::Method.encode_body rate: #{(r / 1000).round(2)} KGHz"
27
- puts
28
- puts "-" * 80
data/codegen/__init__.py DELETED
File without changes
@@ -1 +0,0 @@
1
- {"tx": {"select-ok": ["client"], "rollback": ["server"], "commit": ["server"], "rollback-ok": ["client"], "select": ["server"], "commit-ok": ["client"]}, "exchange": {"delete-ok": ["client"], "declare-ok": ["client"], "declare": ["server"], "delete": ["server"], "bind": ["server"], "bind-ok": ["client"], "unbind": ["server"], "unbind-ok": ["client"]}, "queue": {"unbind": ["server"], "unbind-ok": ["client"], "purge-ok": ["client"], "bind": ["server"], "purge": ["server"], "declare-ok": ["client"], "delete-ok": ["client"], "delete": ["server"], "declare": ["server"], "bind-ok": ["client"]}, "connection": {"secure": ["client"], "secure-ok": ["server"], "open-ok": ["client"], "close-ok": ["client", "server"], "start": ["client"], "tune": ["client"], "start-ok": ["server"], "close": ["client", "server"], "open": ["server"], "tune-ok": ["server"]}, "basic": {"qos": ["server"], "consume": ["server"], "reject": ["server"], "get": ["server"], "ack": ["client", "server"], "get-ok": ["client"], "consume-ok": ["client"], "deliver": ["client"], "recover-ok": ["client"], "publish": ["server"], "cancel": ["server", "client"], "recover-async": ["server"], "get-empty": ["client"], "qos-ok": ["client"], "return": ["client"], "recover": ["server"], "cancel-ok": ["client"]}, "channel": {"flow-ok": ["server", "client"], "flow": ["server", "client"], "open-ok": ["client"], "close-ok": ["client", "server"], "close": ["client", "server"], "open": ["server"]}}
data/codegen/codegen.py DELETED
@@ -1,151 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- # Documentation for Mako templates:
5
- # http://www.makotemplates.org/docs/syntax.html
6
-
7
- import os, sys, re
8
-
9
- sys.path.append(os.path.join("codegen", "rabbitmq-codegen"))
10
-
11
- from amqp_codegen import *
12
- try:
13
- from mako.template import Template
14
- except ImportError:
15
- print("Mako isn't installed. Please install mako via pip or similar.")
16
- sys.exit(1)
17
-
18
- # main class
19
- class AmqpSpecObject(AmqpSpec):
20
- IGNORED_CLASSES = ["access"]
21
- IGNORED_FIELDS = {
22
- 'ticket': 0,
23
- 'capabilities': '',
24
- 'insist' : 0,
25
- 'out_of_band': '',
26
- 'known_hosts': '',
27
- }
28
-
29
- def __init__(self, path):
30
- AmqpSpec.__init__(self, path)
31
-
32
- def extend_field(field):
33
- field.ruby_name = re.sub("[- ]", "_", field.name)
34
- field.type = self.resolveDomain(field.domain)
35
- field.ignored = bool(field.name in self.__class__.IGNORED_FIELDS) # I. e. deprecated
36
-
37
- for klass in self.classes:
38
- klass.ignored = bool(klass.name in self.__class__.IGNORED_CLASSES)
39
-
40
- for field in klass.fields:
41
- extend_field(field)
42
-
43
- for method in klass.methods:
44
- for field in method.arguments:
45
- extend_field(field)
46
-
47
- self.classes = filter(lambda klass: not klass.ignored, self.classes)
48
-
49
- original_init = AmqpEntity.__init__
50
- def new_init(self, arg):
51
- original_init(self, arg)
52
- constant_name = ""
53
- for chunk in self.name.split("-"):
54
- constant_name += chunk.capitalize()
55
- self.constant_name = constant_name
56
- AmqpEntity.__init__ = new_init
57
-
58
- # method.accepted_by("server")
59
- # method.accepted_by("client", "server")
60
- accepted_by_update = json.loads(open("codegen/amqp_0.9.1_changes.json").read())
61
-
62
- def accepted_by(self, *receivers):
63
- def get_accepted_by(self):
64
- try:
65
- return accepted_by_update[self.klass.name][self.name]
66
- except KeyError:
67
- return ["server", "client"]
68
-
69
- actual_receivers = get_accepted_by(self)
70
- return all(map(lambda receiver: receiver in actual_receivers, receivers))
71
-
72
- AmqpMethod.accepted_by = accepted_by
73
-
74
- def convert_value_to_ruby(value):
75
- values = {None: "nil", False: "false", True: "true", "": "EMPTY_STRING"}
76
-
77
- try:
78
- return values[value]
79
- except:
80
- return value.__repr__()
81
-
82
- def convert_to_ruby(field):
83
- name = re.sub("-", "_", field.name) # TODO: use ruby_name
84
- if name == "ticket":
85
- return "%s = %s" % (name, field.defaultvalue) # we want to keep it as an int, not as a boolean
86
- else:
87
- return "%s = %s" % (name, convert_value_to_ruby(field.defaultvalue))
88
-
89
- def not_ignored_args(self):
90
- if self.hasContent:
91
- return ["payload", "user_headers"] + map(lambda argument: argument.ruby_name, filter(lambda argument: not argument.ignored, self.arguments)) + ["frame_size"]
92
- else:
93
- return map(lambda argument: argument.ruby_name, filter(lambda argument: not argument.ignored, self.arguments))
94
-
95
- AmqpMethod.not_ignored_args = not_ignored_args
96
-
97
- def ignored_args(self):
98
- return filter(lambda argument: argument.ignored, self.arguments)
99
-
100
- AmqpMethod.ignored_args = ignored_args
101
-
102
- # helpers
103
- def to_ruby_name(name):
104
- return re.sub("[- ]", "_", name)
105
-
106
- def to_ruby_class_name(name):
107
- parts = re.split("[- ]", name)
108
- ruby_class_name = ""
109
- for part in parts:
110
- ruby_class_name = ruby_class_name + part[0].upper() + part[1:].lower()
111
- return ruby_class_name
112
-
113
- def params(self):
114
- buffer = []
115
- for f in self.arguments:
116
- buffer.append(convert_to_ruby(f))
117
- if self.hasContent:
118
- buffer.append("user_headers = nil")
119
- buffer.append("payload = \"\"")
120
- buffer.append("frame_size = nil")
121
- return buffer
122
-
123
- AmqpMethod.params = params
124
-
125
- def args(self):
126
- return map(lambda item: item.split(" ")[0], self.params())
127
-
128
- AmqpMethod.args = args
129
-
130
- def binary(self):
131
- method_id = self.klass.index << 16 | self.index
132
- return "0x%08X # %i, %i, %i" % (method_id, self.klass.index, self.index, method_id)
133
-
134
- AmqpMethod.binary = binary
135
-
136
- # helpers
137
- def render(path, **context):
138
- file = open(path)
139
- template = Template(file.read())
140
- return template.render(**context)
141
-
142
- def generateMain(type):
143
- def main(json_spec_path):
144
- spec = AmqpSpecObject(json_spec_path)
145
- spec.type = type
146
- print(render("codegen/protocol.rb.pytemplate", spec = spec))
147
-
148
- return main
149
-
150
- if __name__ == "__main__":
151
- do_main_dict({"client": generateMain("client")})
@@ -1,162 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from __future__ import print_function
4
-
5
- def genSingleEncode(spec, cValue, unresolved_domain):
6
- buffer = []
7
- type = spec.resolveDomain(unresolved_domain)
8
- if type == 'shortstr':
9
- buffer.append("buffer << %s.to_s.bytesize.chr" % (cValue,))
10
- buffer.append("buffer << %s.to_s" % (cValue,))
11
- elif type == 'longstr':
12
- buffer.append("buffer << [%s.to_s.bytesize].pack(PACK_UINT32)" % (cValue,))
13
- buffer.append("buffer << %s.to_s" % (cValue,))
14
- elif type == 'octet':
15
- buffer.append("buffer << [%s].pack(PACK_CHAR)" % (cValue,))
16
- elif type == 'short':
17
- buffer.append("buffer << [%s].pack(PACK_UINT16)" % (cValue,))
18
- elif type == 'long':
19
- buffer.append("buffer << [%s].pack(PACK_UINT32)" % (cValue,))
20
- elif type == 'longlong':
21
- buffer.append("buffer << AMQ::Pack.pack_uint64_big_endian(%s)" % (cValue,))
22
- elif type == 'timestamp':
23
- buffer.append("buffer << AMQ::Pack.pack_uint64_big_endian(%s)" % (cValue,))
24
- elif type == 'bit':
25
- raise "Can't encode bit in genSingleEncode"
26
- elif type == 'table':
27
- buffer.append("buffer << AMQ::Protocol::Table.encode(%s)" % (cValue,))
28
- else:
29
- raise "Illegal domain in genSingleEncode: {0}".format(type)
30
-
31
- return buffer
32
-
33
- def genSingleDecode(spec, field):
34
- cLvalue = field.ruby_name
35
- unresolved_domain = field.domain
36
-
37
- if cLvalue == "known_hosts":
38
- import sys
39
- print(field, field.ignored, file = sys.stderr)
40
-
41
- type = spec.resolveDomain(unresolved_domain)
42
- buffer = []
43
- if type == 'shortstr':
44
- buffer.append("length = data[offset, 1].unpack(PACK_CHAR).first")
45
- buffer.append("offset += 1")
46
- buffer.append("%s = data[offset, length]" % (cLvalue,))
47
- buffer.append("offset += length")
48
- elif type == 'longstr':
49
- buffer.append("length = data[offset, 4].unpack(PACK_UINT32).first")
50
- buffer.append("offset += 4")
51
- buffer.append("%s = data[offset, length]" % (cLvalue,))
52
- buffer.append("offset += length")
53
- elif type == 'octet':
54
- buffer.append("%s = data[offset, 1].unpack(PACK_CHAR).first" % (cLvalue,))
55
- buffer.append("offset += 1")
56
- elif type == 'short':
57
- buffer.append("%s = data[offset, 2].unpack(PACK_UINT16).first" % (cLvalue,))
58
- buffer.append("offset += 2")
59
- elif type == 'long':
60
- buffer.append("%s = data[offset, 4].unpack(PACK_UINT32).first" % (cLvalue,))
61
- buffer.append("offset += 4")
62
- elif type == 'longlong':
63
- buffer.append("%s = AMQ::Pack.unpack_uint64_big_endian(data[offset, 8]).first" % (cLvalue,))
64
- buffer.append("offset += 8")
65
- elif type == 'timestamp':
66
- buffer.append("%s = data[offset, 8].unpack(PACK_UINT64_BE).first" % (cLvalue,))
67
- buffer.append("offset += 8")
68
- elif type == 'bit':
69
- raise "Can't decode bit in genSingleDecode"
70
- elif type == 'table':
71
- buffer.append("table_length = Table.length(data[offset, 4])")
72
- buffer.append("%s = Table.decode(data[offset, table_length + 4])" % (cLvalue,))
73
- buffer.append("offset += table_length + 4")
74
- else:
75
- raise StandardError("Illegal domain '{0}' in genSingleDecode".format(type))
76
-
77
- return buffer
78
-
79
-
80
-
81
- def genSingleSimpleDecode(spec, field):
82
- cLvalue = field.ruby_name
83
- unresolved_domain = field.domain
84
-
85
- if cLvalue == "known_hosts":
86
- import sys
87
- print >> sys.stderr, field, field.ignored
88
-
89
- type = spec.resolveDomain(unresolved_domain)
90
- buffer = []
91
- if type == 'shortstr':
92
- buffer.append("data.to_s")
93
- elif type == 'longstr':
94
- buffer.append("data.to_s")
95
- elif type == 'octet':
96
- buffer.append("data.unpack(PACK_INT8).first")
97
- elif type == 'short':
98
- buffer.append("data.unpack(PACK_UINT16).first")
99
- elif type == 'long':
100
- buffer.append("data.unpack(PACK_UINT32).first")
101
- elif type == 'longlong':
102
- buffer.append("AMQ::Pack.unpack_uint64_big_endian(data).first")
103
- elif type == 'timestamp':
104
- buffer.append("Time.at(data.unpack(PACK_UINT64_BE).last)")
105
- elif type == 'bit':
106
- raise "Can't decode bit in genSingleDecode"
107
- elif type == 'table':
108
- buffer.append("Table.decode(data)")
109
- else:
110
- raise StandardError("Illegal domain '" + type + "' in genSingleSimpleDecode")
111
-
112
- return buffer
113
-
114
-
115
- def genEncodeMethodDefinition(spec, m):
116
- def finishBits():
117
- if bit_index is not None:
118
- buffer.append("buffer << [bit_buffer].pack(PACK_CHAR)")
119
-
120
- bit_index = None
121
- buffer = []
122
-
123
- for f in m.arguments:
124
- if spec.resolveDomain(f.domain) == 'bit':
125
- if bit_index is None:
126
- bit_index = 0
127
- buffer.append("bit_buffer = 0")
128
- if bit_index >= 8:
129
- finishBits()
130
- buffer.append("bit_buffer = 0")
131
- bit_index = 0
132
- buffer.append("bit_buffer = bit_buffer | (1 << %d) if %s" % (bit_index, f.ruby_name))
133
- bit_index = bit_index + 1
134
- else:
135
- finishBits()
136
- bit_index = None
137
- buffer += genSingleEncode(spec, f.ruby_name, f.domain)
138
-
139
- finishBits()
140
- return buffer
141
-
142
- def genDecodeMethodDefinition(spec, m):
143
- buffer = []
144
- bitindex = None
145
- for f in m.arguments:
146
- if spec.resolveDomain(f.domain) == 'bit':
147
- if bitindex is None:
148
- bitindex = 0
149
- if bitindex >= 8:
150
- bitindex = 0
151
- if bitindex == 0:
152
- buffer.append("bit_buffer = data[offset, 1].unpack(PACK_CHAR).first")
153
- buffer.append("offset += 1")
154
- buffer.append("%s = (bit_buffer & (1 << %d)) != 0" % (f.ruby_name, bitindex))
155
- #### TODO: ADD bitindex TO THE buffer
156
- else:
157
- buffer.append("%s = (bit_buffer & (1 << %d)) != 0" % (f.ruby_name, bitindex))
158
- bitindex = bitindex + 1
159
- else:
160
- bitindex = None
161
- buffer += genSingleDecode(spec, f)
162
- return buffer