| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| require 'qpid/packer.rb' |
| require 'iconv' |
| |
| module Qpid |
| |
| class Codec |
| |
| include Qpid::Packer |
| |
| def initialize(spec = "") |
| @spec = spec |
| end |
| |
| def write_void(v) |
| unless v.nil? |
| raise Exception.new("void not nil: #{v}") |
| end |
| end |
| |
| def read_void |
| return nil |
| end |
| |
| def write_bit(b) |
| unless b |
| raise Exception.new("bit is nil: #{b}") |
| end |
| end |
| |
| def read_bit |
| return true |
| end |
| |
| def read_uint8 |
| return unpack("C", 1) |
| end |
| |
| def write_uint8(n) |
| return pack("C", n) |
| end |
| |
| def read_int8 |
| return unpack("c", 1) |
| end |
| |
| def write_int8(n) |
| pack("c", n) |
| end |
| |
| def read_char |
| return unpack("c", 1) |
| end |
| |
| def write_char(c) |
| pack("c") |
| end |
| |
| def read_boolean |
| return read_uint8 != 0 |
| end |
| |
| def write_boolean(b) |
| n = 0 |
| n = 1 if b != 0 |
| write_uint8(n) |
| end |
| |
| def read_uint16 |
| return unpack("n", 2) |
| end |
| |
| def write_uint16(n) |
| pack("n", n) |
| end |
| |
| def read_int16 |
| # XXX: holy moly.. pack/unpack doesn't have signed network byte order. Crazy hackery. |
| val = unpack("n", 2) |
| val -= 2 ** 16 if val >= 2 ** 15 |
| return val |
| end |
| |
| def write_int16(n) |
| # XXX: Magically this one works even though it's not signed. |
| pack("n", n) |
| end |
| |
| def read_uint32 |
| return unpack("N", 4) |
| end |
| |
| def write_uint32(n) |
| pack("N", n) |
| end |
| |
| def read_int32 |
| # Again no pack/unpack for signed int |
| return unpack("N", 4) |
| end |
| |
| def write_int32(n) |
| # FIXME |
| pack("N", n) |
| end |
| |
| def read_float |
| return unpack("g", 4) |
| end |
| |
| def write_float(n) |
| pack("g", n) |
| end |
| |
| def read_sequence_no |
| return read_uint32.to_serial |
| end |
| |
| def write_sequence_no(n) |
| write_uint32(n.value) |
| end |
| |
| def encode_64bit(num, signed = false) |
| b = [] |
| |
| if num < 0 && signed |
| num += 2 ** 64 |
| end |
| |
| (0..7).each do |c| |
| d = 7 - c |
| b[c] = (num & (0xff << d * 8)) >> d * 8 |
| end |
| pack('C8', *b) |
| end |
| |
| |
| def decode_64bit(signed = false) |
| # Silly ruby pack/unpack does not implement 64 bit network byte order |
| # encode/decode. |
| a = unpack('C8', 8) |
| num = 0 |
| (0..7).each do |c| |
| d = 7 - c |
| num |= a[c] << 8 * d |
| end |
| |
| if signed && num >= 2 ** 63 |
| num -= 2 ** 64 |
| end |
| return num |
| end |
| |
| def read_uint64 |
| return decode_64bit |
| end |
| |
| def write_uint64(n) |
| encode_64bit(n) |
| end |
| |
| def read_int64 |
| return decode_64bit(signed = true) |
| end |
| |
| def write_int64(n) |
| encode_64bit(n, signed = true) |
| end |
| |
| def read_datetime |
| return read_uint64 |
| end |
| |
| def write_datetime(n) |
| write_uint64(n) |
| end |
| |
| def read_double |
| return unpack("G", 8) |
| end |
| |
| def write_double(n) |
| pack("G", n) |
| end |
| |
| def read_vbin8 |
| # XXX |
| return read(read_uint8) |
| end |
| |
| def write_vbin8(b) |
| # XXX |
| write_uint8(b.length) |
| write(b) |
| end |
| |
| def read_str8 |
| # FIXME: Check iconv.. I think this will throw if there are odd characters. |
| return Iconv.conv("ASCII", "UTF-8", read_vbin8) |
| end |
| |
| def write_str8(s) |
| write_vbin8(Iconv.conv("UTF-8", "ASCII", s)) |
| end |
| |
| def read_str16 |
| return Iconv.conv("ASCII", "UTF-8", read_vbin16) |
| end |
| |
| def write_str16(s) |
| write_vbin16(Iconv.conv("UTF-8", "ASCII", s)) |
| end |
| |
| def read_vbin16 |
| # XXX: Using read method? |
| return read(read_uint16) |
| end |
| |
| def write_vbin16(b) |
| write_uint16(b.length) |
| write(b) |
| end |
| |
| def read_sequence_set |
| # FIXME: Need datatypes |
| result = RangedSet.new |
| size = read_uint16 |
| nranges = size / 8 |
| nranges.times do |i| |
| lower = read_sequence_no |
| upper = read_sequence_no |
| result.add(lower, upper) |
| end |
| return result |
| end |
| |
| def write_sequence_set(ss) |
| size = 8 * ss.ranges.length |
| write_uint16(size) |
| ss.ranges.each do |range| |
| write_sequence_no(range.lower) |
| write_sequence_no(range.upper) |
| end |
| end |
| |
| def read_vbin32 |
| return read(read_uint32) |
| end |
| |
| def write_vbin32(b) |
| write_uint32(b.length) |
| write(b) |
| end |
| |
| def write_map(m) |
| sc = StringCodec.new(@spec) |
| unless m.nil? |
| sc.write_uint32(m.size) |
| m.each do |k, v| |
| unless type = @spec.encoding(v.class) |
| raise Exception.new("no encoding for: #{v.class}") |
| end |
| sc.write_str8(k) |
| sc.write_uint8(type.code) |
| type.encode(sc, v) |
| end |
| end |
| write_vbin32(sc.encoded) |
| end |
| |
| def read_map |
| sc = StringCodec.new(@spec, read_vbin32) |
| return nil unless sc.encoded |
| count = sc.read_uint32 |
| result = nil |
| if count |
| result = {} |
| until sc.encoded.empty? |
| k = sc.read_str8 |
| code = sc.read_uint8 |
| type = @spec.types[code] |
| v = type.decode(sc) |
| result[k] = v |
| end |
| end |
| return result |
| end |
| |
| def write_array(a) |
| sc = StringCodec.new(@spec) |
| unless a.nil? |
| if a.length > 0 |
| type = @spec.encoding(a[0].class) |
| else |
| type = @spec.encoding(nil.class) |
| end |
| sc.write_uint8(type.code) |
| sc.write_uint32(a.size) |
| a.each { |o| type.encode(sc, o) } |
| end |
| write_vbin32(sc.encoded) |
| end |
| |
| def read_array |
| sc = StringCodec.new(@spec, read_vbin32) |
| return nil if not sc.encoded |
| type = @spec.types[sc.read_uint8] |
| count = sc.read_uint32 |
| result = nil |
| if count |
| result = [] |
| count.times { |i| result << (type.decode(sc)) } |
| end |
| return result |
| end |
| |
| def write_list(l) |
| sc = StringCodec.new(@spec) |
| unless l.nil? |
| sc.write_uint32(l.length) |
| l.each do |o| |
| type = @spec.encoding(o.class) |
| sc.write_uint8(type.code) |
| type.encode(sc, o) |
| end |
| end |
| write_vbin32(sc.encoded) |
| end |
| |
| def read_list |
| sc = StringCodec.new(@spec, read_vbin32) |
| return nil if not sc.encoded |
| count = sc.read_uint32 |
| result = nil |
| if count |
| result = [] |
| count.times do |i| |
| type = @spec.types[sc.read_uint8] |
| result << type.decode(sc) |
| end |
| end |
| return result |
| end |
| |
| def read_struct32 |
| size = read_uint32 |
| code = read_uint16 |
| type = @spec.structs[code] |
| # XXX: BLEH! |
| fields = type.decode_fields(self) |
| return Qpid::struct(type, fields) |
| end |
| |
| def write_struct32(value) |
| type = value.type |
| sc = StringCodec.new(@spec) |
| sc.write_uint16(type.code) |
| type.encode_fields(sc, value) |
| write_vbin32(sc.encoded) |
| end |
| |
| def read_control |
| cntrl = @spec.controls[read_uint16] |
| return Qpid::struct(cntrl, cntrl.decode_fields(self)) |
| end |
| |
| def write_control(ctrl) |
| type = ctrl.type |
| write_uint16(type.code) |
| type.encode_fields(self, ctrl) |
| end |
| |
| def read_command |
| type = @spec.commands[read_uint16] |
| hdr = @spec[:header].decode(self) |
| cmd = Qpid::struct(type, type.decode_fields(self)) |
| return hdr, cmd |
| end |
| |
| def write_command(hdr, cmd) |
| type = cmd.type |
| write_uint16(type.code) |
| hdr.type.encode(self, hdr) |
| type.encode_fields(self, cmd) |
| end |
| |
| def read_size(width) |
| if width > 0 |
| return send(:"read_uint#{width * 8}") |
| end |
| end |
| |
| def write_size(width, n) |
| if width > 0 |
| send(:"write_uint#{width * 8}", n) |
| end |
| end |
| |
| def read_uuid |
| return unpack("A16", 16) |
| end |
| |
| def write_uuid(s) |
| pack("A16", s) |
| end |
| |
| def read_bin128 |
| return unpack("A16", 16) |
| end |
| |
| def write_bin128(b) |
| pack("A16", b) |
| end |
| |
| end |
| |
| class StringCodec < Codec |
| |
| def initialize(spec, encoded = "") |
| @spec = spec |
| @encoded = encoded |
| end |
| |
| attr_reader :encoded |
| |
| def write(s) |
| @encoded += s |
| end |
| |
| def read(n) |
| return "" if n.nil? |
| result = @encoded[0...n] |
| @encoded = @encoded[n...@encoded.size] || "" |
| return result |
| end |
| end |
| end |