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