forked from bastibe/transplant
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dumpmsgpack.m
201 lines (182 loc) · 6.73 KB
/
dumpmsgpack.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
%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);
end
function msgpack = dump(data)
% convert numeric matrices to cell matrices since msgpack doesn't know matrices
if (isnumeric(data) || islogical(data)) && ~isscalar(data) && ~isempty(data)
data = num2cell(data);
end
% convert character matrices to cell of strings or cell matrices
if ischar(data) && ~isvector(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)
data = containers.Map(fieldnames(data), struct2cell(data));
end
if isnumeric(data) && isempty(data)
msgpack = uint8(192); % encode nil
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)
b11110000 = 240;
% 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 = uint8(bitor(-value, b11100000));
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 = 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
msgpack = [msgpack, dump(value{n})];
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
msgpack = [msgpack, dump(keys{n}), dump(values{n})];
end
end