aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/fs4/PacketDecoder.java
blob: 0f6f58ba7b77d76aad18561b4a3d50ca69303fde (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.fs4;

import java.nio.ByteBuffer;

/**
 * Returns the correct package for a package byte stream
 *
 * @author  bratseth
 * @author  <a href="mailto:borud@yahoo-inc.com">Bj\u00f8rn Borud</a>
 */
public class PacketDecoder {

    /** Represents a packet and the data used to construct it */
    public static class DecodedPacket {
        public BasicPacket packet;
        public ByteBuffer consumedBytes;

        DecodedPacket(BasicPacket packet, ByteBuffer consumedBytes) {
            this.packet = packet;
            this.consumedBytes = consumedBytes;
        }
    }

    private PacketDecoder() {}

    /**
     * Returns the package starting at the current position in the buffer
     *
     * @throws IllegalArgumentException if an unknown package code is
     *          encountered
     * @throws java.nio.BufferUnderflowException if the buffer contains too little
     *          data to decode the pcode.
     */
     public static BasicPacket decode(ByteBuffer buffer) {
         int packetCode = buffer.getInt(buffer.position()+4);
         packetCode &= BasicPacket.CODE_MASK;

         switch (packetCode) {
             case 200:
                 return EolPacket.create().decode(buffer);

             case 203:
                 return ErrorPacket.create().decode(buffer);

             case 205:
                 return DocsumPacket.create().decode(buffer);

             case 202:
             case 208:
             case 214:
             case 217:
                 return QueryResultPacket.create().decode(buffer);

             case 210:
             case 221:
                 return PongPacket.create().decode(buffer);

             case 207:
                 return SearchNodePongPacket.create().decode(buffer);

             default:
                 throw new IllegalArgumentException("No support for packet " + packetCode);
         }
    }

    /** Gives the packet along with the bytes consumed to construct it. */
    public static DecodedPacket decodePacket(ByteBuffer buffer) {
        ByteBuffer dataUsed = buffer.slice();
        int start = buffer.position();

        BasicPacket packet = decode(buffer);
        dataUsed.limit(buffer.position() - start);
        return new DecodedPacket(packet, dataUsed);
    }

     /** Sniff channel ID for query result packets */
     public static int sniffChannel(ByteBuffer buffer) {
        int remaining = buffer.remaining();
        if (remaining < 12) {
            return 0;
        }
         int packetCode = buffer.getInt(buffer.position()+4);
         packetCode &= BasicPacket.CODE_MASK;
         switch (packetCode) {
         case 202:
         case 208:
         case 214:
         case 217:
             return buffer.getInt(buffer.position()+8);
         default:
             return 0;
         }
     }

     /**
      * Test whether the buffer contains (the start of) a pong packet.
      *
      * Returns false if there is not enough data to determine the
      * answer.
      */
     public static boolean isPongPacket(ByteBuffer buffer) {

        int remaining = buffer.remaining();
        if (remaining < 8)
            return false;
         int packetCode = buffer.getInt(buffer.position()+4);
         packetCode &= BasicPacket.CODE_MASK;
         if (packetCode == 210 || packetCode == 221)
             return true;
         else
             return false;
     }

    /**
     * Note that it assumes that the position of the ByteBuffer is at the
     * start of a packet and that we have enough data to actually read
     * an integer out of the buffer.
     *
     * @return Return the length of the fs4 packet.  Returns -1 if length
     *         could not be determined because we had too little
     *         data in the buffer.
     *
     */
    public static int packetLength(ByteBuffer buffer)
    {
        if (buffer.remaining() < 4) {
            return -1;
        }
        return (buffer.getInt(buffer.position()) + 4);
    }

    /**
     * Takes a buffer possibly containing a packet.
     *
     * <P>
     * If we return a packet when we return:
     * <UL>
     *  <LI> the buffer is positioned at the beginning of the next
     *       packet when we return.
     *  <LI> limit is unchanged
     * </UL>
     *
     * <P>
     * If we return <code>null</code> there were no more packets
     * there to decode and the following is true of the buffer
     * <UL>
     *  <LI> the buffer is compacted, ie. partial packet is
     *       moved to the start, or if no more data is available
     *       the buffer is cleared.
     *  <LI> the position is set to the next byte after the valid
     *       data so the buffer is ready for reading.
     * </UL>
     *
     * If there are no packets to be returned the buffer is compacted
     * (ie. content is moved to the start and read-pointer is positioned
     *
     * @return Returns the next available packet from the buffer or
     *         <code>null</code> if there are no more <b>complete</b>
     *         packets in the buffer at this time.
     */
    public static DecodedPacket extractPacket(ByteBuffer buffer)
        throws BufferTooSmallException
    {
        int remaining = buffer.remaining();

        // if we are empty we can reset the buffer
        if (remaining == 0) {
            buffer.clear();
            return null;
        }

        // if we can't figure out the size because we have less than
        // 4 bytes we prepare the buffer for more data reading.
        if (remaining < 4) {
            buffer.compact();
            return null;
        }

        int plen = packetLength(buffer);

        // -1 means that we do not have enough data to read the packet
        // size yet
        if (plen == -1) {
            buffer.compact();
            return null;
        }

        // if we haven't read an entire packet yet, we compact and return
        // (same as above but separate code for clarity).  note that this
        // also occurs when there is no physical room for the packet, so
        // clients of this API need to be aware of this and check for it
        if (remaining < plen) {

            // if the read buffer is too small we must take drastic action
            if (buffer.capacity() < plen) {
                throw new BufferTooSmallException("Buffer too small to hold packet");
            }

            buffer.compact();
            return null;
        }

        return PacketDecoder.decodePacket(buffer);
    }

}