1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 module capnproto.Serialize; 23 24 import std.array : Appender; 25 26 import java.io.IOException; 27 import java.nio.channels.ReadableByteChannel; 28 import java.nio.channels.WritableByteChannel; 29 import java.nio.ByteBuffer; 30 31 import capnproto.Constants; 32 import capnproto.DecodeException; 33 import capnproto.MessageBuilder; 34 import capnproto.MessageReader; 35 import capnproto.ReaderOptions; 36 37 struct Serialize 38 { 39 public: //Methods. 40 static void fillBuffer(ref ByteBuffer buffer, ReadableByteChannel bc) 41 { 42 while(!buffer.empty()) 43 { 44 auto r = bc.read(buffer); 45 if(r < 0) 46 throw new IOException("Premature EOF."); 47 if(r == 0) 48 break; 49 } 50 } 51 52 static MessageReader read(ReadableByteChannel bc) 53 { 54 return read(bc, cast(ReaderOptions)ReaderOptions.DEFAULT_READER_OPTIONS); 55 } 56 57 static MessageReader read(ReadableByteChannel bc, ReaderOptions options) 58 { 59 auto firstWord = makeByteBuffer(Constants.BYTES_PER_WORD); 60 fillBuffer(firstWord, bc); 61 62 int segmentCount = 1 + firstWord.get!int(0); 63 64 int segment0Size = 0; 65 if(segmentCount > 0) 66 segment0Size = firstWord.get!int(4); 67 68 int totalWords = segment0Size; 69 70 if(segmentCount > 512) 71 throw new IOException("Too many segments."); 72 73 //In words. 74 Appender!(int[]) moreSizes; 75 76 if(segmentCount > 1) 77 { 78 auto moreSizesRaw = makeByteBuffer(4 * (segmentCount & ~1)); 79 fillBuffer(moreSizesRaw, bc); 80 foreach(ii; 0..segmentCount-1) 81 { 82 int size = moreSizesRaw.get!int(ii * 4); 83 moreSizes ~= size; 84 totalWords += size; 85 } 86 } 87 88 if(totalWords > options.traversalLimitInWords) 89 throw new DecodeException("Message size exceeds traversal limit."); 90 91 auto allSegments = makeByteBuffer(totalWords * Constants.BYTES_PER_WORD); 92 fillBuffer(allSegments, bc); 93 94 auto segmentSlices = new ByteBuffer[](segmentCount); 95 96 allSegments.rewind(); 97 segmentSlices[0] = allSegments.slice(); 98 segmentSlices[0].limit = segment0Size * Constants.BYTES_PER_WORD; 99 100 int offset = segment0Size; 101 foreach(ii; 1..segmentCount) 102 { 103 allSegments.position = offset * Constants.BYTES_PER_WORD; 104 segmentSlices[ii] = allSegments.slice(); 105 segmentSlices[ii].limit = moreSizes.data[ii - 1] * Constants.BYTES_PER_WORD; 106 offset += moreSizes.data[ii - 1]; 107 } 108 109 return new MessageReader(segmentSlices, options); 110 } 111 112 static MessageReader read(ref ByteBuffer bb) 113 { 114 return read(bb, cast(ReaderOptions)ReaderOptions.DEFAULT_READER_OPTIONS); 115 } 116 117 ///Upon return, `bb.position()` will be at the end of the message. 118 static MessageReader read(ref ByteBuffer bb, ReaderOptions options) 119 { 120 int segmentCount = 1 + bb.get!int(); 121 if(segmentCount > 512) 122 throw new IOException("Too many segments."); 123 124 auto segmentSlices = new ByteBuffer[](segmentCount); 125 126 auto segmentSizesBase = bb.position; 127 int segmentSizesSize = segmentCount * 4; 128 129 int align_ = Constants.BYTES_PER_WORD - 1; 130 auto segmentBase = (segmentSizesBase + segmentSizesSize + align_) & ~align_; 131 132 int totalWords = 0; 133 134 foreach(ii; 0..segmentCount) 135 { 136 int segmentSize = bb.get!int(segmentSizesBase + ii * 4); 137 138 bb.position = segmentBase + totalWords * Constants.BYTES_PER_WORD; 139 segmentSlices[ii] = bb.slice(); 140 segmentSlices[ii].limit = segmentSize * Constants.BYTES_PER_WORD; 141 142 totalWords += segmentSize; 143 } 144 bb.position = segmentBase + totalWords * Constants.BYTES_PER_WORD; 145 146 if(totalWords > options.traversalLimitInWords) 147 throw new DecodeException("Message size exceeds traversal limit."); 148 149 return new MessageReader(segmentSlices, options); 150 } 151 152 static long computeSerializedSizeInWords(MessageBuilder message) 153 { 154 auto segments = message.getSegmentsForOutput(); 155 156 //From the capnproto documentation: 157 //"When transmitting over a stream, the following should be sent..." 158 long bytes = 0; 159 //"(4 bytes) The number of segments, minus one..." 160 bytes += 4; 161 //"(N * 4 bytes) The size of each segment, in words." 162 bytes += segments.length * 4; 163 //"(0 or 4 bytes) Padding up to the next word boundary." 164 if(bytes % 8 != 0) 165 bytes += 4; 166 167 //The content of each segment, in order. 168 foreach(i; 0..segments.length) 169 { 170 auto s = segments[i]; 171 bytes += s.limit; 172 } 173 174 return bytes / Constants.BYTES_PER_WORD; 175 } 176 177 static void write(WritableByteChannel outputChannel, MessageBuilder message) 178 { 179 auto segments = message.getSegmentsForOutput(); 180 auto tableSize = (segments.length + 2) & (~1); 181 182 auto table = ByteBuffer(new ubyte[](4 * tableSize)); 183 184 table.put!int(0, cast(int)(segments.length-1)); 185 186 foreach(i; 0..segments.length) 187 table.put!int(4 * (i + 1), cast(int)(segments[i].limit/8)); 188 189 //Any padding is already zeroed. 190 while(!table.empty()) 191 outputChannel.write(table); 192 193 foreach(buffer; segments) 194 { 195 while(!buffer.empty()) 196 outputChannel.write(buffer); 197 } 198 } 199 200 private: //Methods. 201 static ByteBuffer makeByteBuffer(int bytes) 202 { 203 auto result = ByteBuffer.prepare(bytes); 204 //result.mark(); //TODO: Is this used for anything? 205 return result; 206 } 207 }