001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.openwire;
018
019import java.io.DataInput;
020import java.io.DataOutput;
021import java.io.IOException;
022import java.lang.reflect.Method;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.activemq.command.CommandTypes;
027import org.apache.activemq.command.DataStructure;
028import org.apache.activemq.command.WireFormatInfo;
029import org.apache.activemq.util.ByteSequence;
030import org.apache.activemq.util.ByteSequenceData;
031import org.apache.activemq.util.DataByteArrayInputStream;
032import org.apache.activemq.util.DataByteArrayOutputStream;
033import org.apache.activemq.util.IOExceptionSupport;
034import org.apache.activemq.wireformat.WireFormat;
035
036/**
037 *
038 *
039 */
040public final class OpenWireFormat implements WireFormat {
041
042    public static final int DEFAULT_STORE_VERSION = CommandTypes.PROTOCOL_STORE_VERSION;
043    public static final int DEFAULT_WIRE_VERSION = CommandTypes.PROTOCOL_VERSION;
044    public static final int DEFAULT_LEGACY_VERSION = CommandTypes.PROTOCOL_LEGACY_STORE_VERSION;
045    public static final long DEFAULT_MAX_FRAME_SIZE = Long.MAX_VALUE;
046
047    static final byte NULL_TYPE = CommandTypes.NULL;
048    private static final int MARSHAL_CACHE_SIZE = Short.MAX_VALUE / 2;
049    private static final int MARSHAL_CACHE_FREE_SPACE = 100;
050
051    private DataStreamMarshaller[] dataMarshallers;
052    private int version;
053    private boolean stackTraceEnabled;
054    private boolean tcpNoDelayEnabled;
055    private boolean cacheEnabled;
056    private boolean tightEncodingEnabled;
057    private boolean sizePrefixDisabled;
058    private boolean maxFrameSizeEnabled = true;
059    private long maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
060
061    // The following fields are used for value caching
062    private short nextMarshallCacheIndex;
063    private short nextMarshallCacheEvictionIndex;
064    private Map<DataStructure, Short> marshallCacheMap = new HashMap<>();
065    private DataStructure marshallCache[] = null;
066    private DataStructure unmarshallCache[] = null;
067    private final DataByteArrayOutputStream bytesOut = new DataByteArrayOutputStream();
068    private final DataByteArrayInputStream bytesIn = new DataByteArrayInputStream();
069    private WireFormatInfo preferedWireFormatInfo;
070
071    // Used to track the currentFrameSize for validation during unmarshalling
072    // Ideally we would pass the MarshallingContext directly to the marshalling methods,
073    // however this would require modifying the DataStreamMarshaller interface which would result
074    // in hundreds of existing methods having to be updated so this allows avoiding that and
075    // tracking the state without breaking the existing API.
076    // Note that while this is currently only used during unmarshalling, but if necessary could
077    // be extended in the future to be used during marshalling as well.
078    private final ThreadLocal<MarshallingContext> marshallingContext = new ThreadLocal<>();
079
080    public OpenWireFormat() {
081        this(DEFAULT_STORE_VERSION);
082    }
083
084    public OpenWireFormat(int i) {
085        setVersion(i);
086    }
087
088    @Override
089    public int hashCode() {
090        return version ^ (cacheEnabled ? 0x10000000 : 0x20000000)
091               ^ (stackTraceEnabled ? 0x01000000 : 0x02000000)
092               ^ (tightEncodingEnabled ? 0x00100000 : 0x00200000)
093               ^ (sizePrefixDisabled ? 0x00010000 : 0x00020000)
094               ^ (maxFrameSizeEnabled ? 0x00010000 : 0x00020000);
095    }
096
097    public OpenWireFormat copy() {
098        OpenWireFormat answer = new OpenWireFormat(version);
099        answer.stackTraceEnabled = stackTraceEnabled;
100        answer.tcpNoDelayEnabled = tcpNoDelayEnabled;
101        answer.cacheEnabled = cacheEnabled;
102        answer.tightEncodingEnabled = tightEncodingEnabled;
103        answer.sizePrefixDisabled = sizePrefixDisabled;
104        answer.preferedWireFormatInfo = preferedWireFormatInfo;
105        answer.maxFrameSizeEnabled = maxFrameSizeEnabled;
106        return answer;
107    }
108
109    @Override
110    public boolean equals(Object object) {
111        if (object == null) {
112            return false;
113        }
114        OpenWireFormat o = (OpenWireFormat)object;
115        return o.stackTraceEnabled == stackTraceEnabled && o.cacheEnabled == cacheEnabled
116               && o.version == version && o.tightEncodingEnabled == tightEncodingEnabled
117               && o.sizePrefixDisabled == sizePrefixDisabled
118               && o.maxFrameSizeEnabled == maxFrameSizeEnabled;
119    }
120
121
122    @Override
123    public String toString() {
124        return "OpenWireFormat{version=" + version + ", cacheEnabled=" + cacheEnabled + ", stackTraceEnabled=" + stackTraceEnabled + ", tightEncodingEnabled="
125               + tightEncodingEnabled + ", sizePrefixDisabled=" + sizePrefixDisabled +  ", maxFrameSize=" + maxFrameSize + ", maxFrameSizeEnabled=" + maxFrameSizeEnabled + "}";
126        // return "OpenWireFormat{id="+id+",
127        // tightEncodingEnabled="+tightEncodingEnabled+"}";
128    }
129
130    @Override
131    public int getVersion() {
132        return version;
133    }
134
135    @Override
136    public synchronized ByteSequence marshal(Object command) throws IOException {
137
138        if (cacheEnabled) {
139            runMarshallCacheEvictionSweep();
140        }
141
142        ByteSequence sequence = null;
143        int size = 1;
144        if (command != null) {
145
146            DataStructure c = (DataStructure)command;
147            byte type = c.getDataStructureType();
148            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
149            if (dsm == null) {
150                throw new IOException("Unknown data type: " + type);
151            }
152            if (tightEncodingEnabled) {
153
154                BooleanStream bs = new BooleanStream();
155                size += dsm.tightMarshal1(this, c, bs);
156                size += bs.marshalledSize();
157
158                if(maxFrameSizeEnabled && size > maxFrameSize) {
159                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
160                }
161
162                bytesOut.restart(size);
163                if (!sizePrefixDisabled) {
164                    bytesOut.writeInt(size);
165                }
166                bytesOut.writeByte(type);
167                bs.marshal(bytesOut);
168                dsm.tightMarshal2(this, c, bytesOut, bs);
169                sequence = bytesOut.toByteSequence();
170
171            } else {
172                bytesOut.restart();
173                if (!sizePrefixDisabled) {
174                    bytesOut.writeInt(0); // we don't know the final size
175                    // yet but write this here for
176                    // now.
177                }
178                bytesOut.writeByte(type);
179                dsm.looseMarshal(this, c, bytesOut);
180                sequence = bytesOut.toByteSequence();
181
182                if (!sizePrefixDisabled) {
183                    size = sequence.getLength() - 4;
184                    int pos = sequence.offset;
185                    ByteSequenceData.writeIntBig(sequence, size);
186                    sequence.offset = pos;
187                }
188            }
189
190        } else {
191            bytesOut.restart(5);
192            bytesOut.writeInt(size);
193            bytesOut.writeByte(NULL_TYPE);
194            sequence = bytesOut.toByteSequence();
195        }
196
197        return sequence;
198    }
199
200    @Override
201    public synchronized Object unmarshal(ByteSequence sequence) throws IOException {
202        bytesIn.restart(sequence);
203
204        try {
205            final MarshallingContext context = new MarshallingContext();
206            marshallingContext.set(context);
207
208            if (!sizePrefixDisabled) {
209                int size = bytesIn.readInt();
210                if (maxFrameSizeEnabled && size > maxFrameSize) {
211                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
212                }
213                context.setFrameSize(size);
214            }
215            return doUnmarshal(bytesIn);
216        } finally {
217            // After we unmarshal we can clear the context
218            marshallingContext.remove();
219        }
220    }
221
222    @Override
223    public synchronized void marshal(Object o, DataOutput dataOut) throws IOException {
224
225        if (cacheEnabled) {
226            runMarshallCacheEvictionSweep();
227        }
228
229        int size = 1;
230        if (o != null) {
231
232            DataStructure c = (DataStructure)o;
233            byte type = c.getDataStructureType();
234            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
235            if (dsm == null) {
236                throw new IOException("Unknown data type: " + type);
237            }
238            if (tightEncodingEnabled) {
239                BooleanStream bs = new BooleanStream();
240                size += dsm.tightMarshal1(this, c, bs);
241                size += bs.marshalledSize();
242
243                if(maxFrameSizeEnabled && size > maxFrameSize) {
244                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
245                }
246
247                if (!sizePrefixDisabled) {
248                    dataOut.writeInt(size);
249                }
250
251                dataOut.writeByte(type);
252                bs.marshal(dataOut);
253                dsm.tightMarshal2(this, c, dataOut, bs);
254
255            } else {
256                DataOutput looseOut = dataOut;
257
258                if (!sizePrefixDisabled) {
259                    bytesOut.restart();
260                    looseOut = bytesOut;
261                }
262
263                looseOut.writeByte(type);
264                dsm.looseMarshal(this, c, looseOut);
265
266                if (!sizePrefixDisabled) {
267                    ByteSequence sequence = bytesOut.toByteSequence();
268                    dataOut.writeInt(sequence.getLength());
269                    dataOut.write(sequence.getData(), sequence.getOffset(), sequence.getLength());
270                }
271
272            }
273
274        } else {
275            if (!sizePrefixDisabled) {
276                dataOut.writeInt(size);
277            }
278            dataOut.writeByte(NULL_TYPE);
279        }
280    }
281
282    @Override
283    public Object unmarshal(DataInput dis) throws IOException {
284        try {
285            final MarshallingContext context = new MarshallingContext();
286            marshallingContext.set(context);
287
288          if (!sizePrefixDisabled) {
289                int size = dis.readInt();
290                if (maxFrameSizeEnabled && size > maxFrameSize) {
291                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
292                }
293                context.setFrameSize(size);
294            }
295            return doUnmarshal(dis);
296        } finally {
297            // After we unmarshal we can clear
298            marshallingContext.remove();
299        }
300    }
301
302    /**
303     * Used by NIO or AIO transports
304     */
305    public int tightMarshal1(Object o, BooleanStream bs) throws IOException {
306        int size = 1;
307        if (o != null) {
308            DataStructure c = (DataStructure)o;
309            byte type = c.getDataStructureType();
310            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
311            if (dsm == null) {
312                throw new IOException("Unknown data type: " + type);
313            }
314
315            size += dsm.tightMarshal1(this, c, bs);
316            size += bs.marshalledSize();
317        }
318        return size;
319    }
320
321    /**
322     * Used by NIO or AIO transports; note that the size is not written as part
323     * of this method.
324     */
325    public void tightMarshal2(Object o, DataOutput ds, BooleanStream bs) throws IOException {
326        if (cacheEnabled) {
327            runMarshallCacheEvictionSweep();
328        }
329
330        if (o != null) {
331            DataStructure c = (DataStructure)o;
332            byte type = c.getDataStructureType();
333            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
334            if (dsm == null) {
335                throw new IOException("Unknown data type: " + type);
336            }
337            ds.writeByte(type);
338            bs.marshal(ds);
339            dsm.tightMarshal2(this, c, ds, bs);
340        }
341    }
342
343    /**
344     * Allows you to dynamically switch the version of the openwire protocol
345     * being used.
346     *
347     * @param version
348     */
349    @Override
350    public void setVersion(int version) {
351        String mfName = "org.apache.activemq.openwire.v" + version + ".MarshallerFactory";
352        Class mfClass;
353        try {
354            mfClass = Class.forName(mfName, false, getClass().getClassLoader());
355        } catch (ClassNotFoundException e) {
356            throw (IllegalArgumentException)new IllegalArgumentException("Invalid version: " + version
357                                                                         + ", could not load " + mfName)
358                .initCause(e);
359        }
360        try {
361            Method method = mfClass.getMethod("createMarshallerMap", new Class[] {OpenWireFormat.class});
362            dataMarshallers = (DataStreamMarshaller[])method.invoke(null, new Object[] {this});
363        } catch (Throwable e) {
364            throw (IllegalArgumentException)new IllegalArgumentException(
365                                                                         "Invalid version: "
366                                                                             + version
367                                                                             + ", "
368                                                                             + mfName
369                                                                             + " does not properly implement the createMarshallerMap method.")
370                .initCause(e);
371        }
372        this.version = version;
373    }
374
375    private Object doUnmarshal(DataInput dis) throws IOException {
376        byte dataType = dis.readByte();
377        if (dataType != NULL_TYPE) {
378            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
379            if (dsm == null) {
380                throw new IOException("Unknown data type: " + dataType);
381            }
382            Object data = dsm.createObject();
383            if (this.tightEncodingEnabled) {
384                BooleanStream bs = new BooleanStream();
385                bs.unmarshal(dis);
386                dsm.tightUnmarshal(this, data, dis, bs);
387            } else {
388                dsm.looseUnmarshal(this, data, dis);
389            }
390            return data;
391        } else {
392            return null;
393        }
394    }
395
396    // public void debug(String msg) {
397    // String t = (Thread.currentThread().getName()+" ").substring(0, 40);
398    // System.out.println(t+": "+msg);
399    // }
400    public int tightMarshalNestedObject1(DataStructure o, BooleanStream bs) throws IOException {
401        bs.writeBoolean(o != null);
402        if (o == null) {
403            return 0;
404        }
405
406        if (o.isMarshallAware()) {
407            // MarshallAware ma = (MarshallAware)o;
408            ByteSequence sequence = null;
409            // sequence=ma.getCachedMarshalledForm(this);
410            bs.writeBoolean(sequence != null);
411            if (sequence != null) {
412                return 1 + sequence.getLength();
413            }
414        }
415
416        byte type = o.getDataStructureType();
417        DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
418        if (dsm == null) {
419            throw new IOException("Unknown data type: " + type);
420        }
421        return 1 + dsm.tightMarshal1(this, o, bs);
422    }
423
424    public void tightMarshalNestedObject2(DataStructure o, DataOutput ds, BooleanStream bs)
425        throws IOException {
426        if (!bs.readBoolean()) {
427            return;
428        }
429
430        byte type = o.getDataStructureType();
431        ds.writeByte(type);
432
433        if (o.isMarshallAware() && bs.readBoolean()) {
434
435            // We should not be doing any caching
436            throw new IOException("Corrupted stream");
437            // MarshallAware ma = (MarshallAware) o;
438            // ByteSequence sequence=ma.getCachedMarshalledForm(this);
439            // ds.write(sequence.getData(), sequence.getOffset(),
440            // sequence.getLength());
441
442        } else {
443
444            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
445            if (dsm == null) {
446                throw new IOException("Unknown data type: " + type);
447            }
448            dsm.tightMarshal2(this, o, ds, bs);
449
450        }
451    }
452
453    public DataStructure tightUnmarshalNestedObject(DataInput dis, BooleanStream bs) throws IOException {
454        if (bs.readBoolean()) {
455
456            byte dataType = dis.readByte();
457            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
458            if (dsm == null) {
459                throw new IOException("Unknown data type: " + dataType);
460            }
461            DataStructure data = dsm.createObject();
462
463            if (data.isMarshallAware() && bs.readBoolean()) {
464
465                dis.readInt();
466                dis.readByte();
467
468                BooleanStream bs2 = new BooleanStream();
469                bs2.unmarshal(dis);
470                dsm.tightUnmarshal(this, data, dis, bs2);
471
472                // TODO: extract the sequence from the dis and associate it.
473                // MarshallAware ma = (MarshallAware)data
474                // ma.setCachedMarshalledForm(this, sequence);
475
476            } else {
477                dsm.tightUnmarshal(this, data, dis, bs);
478            }
479
480            return data;
481        } else {
482            return null;
483        }
484    }
485
486    public DataStructure looseUnmarshalNestedObject(DataInput dis) throws IOException {
487        if (dis.readBoolean()) {
488
489            byte dataType = dis.readByte();
490            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
491            if (dsm == null) {
492                throw new IOException("Unknown data type: " + dataType);
493            }
494            DataStructure data = dsm.createObject();
495            dsm.looseUnmarshal(this, data, dis);
496            return data;
497
498        } else {
499            return null;
500        }
501    }
502
503    public void looseMarshalNestedObject(DataStructure o, DataOutput dataOut) throws IOException {
504        dataOut.writeBoolean(o != null);
505        if (o != null) {
506            byte type = o.getDataStructureType();
507            dataOut.writeByte(type);
508            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
509            if (dsm == null) {
510                throw new IOException("Unknown data type: " + type);
511            }
512            dsm.looseMarshal(this, o, dataOut);
513        }
514    }
515
516    public void runMarshallCacheEvictionSweep() {
517        // Do we need to start evicting??
518        while (marshallCacheMap.size() > marshallCache.length - MARSHAL_CACHE_FREE_SPACE) {
519
520            marshallCacheMap.remove(marshallCache[nextMarshallCacheEvictionIndex]);
521            marshallCache[nextMarshallCacheEvictionIndex] = null;
522
523            nextMarshallCacheEvictionIndex++;
524            if (nextMarshallCacheEvictionIndex >= marshallCache.length) {
525                nextMarshallCacheEvictionIndex = 0;
526            }
527
528        }
529    }
530
531    public Short getMarshallCacheIndex(DataStructure o) {
532        return marshallCacheMap.get(o);
533    }
534
535    public Short addToMarshallCache(DataStructure o) {
536        short i = nextMarshallCacheIndex++;
537        if (nextMarshallCacheIndex >= marshallCache.length) {
538            nextMarshallCacheIndex = 0;
539        }
540
541        // We can only cache that item if there is space left.
542        if (marshallCacheMap.size() < marshallCache.length) {
543            marshallCache[i] = o;
544            Short index = new Short(i);
545            marshallCacheMap.put(o, index);
546            return index;
547        } else {
548            // Use -1 to indicate that the value was not cached due to cache
549            // being full.
550            return new Short((short)-1);
551        }
552    }
553
554    public void setInUnmarshallCache(short index, DataStructure o) {
555
556        // There was no space left in the cache, so we can't
557        // put this in the cache.
558        if (index == -1) {
559            return;
560        }
561
562        unmarshallCache[index] = o;
563    }
564
565    public DataStructure getFromUnmarshallCache(short index) {
566        return unmarshallCache[index];
567    }
568
569    public void setStackTraceEnabled(boolean b) {
570        stackTraceEnabled = b;
571    }
572
573    public boolean isStackTraceEnabled() {
574        return stackTraceEnabled;
575    }
576
577    public boolean isTcpNoDelayEnabled() {
578        return tcpNoDelayEnabled;
579    }
580
581    public void setTcpNoDelayEnabled(boolean tcpNoDelayEnabled) {
582        this.tcpNoDelayEnabled = tcpNoDelayEnabled;
583    }
584
585    public boolean isCacheEnabled() {
586        return cacheEnabled;
587    }
588
589    public void setCacheEnabled(boolean cacheEnabled) {
590        if(cacheEnabled){
591            marshallCache = new DataStructure[MARSHAL_CACHE_SIZE];
592            unmarshallCache = new DataStructure[MARSHAL_CACHE_SIZE];
593        }
594        this.cacheEnabled = cacheEnabled;
595    }
596
597    public boolean isTightEncodingEnabled() {
598        return tightEncodingEnabled;
599    }
600
601    public void setTightEncodingEnabled(boolean tightEncodingEnabled) {
602        this.tightEncodingEnabled = tightEncodingEnabled;
603    }
604
605    public boolean isSizePrefixDisabled() {
606        return sizePrefixDisabled;
607    }
608
609    public void setSizePrefixDisabled(boolean prefixPacketSize) {
610        this.sizePrefixDisabled = prefixPacketSize;
611    }
612
613    public void setPreferedWireFormatInfo(WireFormatInfo info) {
614        this.preferedWireFormatInfo = info;
615    }
616
617    public WireFormatInfo getPreferedWireFormatInfo() {
618        return preferedWireFormatInfo;
619    }
620
621    public long getMaxFrameSize() {
622        return maxFrameSize;
623    }
624
625    public void setMaxFrameSize(long maxFrameSize) {
626        this.maxFrameSize = maxFrameSize;
627    }
628
629    public boolean isMaxFrameSizeEnabled() {
630        return maxFrameSizeEnabled;
631    }
632
633    /**
634     * Set whether the maxFrameSize check will be enabled. Note this is only applied to this format
635     * and will NOT be negotiated
636     *
637     * @param maxFrameSizeEnabled
638     */
639    public void setMaxFrameSizeEnabled(boolean maxFrameSizeEnabled) {
640        this.maxFrameSizeEnabled = maxFrameSizeEnabled;
641    }
642
643    public void renegotiateWireFormat(WireFormatInfo info) throws IOException {
644
645        if (preferedWireFormatInfo == null) {
646            throw new IllegalStateException("Wireformat cannot not be renegotiated.");
647        }
648
649        this.setVersion(min(preferedWireFormatInfo.getVersion(), info.getVersion()));
650        info.setVersion(this.getVersion());
651
652        this.setMaxFrameSize(min(preferedWireFormatInfo.getMaxFrameSize(), info.getMaxFrameSize()));
653        info.setMaxFrameSize(this.getMaxFrameSize());
654        //Note: Don't negotiate maxFrameSizeEnabled so the client and server can set independently
655
656        this.stackTraceEnabled = info.isStackTraceEnabled() && preferedWireFormatInfo.isStackTraceEnabled();
657        info.setStackTraceEnabled(this.stackTraceEnabled);
658
659        this.tcpNoDelayEnabled = info.isTcpNoDelayEnabled() && preferedWireFormatInfo.isTcpNoDelayEnabled();
660        info.setTcpNoDelayEnabled(this.tcpNoDelayEnabled);
661
662        this.cacheEnabled = info.isCacheEnabled() && preferedWireFormatInfo.isCacheEnabled();
663        info.setCacheEnabled(this.cacheEnabled);
664
665        this.tightEncodingEnabled = info.isTightEncodingEnabled()
666                                    && preferedWireFormatInfo.isTightEncodingEnabled();
667        info.setTightEncodingEnabled(this.tightEncodingEnabled);
668
669        this.sizePrefixDisabled = info.isSizePrefixDisabled()
670                                  && preferedWireFormatInfo.isSizePrefixDisabled();
671        info.setSizePrefixDisabled(this.sizePrefixDisabled);
672
673        if (cacheEnabled) {
674
675            int size = Math.min(preferedWireFormatInfo.getCacheSize(), info.getCacheSize());
676            info.setCacheSize(size);
677
678            if (size == 0) {
679                size = MARSHAL_CACHE_SIZE;
680            }
681
682            marshallCache = new DataStructure[size];
683            unmarshallCache = new DataStructure[size];
684            nextMarshallCacheIndex = 0;
685            nextMarshallCacheEvictionIndex = 0;
686            marshallCacheMap = new HashMap<DataStructure, Short>();
687        } else {
688            marshallCache = null;
689            unmarshallCache = null;
690            nextMarshallCacheIndex = 0;
691            nextMarshallCacheEvictionIndex = 0;
692            marshallCacheMap = null;
693        }
694
695    }
696
697    protected int min(int version1, int version2) {
698        if (version1 < version2 && version1 > 0 || version2 <= 0) {
699            return version1;
700        }
701        return version2;
702    }
703
704    protected long min(long version1, long version2) {
705        if (version1 < version2 && version1 > 0 || version2 <= 0) {
706            return version1;
707        }
708        return version2;
709    }
710
711    MarshallingContext getMarshallingContext() {
712        return marshallingContext.get();
713    }
714
715    // Used to track the estimated allocated buffer sizes to validate
716    // against the current frame being processed
717    static class MarshallingContext {
718        // Use primitives to minimize memory footprint
719        private int frameSize = -1;
720        private int estimatedAllocated = 0;
721
722        void setFrameSize(int frameSize) throws IOException {
723            this.frameSize = frameSize;
724            if (frameSize < 0) {
725                throw error("Frame size " + frameSize + " can't be negative.");
726            }
727        }
728
729        void increment(int size) throws IOException {
730            if (size < 0) {
731                throw error("Size " + size + " can't be negative.");
732            }
733            try {
734                estimatedAllocated = Math.addExact(estimatedAllocated, size);
735            } catch (ArithmeticException e) {
736                throw error("Buffer overflow when incrementing size value: " + size);
737            }
738        }
739
740        public int getFrameSize() {
741            return frameSize;
742        }
743
744        public int getEstimatedAllocated() {
745            return estimatedAllocated;
746        }
747
748        private static IOException error(String errorMessage) {
749            return new IOException(new IllegalArgumentException(errorMessage));
750        }
751    }
752
753}