1 /*
2  * hunt-proton: AMQP Protocol library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.proton.codec.messaging.FastPathPropertiesType;
13 
14 import hunt.collection.Collection;
15 
16 import hunt.proton.codec.messaging.PropertiesType;
17 import hunt.proton.amqp.Symbol;
18 import hunt.proton.amqp.UnsignedLong;
19 import hunt.proton.amqp.messaging.Properties;
20 import hunt.proton.codec.AMQPType;
21 import hunt.proton.codec.DecodeException;
22 import hunt.proton.codec.Decoder;
23 import hunt.proton.codec.DecoderImpl;
24 import hunt.proton.codec.EncoderImpl;
25 import hunt.proton.codec.EncodingCodes;
26 import hunt.proton.codec.FastPathDescribedTypeConstructor;
27 import hunt.proton.codec.ReadableBuffer;
28 import hunt.proton.codec.TypeEncoding;
29 import hunt.proton.codec.WritableBuffer;
30 import hunt.Exceptions;
31 import std.concurrency : initOnce;
32 import hunt.logging;
33 import std.conv : to;
34 import hunt.String;
35 
36 class FastPathPropertiesType : AMQPType!(Properties), FastPathDescribedTypeConstructor!(Properties) {
37 
38     private static byte DESCRIPTOR_CODE = 0x73;
39 
40     //private static Object[] DESCRIPTORS =
41     //{
42     //    UnsignedLong.valueOf(DESCRIPTOR_CODE), Symbol.valueOf("amqp:properties:list"),
43     //};
44 
45     static Object[]  DESCRIPTORS() {
46         __gshared Object[]  inst;
47         return initOnce!inst([UnsignedLong.valueOf(DESCRIPTOR_CODE), Symbol.valueOf("amqp:properties:list")]);
48     }
49 
50 
51     private PropertiesType propertiesType;
52 
53     this(EncoderImpl encoder) {
54         this.propertiesType = new PropertiesType(encoder);
55     }
56 
57     public EncoderImpl getEncoder() {
58         return propertiesType.getEncoder();
59     }
60 
61     public DecoderImpl getDecoder() {
62         return propertiesType.getDecoder();
63     }
64 
65     override
66     public Properties readValue() {
67         DecoderImpl decoder = getDecoder();
68         ReadableBuffer buffer = decoder.getBuffer();
69         byte typeCode = decoder.getBuffer().get();
70 
71         int size = 0;
72         int count = 0;
73 
74         switch (typeCode) {
75             case EncodingCodes.LIST0:
76                 break;
77             case EncodingCodes.LIST8:
78                 size = buffer.get() & 0xff;
79                 count = buffer.get() & 0xff;
80                 break;
81             case EncodingCodes.LIST32:
82                 size = buffer.getInt();
83                 count = buffer.getInt();
84                 break;
85             default:
86             {
87                 logError("Incorrect type found in Properties encoding: %d", typeCode);
88                 break;
89             }
90                // throw new DecodeException("Incorrect type found in Properties encoding: " ~ typeCode);
91         }
92 
93         Properties properties = new Properties();
94 
95         for (int index = 0; index < count; ++index) {
96             switch (index) {
97                 case 0:
98                     properties.setMessageId(cast(String)decoder.readObject());
99                     break;
100                 case 1:
101                     properties.setUserId(decoder.readBinary(null));
102                     break;
103                 case 2:
104                     properties.setTo(decoder.readString(null));
105                     break;
106                 case 3:
107                     properties.setSubject(decoder.readString(null));
108                     break;
109                 case 4:
110                     properties.setReplyTo(decoder.readString(null));
111                     break;
112                 case 5:
113                     properties.setCorrelationId(cast(String)decoder.readObject());
114                     break;
115                 case 6:
116                     properties.setContentType(decoder.readSymbol(null));
117                     break;
118                 case 7:
119                     properties.setContentEncoding(decoder.readSymbol(null));
120                     break;
121                 case 8:
122                     properties.setAbsoluteExpiryTime(decoder.readTimestamp(null));
123                     break;
124                 case 9:
125                     properties.setCreationTime(decoder.readTimestamp(null));
126                     break;
127                 case 10:
128                     properties.setGroupId(decoder.readString(null));
129                     break;
130                 case 11:
131                     properties.setGroupSequence(decoder.readUnsignedInteger(null));
132                     break;
133                 case 12:
134                     properties.setReplyToGroupId(decoder.readString(null));
135                     break;
136                 default:
137                     throw new IllegalStateException("To many entries in Properties encoding");
138             }
139         }
140 
141         return properties;
142     }
143 
144     override
145     public void skipValue() {
146        // implementationMissing(false);
147         getDecoder().readConstructor().skipValue();
148     }
149 
150     override
151     public bool encodesJavaPrimitive() {
152         return false;
153     }
154 
155     override
156     public TypeInfo getTypeClass() {
157         return typeid(Properties);
158     }
159 
160     override
161     public ITypeEncoding getEncoding(Object properties) {
162         return propertiesType.getEncoding(cast(Properties)properties);
163     }
164 
165     override
166     public TypeEncoding!(Properties) getCanonicalEncoding() {
167         return propertiesType.getCanonicalEncoding();
168     }
169 
170     override
171     public  Collection!(TypeEncoding!(Properties)) getAllEncodings() {
172         return propertiesType.getAllEncodings();
173     }
174 
175     override
176     public void write(Object v) {
177         Properties value = cast(Properties) v;
178         WritableBuffer buffer = getEncoder().getBuffer();
179         int count = getElementCount(value);
180         byte encodingCode = deduceEncodingCode(value, count);
181 
182         buffer.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
183         buffer.put(EncodingCodes.SMALLULONG);
184         buffer.put(DESCRIPTOR_CODE);
185         buffer.put(encodingCode);
186 
187         // Optimized step, no other data to be written.
188         if (encodingCode == EncodingCodes.LIST0) {
189             return;
190         }
191 
192         int fieldWidth;
193 
194         if (encodingCode == EncodingCodes.LIST8) {
195             fieldWidth = 1;
196         } else {
197             fieldWidth = 4;
198         }
199 
200         int startIndex = buffer.position();
201 
202         // Reserve space for the size and write the count of list elements.
203         if (fieldWidth == 1) {
204             buffer.put(cast(byte) 0);
205             buffer.put(cast(byte) count);
206         } else {
207             buffer.putInt(0);
208             buffer.putInt(count);
209         }
210 
211         // Write the list elements and then compute total size written.
212         for (int i = 0; i < count; ++i) {
213             writeElement(value, i);
214         }
215 
216         // Move back and write the size
217         int endIndex = buffer.position();
218         int writeSize = endIndex - startIndex - fieldWidth;
219 
220         buffer.position(startIndex);
221         if (fieldWidth == 1) {
222             buffer.put(cast(byte) writeSize);
223         } else {
224             buffer.putInt(writeSize);
225         }
226         buffer.position(endIndex);
227     }
228 
229     private byte deduceEncodingCode(Properties value, int elementCount) {
230         if (elementCount == 0) {
231             return EncodingCodes.LIST0;
232         } else {
233             return EncodingCodes.LIST32;
234         }
235     }
236 
237     private void writeElement(Properties properties, int index) {
238         switch (index) {
239             case 0:
240                 getEncoder().writeObject(properties.getMessageId());
241                 break;
242             case 1:
243                 getEncoder().writeBinary(properties.getUserId());
244                 break;
245             case 2:
246                 getEncoder().writeString(properties.getTo());
247                 break;
248             case 3:
249                 getEncoder().writeString(properties.getSubject());
250                 break;
251             case 4:
252                 getEncoder().writeString(properties.getReplyTo());
253                 break;
254             case 5:
255                 getEncoder().writeObject(properties.getCorrelationId());
256                 break;
257             case 6:
258                 getEncoder().writeSymbol(properties.getContentType());
259                 break;
260             case 7:
261                 getEncoder().writeSymbol(properties.getContentEncoding());
262                 break;
263             case 8:
264                 getEncoder().writeTimestamp(properties.getAbsoluteExpiryTime());
265                 break;
266             case 9:
267                 getEncoder().writeTimestamp(properties.getCreationTime());
268                 break;
269             case 10:
270                 getEncoder().writeString(properties.getGroupId());
271                 break;
272             case 11:
273                 getEncoder().writeUnsignedInteger(properties.getGroupSequence());
274                 break;
275             case 12:
276                 getEncoder().writeString(properties.getReplyToGroupId());
277                 break;
278             default:
279                 throw new IllegalArgumentException("Unknown Properties value index: " ~ to!string(index));
280         }
281     }
282 
283     private int getElementCount(Properties properties) {
284         if (properties.getReplyToGroupId() !is null) {
285             return 13;
286         } else if (properties.getGroupSequence() !is null) {
287             return 12;
288         } else if (properties.getGroupId() !is null) {
289             return 11;
290         } else if (properties.getCreationTime() !is null) {
291             return 10;
292         } else if (properties.getAbsoluteExpiryTime() !is null) {
293             return 9;
294         } else if (properties.getContentEncoding() !is null) {
295             return 8;
296         } else if (properties.getContentType() !is null) {
297             return 7;
298         } else if (properties.getCorrelationId() !is null) {
299             return 6;
300         } else if (properties.getReplyTo() !is null) {
301             return 5;
302         } else if (properties.getSubject() !is null) {
303             return 4;
304         } else if (properties.getTo() !is null) {
305             return 3;
306         } else if (properties.getUserId() !is null) {
307             return 2;
308         } else if (properties.getMessageId() !is null) {
309             return 1;
310         }
311 
312         return 0;
313     }
314 
315     public static void register(Decoder decoder, EncoderImpl encoder) {
316         FastPathPropertiesType type = new FastPathPropertiesType(encoder);
317         //implementationMissing(false);
318         foreach(Object descriptor ; DESCRIPTORS) {
319             decoder.registerFastPath(descriptor,  type);
320         }
321         encoder.register(type);
322     }
323 }