1 /* 2 * XDR - A D language implementation of the External Data Representation 3 * Copyright (C) 2015 Paul O'Neil 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 //! Provides an implementation of RFC 4506. 20 module xdr; 21 22 import std.algorithm : copy, map; 23 import std.bitmanip; 24 import std.range; 25 import std.traits; 26 27 version (unittest) 28 { 29 import std.exception : assertThrown; 30 } 31 32 void put(T, Output)(ref Output output, T val) 33 if (isOutputRange!(Output, ubyte) 34 && T.sizeof % 4 == 0 && (isIntegral!T || isFloatingPoint!T)) 35 { 36 std.range.put(output, nativeToBigEndian(val)[]); 37 } 38 39 void put(T: bool, Output)(ref Output output, bool val) 40 if (isOutputRange!(Output, ubyte)) 41 { 42 if (val) 43 { 44 output.put!int(1); 45 } 46 else 47 { 48 output.put!int(0); 49 } 50 } 51 // The existence of this method seems to solve const/overload problems 52 void put(Output)(ref Output output, bool val) if (isOutputRange!(Output, ubyte)) 53 { 54 output.put!bool(val); 55 } 56 57 void put(ulong len, Output)(ref Output output, in ubyte[len] data) 58 if (isOutputRange!(Output, ubyte) && len % 4 == 0) 59 { 60 std.range.put(output, data[]); 61 } 62 63 void put(ulong len, Output)(ref Output output, in ubyte[len] data) 64 if (isOutputRange!(Output, ubyte) && len % 4 != 0) 65 { 66 std.range.put(output, data[]); 67 enum padding = 4 - (len % 4); 68 std.range.put(output, [0, 0, 0][0 .. padding]); 69 } 70 71 void put(Array, Output)(ref Output output, in Array data) 72 if (isOutputRange!(Output, ubyte) && isStaticArray!Array) 73 { 74 foreach (const ref elem; data) 75 { 76 output.put(elem); 77 } 78 } 79 80 void put(Array, Output)(ref Output output, in Array data) 81 if (isOutputRange!(Output, ubyte) 82 && isDynamicArray!Array && !is(Array == ubyte[]) && !is(Array == string) 83 && __traits(compiles, output.put!(ElementType!Array)(data[0]))) 84 in { 85 assert(data.length <= uint.max); 86 } 87 body { 88 output.put!uint(cast(uint)data.length); 89 foreach (const ref elem; data) 90 { 91 output.put(elem); 92 } 93 } 94 95 void put(T: ubyte[], Output)(ref Output output, in T data) 96 if (isOutputRange!(Output, ubyte)) 97 in { 98 assert(data.length <= uint.max); 99 } 100 body { 101 output.put!uint(cast(uint)data.length); 102 std.range.put(output, data); 103 if (data.length % 4 > 0) 104 { 105 immutable pad_length = 4 - (data.length % 4); 106 ubyte[] padding = [0, 0, 0]; 107 std.range.put(output, padding[0 .. pad_length]); 108 } 109 } 110 111 // FIXME combine with ubyte[] 112 void put(T: string, Output)(ref Output output, in T data) 113 if (isOutputRange!(Output, ubyte)) 114 in { 115 assert(data.length <= uint.max); 116 } 117 body { 118 output.put!uint(cast(uint)data.length); 119 std.range.put(output, data); 120 if (data.length % 4 > 0) 121 { 122 immutable pad_length = 4 - (data.length % 4); 123 ubyte[] padding = [0, 0, 0]; 124 std.range.put(output, padding[0 .. pad_length]); 125 } 126 } 127 128 void put(T, Output)(ref Output output, in T data) 129 if (isOutputRange!(Output, ubyte) 130 && isAggregateType!T && !hasIndirections!T) 131 { 132 foreach (elem; data.tupleof) 133 { 134 output.put(elem); 135 } 136 } 137 138 unittest 139 { 140 ubyte[] serializer; 141 142 assert(__traits(compiles, serializer.put!char(2)) == false); 143 assert(__traits(compiles, serializer.put!dchar(2)) == false); 144 assert(__traits(compiles, serializer.put!wchar(2)) == false); 145 146 assert(__traits(compiles, serializer.put!byte(2)) == false); 147 assert(__traits(compiles, serializer.put!ubyte(2)) == false); 148 assert(__traits(compiles, serializer.put!short(2)) == false); 149 assert(__traits(compiles, serializer.put!ushort(2)) == false); 150 151 assert(__traits(compiles, serializer.put!int(2)) == true); 152 assert(__traits(compiles, serializer.put!uint(2)) == true); 153 assert(__traits(compiles, serializer.put!long(2)) == true); 154 assert(__traits(compiles, serializer.put!ulong(2)) == true); 155 156 assert(__traits(compiles, serializer.put!bool(true)) == true); 157 158 assert(__traits(compiles, serializer.put!float(1.0)) == true); 159 assert(__traits(compiles, serializer.put!double(1.0)) == true); 160 161 assert(__traits(compiles, serializer.put!(ubyte[])([])) == true); 162 assert(__traits(compiles, serializer.put!(string)("")) == true); 163 164 assert(__traits(compiles, serializer.put!(short[])([])) == false); 165 assert(__traits(compiles, serializer.put!(ushort[])([])) == false); 166 167 assert(__traits(compiles, serializer.put!(short[2])([1, 2])) == false); 168 169 assert(__traits(compiles, serializer.put!(int[2])([1, 2])) == true); 170 assert(__traits(compiles, serializer.put!(uint[2])([1, 2])) == true); 171 assert(__traits(compiles, serializer.put!(long[2])([1, 2])) == true); 172 assert(__traits(compiles, serializer.put!(ulong[2])([1, 2])) == true); 173 // Commented out pending std.bitmanip.EndianSwap stuff 174 // From std.bitmanip.d:2210 175 // private union EndianSwapper(T) 176 // if(canSwapEndianness!T) 177 // { 178 // Unqual!T value; 179 // ubyte[T.sizeof] array; 180 // 181 // static if(is(FloatingPointTypeOf!T == float)) 182 // uint intValue; 183 // else static if(is(FloatingPointTypeOf!T == double)) 184 // ulong intValue; 185 // 186 // } 187 // The static ifs fail because FloatingPointTypeOf!(const(float)) == const(float), not float 188 // assert(__traits(compiles, serializer.put!(float[2])([1, 2])) == true); 189 // assert(__traits(compiles, serializer.put!(double[2])([1, 2])) == true); 190 assert(__traits(compiles, serializer.put!(bool[2])([true, false])) == true); 191 } 192 193 unittest 194 { 195 ubyte[] outBuffer = new ubyte[4]; 196 ubyte[] movingBuffer = outBuffer[]; 197 198 movingBuffer.put!int(4); 199 assert(outBuffer == [0, 0, 0, 4]); 200 } 201 202 unittest 203 { 204 ubyte[] outBuffer = new ubyte[4]; 205 ubyte[] movingBuffer = outBuffer[]; 206 207 movingBuffer.put!bool(true); 208 assert(outBuffer == [0, 0, 0, 1]); 209 } 210 211 unittest 212 { 213 ubyte[] outBuffer = new ubyte[8]; 214 ubyte[] movingBuffer = outBuffer[]; 215 216 movingBuffer.put!long(4); 217 assert(outBuffer == [0, 0, 0, 0, 0, 0, 0, 4]); 218 } 219 220 unittest 221 { 222 ubyte[] outBuffer = new ubyte[8]; 223 ubyte[] movingBuffer = outBuffer[]; 224 225 ubyte[] data = [1, 2, 3, 4]; 226 movingBuffer.put(data); 227 assert(outBuffer == [0, 0, 0, 4, 1, 2, 3, 4]); 228 } 229 unittest 230 { 231 ubyte[] outBuffer = new ubyte[8]; 232 ubyte[] movingBuffer = outBuffer[]; 233 234 ubyte[] data = [1, 2, 3]; 235 movingBuffer.put(data); 236 assert(outBuffer == [0, 0, 0, 3, 1, 2, 3, 0]); 237 } 238 239 unittest 240 { 241 ubyte[] outBuffer = new ubyte[12]; 242 ubyte[] movingBuffer = outBuffer[]; 243 244 string data = "hello"; 245 movingBuffer.put(data); 246 assert(outBuffer == [0, 0, 0, 5, 'h', 'e', 'l', 'l', 'o', 0, 0, 0]); 247 } 248 249 unittest 250 { 251 ubyte[] outBuffer = new ubyte[8]; 252 ubyte[] movingBuffer = outBuffer[]; 253 254 int[2] data = [1, 2]; 255 movingBuffer.put(data); 256 assert(outBuffer == [0, 0, 0, 1, 0, 0, 0, 2]); 257 } 258 259 unittest 260 { 261 ubyte[] outBuffer = new ubyte[8]; 262 ubyte[] movingBuffer = outBuffer[]; 263 264 struct AB 265 { 266 int a; 267 int b; 268 } 269 270 AB ab = {1, 2}; 271 movingBuffer.put(ab); 272 assert(outBuffer == [0, 0, 0, 1, 0, 0, 0, 2]); 273 } 274 275 class EndOfInput : Exception 276 { 277 this(string file = __FILE__, size_t line = __LINE__) 278 { 279 super("Reached end of input while extracting data.", file, line); 280 } 281 } 282 283 class NotABool : Exception 284 { 285 this(int intVal, string file = __FILE__, size_t line = __LINE__) 286 { 287 import std.conv : to; 288 super("Tried to decode into a bool, but " ~ std.conv.to!string(intVal) ~ " is not a valid XDR bool.", file, line); 289 } 290 } 291 292 // Uses popFrontN, so it may read the end of input without 293 // doing anything useful with it 294 T get(T, Input)(ref Input input) 295 if ((isInputRange!Input && is(ElementType!Input == ubyte)) 296 && (T.sizeof % 4 == 0 && (isIntegral!T || isFloatingPoint!T))) 297 { 298 ubyte[T.sizeof] buffer; 299 ubyte[] remaining = copy(input.take(T.sizeof), buffer[]); 300 if (remaining.length != 0) 301 { 302 throw new EndOfInput(); 303 } 304 // Only need to pop front here if the input.take() does not. 305 // For ubyte[], take does not popFront, but maybe for InputRanges 306 // that are not sliceable it does? 307 input.popFrontExactly(T.sizeof); 308 return bigEndianToNative!T(buffer); 309 } 310 311 bool get(T: bool, Input)(ref Input input) 312 if (isInputRange!Input && is(ElementType!Input == ubyte)) 313 { 314 immutable intVal = input.get!int(); 315 if (intVal == 0) 316 { 317 return false; 318 } 319 else if (intVal == 1) 320 { 321 return true; 322 } 323 else 324 { 325 throw new NotABool(intVal); 326 } 327 } 328 329 auto get(T, Input)(ref Input input) 330 if (isInputRange!Input && is(ElementType!Input == ubyte) 331 && is(T == ubyte[length], ulong length) 332 && isStaticArray!T) 333 { 334 static if (T.length % 4 != 0) 335 { 336 enum pad_length = 4 - (T.length % 4); 337 } 338 else 339 { 340 enum pad_length = 0; 341 } 342 343 auto result = input.take(T.length); 344 input.popFrontExactly(T.length + pad_length); 345 return result; 346 } 347 348 Array get(Array, Input)(ref Input input) 349 if (isInputRange!Input && is(ElementType!Input == ubyte) 350 && is(Array == Element[length], Element, ulong length) 351 && !is(ElementType!Array == ubyte) 352 && isStaticArray!Array) 353 { 354 alias Element = ElementType!Array; 355 enum length = Array.length; 356 static if (Element.sizeof % 4 == 0) 357 { 358 enum elementSize = Element.sizeof; 359 } 360 else 361 { 362 enum elementSize = Element.sizeof + 4 - (Element.sizeof % 4); 363 } 364 365 Array result; 366 // FIXME check on whether I need to pop front afterwards 367 copy(input.take(elementSize * length).chunks(elementSize).map!((chunk)=> chunk.get!Element()), result[]); 368 input.popFrontExactly(elementSize * length); 369 return result; 370 } 371 372 auto get(Array, Input)(ref Input input) 373 if (isInputRange!Input && is(ElementType!Input == ubyte) 374 && isDynamicArray!Array && !is(Array == ubyte[]) && !is(Array == string)) 375 { 376 alias Element = ElementType!Array; 377 378 uint length = input.get!uint(); 379 static if (Element.sizeof % 4 == 0) 380 { 381 enum elementSize = Element.sizeof; 382 } 383 else 384 { 385 enum elementSize = Element.sizeof + 4 - (Element.sizeof % 4); 386 } 387 388 Array result = new Element[length]; 389 // FIXME check on whether I need to pop front afterwards 390 copy(input.take(elementSize * length).chunks(elementSize).map!((chunk)=> chunk.get!Element()), result[]); 391 input.popFrontExactly(elementSize * length); 392 return result; 393 } 394 395 auto get(Array, Input)(ref Input input) 396 if (isInputRange!Input && is(ElementType!Input == ubyte) 397 && isDynamicArray!Array && (is(Array == ubyte[]) || is(Array == string))) 398 { 399 uint length = input.get!uint(); 400 auto result = input.take(length); 401 402 input.popFrontExactly(length); 403 if (length % 4 > 0) 404 { 405 input.popFrontExactly(4 - (length % 4)); 406 } 407 return result; 408 } 409 410 T get(T, Input)(ref Input input) 411 if (isInputRange!Input && is(ElementType!Input == ubyte) 412 && isAggregateType!T && !hasIndirections!T) 413 { 414 T result; 415 foreach (ref elem; result.tupleof) 416 { 417 elem = input.get!(typeof(elem)); 418 } 419 420 return result; 421 } 422 423 unittest 424 { 425 ubyte[] inBuffer = [0, 0, 0, 4]; 426 427 assert(inBuffer.get!int() == 4); 428 } 429 unittest 430 { 431 ubyte[] inBuffer = [0, 0, 0, 4, 0, 0, 0, 12]; 432 433 assert(inBuffer.get!int() == 4); 434 assert(inBuffer.get!int() == 12); 435 436 assertThrown!EndOfInput(inBuffer.get!int()); 437 } 438 439 unittest 440 { 441 ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; 442 443 assert(inBuffer.get!bool() == true); 444 assert(inBuffer.get!bool() == false); 445 assertThrown!NotABool(inBuffer.get!bool()); 446 assertThrown!EndOfInput(inBuffer.get!bool()); 447 } 448 449 unittest 450 { 451 ubyte[] inBuffer = [0, 0, 0, 0, 0, 0, 0, 4]; 452 453 assert(inBuffer.get!long() == 4); 454 assertThrown!EndOfInput(inBuffer.get!long()); 455 } 456 457 unittest 458 { 459 ubyte[] inBuffer = [0, 0, 0, 4, 1, 2, 3, 4]; 460 461 assert(inBuffer.get!(ubyte[])() == [1, 2, 3, 4]); 462 assertThrown!EndOfInput(inBuffer.get!(ubyte[])()); 463 } 464 465 unittest 466 { 467 ubyte[] inBuffer = [0, 0, 0, 3, 1, 2, 3, 0]; 468 469 assert(inBuffer.get!(ubyte[])() == [1, 2, 3]); 470 assertThrown!EndOfInput(inBuffer.get!int()); 471 } 472 473 unittest 474 { 475 ubyte[] inBuffer = [0, 0, 0, 5, 'h', 'e', 'l', 'l', 'o', 0, 0, 0]; 476 477 assert(inBuffer.get!string() == "hello"); 478 assertThrown!EndOfInput(inBuffer.get!int()); 479 } 480 481 unittest 482 { 483 ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 2]; 484 485 assert(inBuffer.get!(int[2])() == [1, 2]); 486 assertThrown!EndOfInput(inBuffer.get!int()); 487 } 488 489 unittest 490 { 491 ubyte[] inBuffer = [0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2]; 492 493 assert(inBuffer.get!(int[])() == [1, 2]); 494 assertThrown!EndOfInput(inBuffer.get!int()); 495 } 496 497 unittest 498 { 499 ubyte[] inBuffer = [1, 2, 0, 0]; 500 501 assert(inBuffer.get!(ubyte[2])() == [1, 2]); 502 assertThrown!EndOfInput(inBuffer.get!int()); 503 } 504 505 unittest 506 { 507 ubyte[] inBuffer = [1, 2, 3, 4]; 508 509 assert(inBuffer.get!(ubyte[4])() == [1, 2, 3, 4]); 510 assertThrown!EndOfInput(inBuffer.get!int()); 511 } 512 513 unittest 514 { 515 ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 2]; 516 517 struct AB 518 { 519 int a; 520 int b; 521 } 522 523 AB ab = {1, 2}; 524 assert(inBuffer.get!AB() == ab); 525 assertThrown!EndOfInput(inBuffer.get!int()); 526 }