1 module mysql.packet;
2 
3 
4 import std.algorithm;
5 import std.traits;
6 
7 import mysql.exception;
8 
9 
10 struct InputPacket {
11     @disable this();
12 
13     this(ubyte[]* buffer) {
14         buffer_ = buffer;
15         in_ = *buffer_;
16     }
17 
18     T peek(T)() if (!isArray!T) {
19         assert(T.sizeof <= in_.length);
20         return *(cast(T*)in_.ptr);
21     }
22 
23     T eat(T)() if (!isArray!T) {
24         assert(T.sizeof <= in_.length);
25         auto ptr = cast(T*)in_.ptr;
26         in_ = in_[T.sizeof..$];
27         return *ptr;
28     }
29 
30     T peek(T)(size_t count) if (isArray!T) {
31         alias ValueType = typeof(Type.init[0]);
32 
33         assert(ValueType.sizeof * count <= in_.length);
34         auto ptr = cast(ValueType*)in_.ptr;
35         return ptr[0..count];
36     }
37 
38     T eat(T)(size_t count) if (isArray!T) {
39         alias ValueType = typeof(T.init[0]);
40 
41         assert(ValueType.sizeof * count <= in_.length);
42         auto ptr = cast(ValueType*)in_.ptr;
43         in_ = in_[ValueType.sizeof * count..$];
44         return ptr[0..count];
45     }
46 
47     void expect(T)(T x) {
48         if (x != eat!T)
49             throw new MySQLProtocolException("Bad packet format");
50     }
51 
52     void skip(size_t count) {
53         assert(count <= in_.length);
54         in_ = in_[count..$];
55     }
56 
57     auto countUntil(ubyte x, bool expect) {
58         auto index = in_.countUntil(x);
59         if (expect) {
60             if ((index < 0) || (in_[index] != x))
61                 throw new MySQLProtocolException("Bad packet format");
62         }
63         return index;
64     }
65 
66     ulong eatLenEnc() {
67         auto header = eat!ubyte;
68         if (header < 0xfb)
69             return header;
70 
71         ulong lo;
72         ulong hi;
73 
74         switch(header) {
75         case 0xfb:
76             return 0;
77         case 0xfc:
78             return eat!ushort;
79         case 0xfd:
80             lo = eat!ubyte;
81             hi = eat!ushort;
82             return lo | (hi << 8);
83         case 0xfe:
84             lo = eat!uint;
85             hi = eat!uint;
86             return lo | (hi << 32);
87         default:
88             throw new MySQLProtocolException("Bad packet format");
89         }
90     }
91 
92     auto remaining() const {
93         return in_.length;
94     }
95 
96     bool empty() const {
97         return in_.length == 0;
98     }
99 protected:
100     ubyte[]* buffer_;
101     ubyte[] in_;
102 }
103 
104 
105 struct OutputPacket {
106     @disable this();
107 
108     this(ubyte[]* buffer) {
109         buffer_ = buffer;
110         out_ = buffer_.ptr + 4;
111     }
112 
113     void put(T)(T x) if (!isArray!T) {
114         put(offset_, x);
115     }
116 
117     void put(T)(T x) if (isArray!T) {
118         put(offset_, x);
119     }
120 
121     void put(T)(size_t offset, T x) if (!isArray!T) {
122         grow(offset, T.sizeof);
123 
124         *(cast(T*)(out_ + offset)) = x;
125         offset_ = max(offset + T.sizeof, offset_);
126     }
127 
128     void put(T)(size_t offset, T x) if (isArray!T) {
129         alias ValueType = Unqual!(typeof(T.init[0]));
130 
131         grow(offset, ValueType.sizeof * x.length);
132 
133         (cast(ValueType*)(out_ + offset))[0..x.length] = x;
134         offset_ = max(offset + (ValueType.sizeof * x.length), offset_);
135     }
136 
137     void putLenEnc(ulong x) {
138         if (x < 0xfb) {
139             put!ubyte(cast(ubyte)x);
140         } else if (x <= ushort.max) {
141             put!ubyte(0xfc);
142             put!ushort(cast(ushort)x);
143         } else if (x <= (uint.max >> 8)) {
144             put!ubyte(0xfd);
145             put!ubyte(cast(ubyte)(x));
146             put!ushort(cast(ushort)(x >> 8));
147         } else {
148             put!ubyte(0xfe);
149             put!uint(cast(uint)x);
150             put!uint(cast(uint)(x >> 32));
151         }
152     }
153 
154     size_t marker(T)() if (!isArray!T) {
155         grow(offset_, T.sizeof);
156 
157         auto place = offset_;
158         offset_ += T.sizeof;
159         return place;
160     }
161 
162     size_t marker(T)(size_t count) if (isArray!T) {
163         alias ValueType = Unqual!(typeof(T.init[0]));
164         grow(offset_, ValueType.sizeof * x.length);
165 
166         auto place = offset_;
167         offset_ += (ValueType.sizeof * x.length);
168         return place;
169     }
170 
171     void finalize(ubyte seq) {
172         if (offset_ >=  0xffffff)
173             throw new MySQLConnectionException("Packet size exceeds 2^24");
174         uint length = cast(uint)offset_;
175         uint header = cast(uint)((offset_ & 0xffffff) | (seq << 24));
176         *(cast(uint*)buffer_.ptr) = header;
177     }
178 
179     void finalize(ubyte seq, size_t extra) {
180         if (offset_ + extra >= 0xffffff)
181             throw new MySQLConnectionException("Packet size exceeds 2^24");
182         uint length = cast(uint)(offset_ + extra);
183         uint header = cast(uint)((length & 0xffffff) | (seq << 24));
184         *(cast(uint*)buffer_.ptr) = header;
185     }
186 
187     void reset() {
188         offset_ = 0;
189     }
190     void reserve(size_t size) {
191         (*buffer_).length = max((*buffer_).length, 4 + size);
192         out_ = buffer_.ptr + 4;
193     }
194 
195     void fill(ubyte x, size_t size) {
196         grow(offset_, size);
197         out_[offset_..offset_ + size] = 0;
198         offset_ += size;
199     }
200 
201     size_t length() const {
202         return offset_;
203     }
204 
205     bool empty() const {
206         return offset_ == 0;
207     }
208 
209     const(ubyte)[] get() const {
210         return (*buffer_)[0..4 + offset_];
211     }
212 protected:
213     void grow(size_t offset, size_t size) {
214         auto requested = 4 + offset + size;
215         if (requested > buffer_.length) {
216             auto capacity = (*buffer_).capacity;
217             while (capacity < requested)
218                 capacity <<= 1;
219             buffer_.length = requested;
220             out_ = buffer_.ptr + 4;
221         }
222     }
223     ubyte[]* buffer_;
224     ubyte* out_;
225     size_t offset_ = 0;
226 }