1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package org.jboss.netty.handler.codec.frame;
17
18 import static org.jboss.netty.buffer.ChannelBuffers.*;
19
20 import java.nio.ByteOrder;
21
22 import org.jboss.netty.buffer.ChannelBuffer;
23 import org.jboss.netty.buffer.ChannelBufferFactory;
24 import org.jboss.netty.channel.Channel;
25 import org.jboss.netty.channel.ChannelHandler.Sharable;
26 import org.jboss.netty.channel.ChannelHandlerContext;
27 import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
28
29 /**
30 * An encoder that prepends the length of the message. The length value is
31 * prepended as a binary form. It is encoded in either big endian or little
32 * endian depending on the default {@link ByteOrder} of the current
33 * {@link ChannelBufferFactory}.
34 * <p>
35 * For example, <tt>{@link LengthFieldPrepender}(2)</tt> will encode the
36 * following 12-bytes string:
37 * <pre>
38 * +----------------+
39 * | "HELLO, WORLD" |
40 * +----------------+
41 * </pre>
42 * into the following:
43 * <pre>
44 * +--------+----------------+
45 * + 0x000C | "HELLO, WORLD" |
46 * +--------+----------------+
47 * </pre>
48 * If you turned on the {@code lengthIncludesLengthFieldLength} flag in the
49 * constructor, the encoded data would look like the following
50 * (12 (original data) + 2 (prepended data) = 14 (0xE)):
51 * <pre>
52 * +--------+----------------+
53 * + 0x000E | "HELLO, WORLD" |
54 * +--------+----------------+
55 * </pre>
56 */
57 @Sharable
58 public class LengthFieldPrepender extends OneToOneEncoder {
59
60 private final int lengthFieldLength;
61 private final boolean lengthIncludesLengthFieldLength;
62
63 /**
64 * Creates a new instance.
65 *
66 * @param lengthFieldLength the length of the prepended length field.
67 * Only 1, 2, 3, 4, and 8 are allowed.
68 *
69 * @throws IllegalArgumentException
70 * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
71 */
72 public LengthFieldPrepender(int lengthFieldLength) {
73 this(lengthFieldLength, false);
74 }
75
76 /**
77 * Creates a new instance.
78 *
79 * @param lengthFieldLength the length of the prepended length field.
80 * Only 1, 2, 3, 4, and 8 are allowed.
81 * @param lengthIncludesLengthFieldLength
82 * if {@code true}, the length of the prepended
83 * length field is added to the value of the
84 * prepended length field.
85 *
86 * @throws IllegalArgumentException
87 * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
88 */
89 public LengthFieldPrepender(
90 int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
91 if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
92 lengthFieldLength != 3 && lengthFieldLength != 4 &&
93 lengthFieldLength != 8) {
94 throw new IllegalArgumentException(
95 "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
96 lengthFieldLength);
97 }
98
99 this.lengthFieldLength = lengthFieldLength;
100 this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
101 }
102
103 @Override
104 protected Object encode(
105 ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
106 if (!(msg instanceof ChannelBuffer)) {
107 return msg;
108 }
109
110 ChannelBuffer body = (ChannelBuffer) msg;
111 ChannelBuffer header = channel.getConfig().getBufferFactory().getBuffer(body.order(), lengthFieldLength);
112
113 int length = lengthIncludesLengthFieldLength?
114 body.readableBytes() + lengthFieldLength : body.readableBytes();
115 switch (lengthFieldLength) {
116 case 1:
117 if (length >= 256) {
118 throw new IllegalArgumentException(
119 "length does not fit into a byte: " + length);
120 }
121 header.writeByte((byte) length);
122 break;
123 case 2:
124 if (length >= 65536) {
125 throw new IllegalArgumentException(
126 "length does not fit into a short integer: " + length);
127 }
128 header.writeShort((short) length);
129 break;
130 case 3:
131 if (length >= 16777216) {
132 throw new IllegalArgumentException(
133 "length does not fit into a medium integer: " + length);
134 }
135 header.writeMedium(length);
136 break;
137 case 4:
138 header.writeInt(length);
139 break;
140 case 8:
141 header.writeLong(length);
142 break;
143 default:
144 throw new Error("should not reach here");
145 }
146 return wrappedBuffer(header, body);
147 }
148 }