ucftools/matlab/+msgpack/dumpmsgpack.m

227 lines
7.7 KiB
Matlab

%DUMPMSGPACK dumps Matlab data structures as a msgpack data
% DUMPMSGPACK(DATA)
% recursively walks through DATA and creates a msgpack byte buffer from it.
% - strings are converted to strings
% - scalars are converted to numbers
% - logicals are converted to `true` and `false`
% - arrays are converted to arrays of numbers
% - matrices are converted to arrays of arrays of numbers
% - empty matrices are converted to nil
% - cell arrays are converted to arrays
% - cell matrices are converted to arrays of arrays
% - struct arrays are converted to arrays of maps
% - structs and container.Maps are converted to maps
% - function handles and matlab objects will raise an error.
%
% There is no way of encoding bins or exts
% (c) 2016 Bastian Bechtold
% This code is licensed under the BSD 3-clause license
function msgpack = dumpmsgpack(data)
msgpack = dump(data);
% collect all parts in a cell array to avoid frequent uint8
% concatenations.
msgpack = [msgpack{:}];
end
function msgpack = dump(data)
% convert numeric matrices to cell matrices since msgpack doesn't know matrices
if (isnumeric(data) || islogical(data)) && ...
~(isvector(data) && isa(data, 'uint8')) && ~isscalar(data) && ~isempty(data)
data = num2cell(data);
end
% convert character matrices to cell of strings or cell matrices
if ischar(data) && ~(isvector(data)||isempty(data)) && ndims(data) == 2
data = cellstr(data);
elseif ischar(data) && ~isvector(data)
data = num2cell(data);
end
% convert struct arrays to cell of structs
if isstruct(data) && ~isscalar(data)
data = num2cell(data);
end
% standardize on always using maps instead of structs
if isstruct(data)
if ~isempty(fieldnames(data))
data = containers.Map(fieldnames(data), struct2cell(data));
else
data = containers.Map();
end
end
if isnumeric(data) && isempty(data)
msgpack = {uint8(192)}; % encode nil
elseif isa(data, 'uint8') && numel(data) > 1
msgpack = dumpbin(data);
elseif islogical(data)
if data
msgpack = {uint8(195)}; % encode true
else
msgpack = {uint8(194)}; % encode false
end
elseif isinteger(data)
msgpack = {dumpinteger(data)};
elseif isnumeric(data)
msgpack = {dumpfloat(data)};
elseif ischar(data)
msgpack = dumpstring(data);
elseif iscell(data)
msgpack = dumpcell(data);
elseif isa(data, 'containers.Map')
msgpack = dumpmap(data);
else
error('transplant:dumpmsgpack:unknowntype', ...
['Unknown type "' class(data) '"']);
end
end
function bytes = scalar2bytes(value)
% reverse byte order to convert from little endian to big endian
bytes = typecast(swapbytes(value), 'uint8');
end
function msgpack = dumpinteger(value)
% if the values are small enough, encode as fixnum:
if value >= 0 && value < 128
% first bit is 0, last 7 bits are value
msgpack = uint8(value);
return
elseif value < 0 && value > -32
% first three bits are 111, last 5 bytes are value
msgpack = typecast(int8(value), 'uint8');
return
end
% otherwise, encode by type:
switch class(value)
case 'uint8' % encode as uint8
msgpack = uint8([204, value]);
case 'uint16' % encode as uint16
msgpack = uint8([205, scalar2bytes(value)]);
case 'uint32' % encode as uint32
msgpack = uint8([206, scalar2bytes(value)]);
case 'uint64' % encode as uint64
msgpack = uint8([207, scalar2bytes(value)]);
case 'int8' % encode as int8
msgpack = uint8([208, scalar2bytes(value)]);
case 'int16' % encode as int16
msgpack = uint8([209, scalar2bytes(value)]);
case 'int32' % encode as int32
msgpack = uint8([210, scalar2bytes(value)]);
case 'int64' % encode as int64
msgpack = uint8([211, scalar2bytes(value)]);
otherwise
error('transplant:dumpmsgpack:unknowninteger', ...
['Unknown integer type "' class(value) '"']);
end
end
function msgpack = dumpfloat(value)
% do double first, as it is more common in Matlab
if isa(value, 'double') % encode as float64
msgpack = uint8([203, scalar2bytes(value)]);
elseif isa(value, 'single') % encode as float32
msgpack = uint8([202, scalar2bytes(value)]);
else
error('transplant:dumpmsgpack:unknownfloat', ...
['Unknown float type "' class(value) '"']);
end
end
function msgpack = dumpstring(value)
b10100000 = 160;
encoded = unicode2native(value, 'utf-8');
len = length(encoded);
if len < 32 % encode as fixint:
% first three bits are 101, last 5 are length:
msgpack = {uint8(bitor(len, b10100000)), encoded};
elseif len < 256 % encode as str8
msgpack = {uint8([217, len]), encoded};
elseif len < 2^16 % encode as str16
msgpack = {uint8(218), scalar2bytes(uint16(len)), encoded};
elseif len < 2^32 % encode as str32
msgpack = {uint8(219), scalar2bytes(uint32(len)), encoded};
else
error('transplant:dumpmsgpack:stringtoolong', ...
sprintf('String is too long (%d bytes)', len));
end
end
function msgpack = dumpbin(value)
len = length(value);
if len < 256 % encode as bin8
msgpack = {uint8([196, len]) value(:)'};
elseif len < 2^16 % encode as bin16
msgpack = {uint8(197), scalar2bytes(uint16(len)), value(:)'};
elseif len < 2^32 % encode as bin32
msgpack = {uint8(198), scalar2bytes(uint32(len)), value(:)'};
else
error('transplant:dumpmsgpack:bintoolong', ...
sprintf('Bin is too long (%d bytes)', len));
end
end
function msgpack = dumpcell(value)
b10010000 = 144;
% Msgpack can only work with 1D-arrays. Thus, Convert a
% multidimensional AxBxC array into a cell-of-cell-of-cell, so
% that indexing value{a, b, c} becomes value{a}{b}{c}.
if length(value) ~= prod(size(value))
for n=ndims(value):-1:2
value = cellfun(@squeeze, num2cell(value, n), ...
'uniformoutput', false);
end
end
% write header
len = length(value);
if len < 16 % encode as fixarray
% first four bits are 1001, last 4 are length
msgpack = {uint8(bitor(len, b10010000))};
elseif len < 2^16 % encode as array16
msgpack = {uint8(220), scalar2bytes(uint16(len))};
elseif len < 2^32 % encode as array32
msgpack = {uint8(221), scalar2bytes(uint32(len))};
else
error('transplant:dumpmsgpack:arraytoolong', ...
sprintf('Array is too long (%d elements)', len));
end
% write values
for n=1:len
stuff = dump(value{n});
msgpack = [msgpack stuff{:}];
end
end
function msgpack = dumpmap(value)
b10000000 = 128;
% write header
len = length(value);
if len < 16 % encode as fixmap
% first four bits are 1000, last 4 are length
msgpack = {uint8(bitor(len, b10000000))};
elseif len < 2^16 % encode as map16
msgpack = {uint8(222), scalar2bytes(uint16(len))};
elseif len < 2^32 % encode as map32
msgpack = {uint8(223), scalar2bytes(uint32(len))};
else
error('transplant:dumpmsgpack:maptoolong', ...
sprintf('Map is too long (%d elements)', len));
end
% write key-value pairs
keys = value.keys();
values = value.values();
for n=1:len
keystuff = dump(keys{n});
valuestuff = dump(values{n});
msgpack = [msgpack, keystuff{:}, valuestuff{:}];
end
end