diff --git a/codec-socks/pom.xml b/codec-socks/pom.xml
new file mode 100644
index 0000000000..e094d2b2ae
--- /dev/null
+++ b/codec-socks/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ 4.0.0
+
+ io.netty
+ netty-parent
+ 4.0.0.Alpha8-SNAPSHOT
+
+
+ netty-codec-socks
+ jar
+
+ Netty/Codec/Socks
+
+
+
+ ${project.groupId}
+ netty-codec
+ ${project.version}
+
+
+ ${project.groupId}
+ netty-handler
+ ${project.version}
+
+
+
\ No newline at end of file
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequest.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequest.java
new file mode 100644
index 0000000000..b1edaf331d
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.util.CharsetUtil;
+
+import java.nio.charset.CharsetEncoder;
+
+/**
+ * An socks auth request.
+ *
+ * @see SocksAuthResponse
+ * @see SocksAuthRequestDecoder
+ */
+public final class SocksAuthRequest extends SocksRequest {
+ private static final CharsetEncoder asciiEncoder = CharsetUtil.getEncoder(CharsetUtil.US_ASCII);
+ private final String username;
+ private final String password;
+
+ /**
+ *
+ * @param username
+ * @param password
+ * @throws NullPointerException
+ * @throws IllegalArgumentException
+ */
+ public SocksAuthRequest(String username, String password) {
+ super(SocksRequestType.AUTH);
+ if (username == null) {
+ throw new NullPointerException("username");
+ }
+ if (password == null) {
+ throw new NullPointerException("username");
+ }
+ if (!asciiEncoder.canEncode(username) || !asciiEncoder.canEncode(password)) {
+ throw new IllegalArgumentException(" username: " + username + " or password: " + password +
+ " values should be in pure ascii");
+ }
+ if (username.length() > 255) {
+ throw new IllegalArgumentException(username + " exceeds 255 char limit");
+ }
+ if (password.length() > 255) {
+ throw new IllegalArgumentException(password + " exceeds 255 char limit");
+ }
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Returns username that needs to be authenticated
+ *
+ * @return username that needs to be authenticated
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Returns password that needs to be validated
+ *
+ * @return password that needs to be validated
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(username.length());
+ byteBuf.writeBytes(username.getBytes(CharsetUtil.US_ASCII));
+ byteBuf.writeByte(password.length());
+ byteBuf.writeBytes(password.getBytes(CharsetUtil.US_ASCII));
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequestDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequestDecoder.java
new file mode 100644
index 0000000000..661c464140
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthRequestDecoder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksAuthRequest}.
+ * Before returning SocksRequest decoder removes itself from pipeline.
+ */
+public class SocksAuthRequestDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_AUTH_REQUEST_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private SocksMessage.ProtocolVersion version;
+ private int fieldLength;
+ private String username;
+ private String password;
+ private SocksRequest msg = SocksCommonUtils.UNKNOWN_SOCKS_REQUEST;
+
+ public SocksAuthRequestDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksRequest decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte((byte) byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_USERNAME);
+ }
+ case READ_USERNAME: {
+ fieldLength = byteBuf.readByte();
+ username = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII);
+ checkpoint(State.READ_PASSWORD);
+ }
+ case READ_PASSWORD: {
+ fieldLength = byteBuf.readByte();
+ password = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII);
+ msg = new SocksAuthRequest(username, password);
+ }
+ }
+ ctx.pipeline().remove(this);
+ return msg;
+ }
+
+ enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_USERNAME,
+ READ_PASSWORD
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponse.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponse.java
new file mode 100644
index 0000000000..1515e87f76
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponse.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An socks auth response.
+ *
+ * @see SocksAuthRequest
+ * @see SocksAuthResponseDecoder
+ */
+public final class SocksAuthResponse extends SocksResponse {
+
+ private final AuthStatus authStatus;
+
+ /**
+ *
+ * @param authStatus
+ * @throws NullPointerException
+ */
+
+ public SocksAuthResponse(AuthStatus authStatus) {
+ super(SocksResponseType.AUTH);
+ if (authStatus == null) {
+ throw new NullPointerException("authStatus");
+ }
+ this.authStatus = authStatus;
+ }
+
+ /**
+ * Returns the {@link AuthStatus} of this {@link SocksAuthResponse}
+ *
+ * @return The {@link AuthStatus} of this {@link SocksAuthResponse}
+ */
+ public AuthStatus getAuthStatus() {
+ return authStatus;
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(authStatus.getByteValue());
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponseDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponseDecoder.java
new file mode 100644
index 0000000000..754770d03e
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksAuthResponseDecoder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksAuthResponse}.
+ * Before returning SocksResponse decoder removes itself from pipeline.
+ */
+public class SocksAuthResponseDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_AUTH_RESPONSE_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private SocksMessage.ProtocolVersion version;
+ private SocksMessage.AuthStatus authStatus;
+ private SocksResponse msg = SocksCommonUtils.UNKNOWN_SOCKS_RESPONSE;
+
+ public SocksAuthResponseDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksResponse decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte(byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_AUTH_RESPONSE);
+ }
+ case READ_AUTH_RESPONSE: {
+ authStatus = SocksMessage.AuthStatus.fromByte(byteBuf.readByte());
+ msg = new SocksAuthResponse(authStatus);
+ }
+ }
+ channelHandlerContext.pipeline().remove(this);
+ return msg;
+ }
+
+ public enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_AUTH_RESPONSE
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequest.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequest.java
new file mode 100644
index 0000000000..e8cd500524
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.util.CharsetUtil;
+import sun.net.util.IPAddressUtil;
+
+import java.net.IDN;
+
+/**
+ * An socks cmd request.
+ *
+ * @see SocksCmdResponse
+ * @see SocksCmdRequestDecoder
+ */
+public final class SocksCmdRequest extends SocksRequest {
+ private final CmdType cmdType;
+ private final AddressType addressType;
+ private final String host;
+ private final int port;
+
+ /**
+ *
+ * @param cmdType
+ * @param addressType
+ * @param host
+ * @param port
+ * @throws NullPointerException
+ * @throws IllegalArgumentException
+ */
+ public SocksCmdRequest(CmdType cmdType, AddressType addressType, String host, int port) {
+ super(SocksRequestType.CMD);
+ if (cmdType == null) {
+ throw new NullPointerException("cmdType");
+ }
+ if (addressType == null) {
+ throw new NullPointerException("addressType");
+ }
+ if (host == null) {
+ throw new NullPointerException("host");
+ }
+ switch (addressType) {
+ case IPv4:
+ if (!IPAddressUtil.isIPv4LiteralAddress(host)) {
+ throw new IllegalArgumentException(host + " is not a valid IPv4 address");
+ }
+ break;
+ case DOMAIN:
+ if (IDN.toASCII(host).length() > 255) {
+ throw new IllegalArgumentException(host + " IDN: " + IDN.toASCII(host) + " exceeds 255 char limit");
+ }
+ break;
+ case IPv6:
+ if (!IPAddressUtil.isIPv6LiteralAddress(host)) {
+ throw new IllegalArgumentException(host + " is not a valid IPv6 address");
+ }
+ break;
+ case UNKNOWN:
+ break;
+ }
+ if ((port < 0) && (port >= 65535)) {
+ throw new IllegalArgumentException(port + " is not in bounds 0 < x < 65536");
+ }
+ this.cmdType = cmdType;
+ this.addressType = addressType;
+ this.host = IDN.toASCII(host);
+ this.port = port;
+ }
+
+ /**
+ * Returns the {@link CmdType} of this {@link SocksCmdRequest}
+ *
+ * @return The {@link CmdType} of this {@link SocksCmdRequest}
+ */
+ public CmdType getCmdType() {
+ return cmdType;
+ }
+
+ /**
+ * Returns the {@link AddressType} of this {@link SocksCmdRequest}
+ *
+ * @return The {@link AddressType} of this {@link SocksCmdRequest}
+ */
+ public AddressType getAddressType() {
+ return addressType;
+ }
+
+ /**
+ * Returns host that is used as a parameter in {@link CmdType}
+ *
+ * @return host that is used as a parameter in {@link CmdType}
+ */
+ public String getHost() {
+ return IDN.toUnicode(host);
+ }
+
+ /**
+ * Returns port that is used as a parameter in {@link CmdType}
+ *
+ * @return port that is used as a parameter in {@link CmdType}
+ */
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(cmdType.getByteValue());
+ byteBuf.writeByte(0x00);
+ byteBuf.writeByte(addressType.getByteValue());
+ switch (addressType) {
+ case IPv4: {
+ byteBuf.writeBytes(IPAddressUtil.textToNumericFormatV4(host));
+ byteBuf.writeShort(port);
+ break;
+ }
+
+ case DOMAIN: {
+ byteBuf.writeByte(host.length());
+ byteBuf.writeBytes(host.getBytes(CharsetUtil.US_ASCII));
+ byteBuf.writeShort(port);
+ break;
+ }
+
+ case IPv6: {
+ byteBuf.writeBytes(IPAddressUtil.textToNumericFormatV6(host));
+ byteBuf.writeShort(port);
+ break;
+ }
+ }
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequestDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequestDecoder.java
new file mode 100644
index 0000000000..a5243a1ccb
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdRequestDecoder.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksCmdRequest}.
+ * Before returning SocksRequest decoder removes itself from pipeline.
+ */
+public class SocksCmdRequestDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_CMD_REQUEST_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private SocksMessage.ProtocolVersion version;
+ private int fieldLength;
+ private SocksMessage.CmdType cmdType;
+ private SocksMessage.AddressType addressType;
+ private byte reserved;
+ private String host;
+ private int port;
+ private SocksRequest msg = SocksCommonUtils.UNKNOWN_SOCKS_REQUEST;
+
+ public SocksCmdRequestDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksRequest decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte(byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_CMD_HEADER);
+ }
+ case READ_CMD_HEADER: {
+ cmdType = SocksMessage.CmdType.fromByte(byteBuf.readByte());
+ reserved = byteBuf.readByte();
+ addressType = SocksMessage.AddressType.fromByte(byteBuf.readByte());
+ checkpoint(State.READ_CMD_ADDRESS);
+ }
+ case READ_CMD_ADDRESS: {
+ switch (addressType) {
+ case IPv4: {
+ host = SocksCommonUtils.intToIp(byteBuf.readInt());
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdRequest(cmdType, addressType, host, port);
+ break;
+ }
+ case DOMAIN: {
+ fieldLength = byteBuf.readByte();
+ host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII);
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdRequest(cmdType, addressType, host, port);
+ break;
+ }
+ case IPv6: {
+ host = SocksCommonUtils.ipv6toStr(byteBuf.readBytes(16).array());
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdRequest(cmdType, addressType, host, port);
+ break;
+ }
+ case UNKNOWN:
+ break;
+ }
+ }
+ }
+ ctx.pipeline().remove(this);
+ return msg;
+ }
+
+ enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_CMD_HEADER,
+ READ_CMD_ADDRESS
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponse.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponse.java
new file mode 100644
index 0000000000..6946fd23f3
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponse.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An socks cmd response.
+ *
+ * @see SocksCmdRequest
+ * @see SocksCmdResponseDecoder
+ */
+public final class SocksCmdResponse extends SocksResponse {
+ private final CmdStatus cmdStatus;
+
+ private final AddressType addressType;
+ // All arrays are initialized on construction time to 0/false/null remove array Initialization
+ private static final byte[] IPv4_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00};
+ private static final byte[] IPv6_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+ /**
+ *
+ * @param cmdStatus
+ * @param addressType
+ * @throws NullPointerException
+ */
+ public SocksCmdResponse(CmdStatus cmdStatus, AddressType addressType) {
+ super(SocksResponseType.CMD);
+ if (cmdStatus == null) {
+ throw new NullPointerException("cmdStatus");
+ }
+ if (addressType == null) {
+ throw new NullPointerException("addressType");
+ }
+ this.cmdStatus = cmdStatus;
+ this.addressType = addressType;
+ }
+
+ /**
+ * Returns the {@link CmdStatus} of this {@link SocksCmdResponse}
+ *
+ * @return The {@link CmdStatus} of this {@link SocksCmdResponse}
+ */
+ public CmdStatus getCmdStatus() {
+ return cmdStatus;
+ }
+
+ /**
+ * Returns the {@link AddressType} of this {@link SocksCmdResponse}
+ *
+ * @return The {@link AddressType} of this {@link SocksCmdResponse}
+ */
+ public AddressType getAddressType() {
+ return addressType;
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(cmdStatus.getByteValue());
+ byteBuf.writeByte(0x00);
+ byteBuf.writeByte(addressType.getByteValue());
+ switch (addressType) {
+ case IPv4: {
+ byteBuf.writeBytes(IPv4_HOSTNAME_ZEROED);
+ byteBuf.writeShort(0);
+ break;
+ }
+ case DOMAIN: {
+ byteBuf.writeByte(1); // domain length
+ byteBuf.writeByte(0); // domain value
+ byteBuf.writeShort(0); // port value
+ break;
+ }
+ case IPv6: {
+ byteBuf.writeBytes(IPv6_HOSTNAME_ZEROED);
+ byteBuf.writeShort(0);
+ break;
+ }
+ }
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponseDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponseDecoder.java
new file mode 100644
index 0000000000..cff0880479
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksCmdResponseDecoder.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+import io.netty.util.CharsetUtil;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksCmdResponse}.
+ * Before returning SocksResponse decoder removes itself from pipeline.
+ */
+public class SocksCmdResponseDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_CMD_RESPONSE_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private SocksMessage.ProtocolVersion version;
+ private int fieldLength;
+ private SocksMessage.CmdStatus cmdStatus;
+ private SocksMessage.AddressType addressType;
+ private byte reserved;
+ private String host;
+ private int port;
+ private SocksResponse msg = SocksCommonUtils.UNKNOWN_SOCKS_RESPONSE;
+
+
+ public SocksCmdResponseDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksResponse decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte(byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_CMD_HEADER);
+ }
+ case READ_CMD_HEADER: {
+ cmdStatus = SocksMessage.CmdStatus.fromByte(byteBuf.readByte());
+ reserved = byteBuf.readByte();
+ addressType = SocksMessage.AddressType.fromByte(byteBuf.readByte());
+ checkpoint(State.READ_CMD_ADDRESS);
+ }
+ case READ_CMD_ADDRESS: {
+ switch (addressType) {
+ case IPv4: {
+ host = SocksCommonUtils.intToIp(byteBuf.readInt());
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdResponse(cmdStatus, addressType);
+ break;
+ }
+ case DOMAIN: {
+ fieldLength = byteBuf.readByte();
+ host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII);
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdResponse(cmdStatus, addressType);
+ break;
+ }
+ case IPv6: {
+ host = SocksCommonUtils.ipv6toStr(byteBuf.readBytes(16).array());
+ port = byteBuf.readUnsignedShort();
+ msg = new SocksCmdResponse(cmdStatus, addressType);
+ break;
+ }
+ case UNKNOWN:
+ break;
+ }
+ }
+ }
+ ctx.pipeline().remove(this);
+ return msg;
+ }
+
+ public enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_CMD_HEADER,
+ READ_CMD_ADDRESS
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksCommonUtils.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksCommonUtils.java
new file mode 100644
index 0000000000..4768c2222e
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksCommonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+final class SocksCommonUtils {
+ public static final SocksRequest UNKNOWN_SOCKS_REQUEST = new UnknownSocksRequest();
+ public static final SocksResponse UNKNOWN_SOCKS_RESPONSE = new UnknownSocksResponse();
+
+ private static final int SECOND_ADDRESS_OCTET_SHIFT = 16;
+ private static final int FIRST_ADDRESS_OCTET_SHIFT = 24;
+ private static final int THIRD_ADDRESS_OCTET_SHIFT = 8;
+ private static final int XOR_DEFAULT_VALUE = 0xff;
+
+ /**
+ * A constructor to stop this class being constructed.
+ */
+ private SocksCommonUtils() {
+ //NOOP
+ }
+
+ public static String intToIp(int i) {
+ return new StringBuilder().append((i >> FIRST_ADDRESS_OCTET_SHIFT) & XOR_DEFAULT_VALUE).append(".")
+ .append((i >> SECOND_ADDRESS_OCTET_SHIFT) & XOR_DEFAULT_VALUE).append(".")
+ .append((i >> THIRD_ADDRESS_OCTET_SHIFT) & XOR_DEFAULT_VALUE).append(".")
+ .append(i & XOR_DEFAULT_VALUE).toString();
+ }
+
+ private static final char[] ipv6conseqZeroFiller = {':', ':'};
+ private static final char ipv6hextetSeparator = ':';
+
+ /*
+ * Convert numeric IPv6 to compressed format, where
+ * the longest sequence of 0's (with 2 or more 0's) is replaced with "::"
+ */
+ public static String ipv6toCompressedForm(byte[] src) {
+ assert src.length == 16;
+ //Find the longest sequence of 0's
+ //start of compressed region (hextet index)
+ int cmprHextet = -1;
+ //length of compressed region
+ int cmprSize = 0;
+ for (int hextet = 0; hextet < 8;) {
+ int curByte = hextet * 2;
+ int size = 0;
+ while (curByte < src.length && src[curByte] == 0
+ && src[curByte + 1] == 0) {
+ curByte += 2;
+ size++;
+ }
+ if (size > cmprSize) {
+ cmprHextet = hextet;
+ cmprSize = size;
+ }
+ hextet = (curByte / 2) + 1;
+ }
+ if (cmprHextet == -1 || cmprSize < 2) {
+ //No compression can be applied
+ return ipv6toStr(src);
+ }
+ StringBuilder sb = new StringBuilder(39);
+ ipv6toStr(sb, src, 0, cmprHextet);
+ sb.append(ipv6conseqZeroFiller);
+ ipv6toStr(sb, src, cmprHextet + cmprSize, 8);
+ return sb.toString();
+ }
+
+ /*
+ * Convert numeric IPv6 to standard (non-compressed) format.
+ *
+ * Borrowed from Inet6Address.java #numericToTextFormat(byte[])
+ * Changed StringBuffer -> StringBuilder and ":" -> ':' for performance.
+ */
+ public static String ipv6toStr(byte[] src) {
+ assert src.length == 16;
+ StringBuilder sb = new StringBuilder(39);
+ ipv6toStr(sb, src, 0, 8);
+ return sb.toString();
+ }
+
+ private static void ipv6toStr(StringBuilder sb, byte[] src,
+ int fromHextet, int toHextet) {
+ for (int i = fromHextet; i < toHextet; i++) {
+ sb.append(Integer.toHexString(((src[i << 1] << 8) & 0xff00)
+ | (src[(i << 1) + 1] & 0xff)));
+ if (i < toHextet - 1) {
+ sb.append(ipv6hextetSeparator);
+ }
+ }
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequest.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequest.java
new file mode 100644
index 0000000000..d4bbfdf6a6
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An socks init request.
+ *
+ * @see SocksInitResponse
+ * @see SocksInitRequestDecoder
+ */
+public final class SocksInitRequest extends SocksRequest {
+ private final List authSchemes;
+
+ public SocksInitRequest(List authSchemes) {
+ super(SocksRequestType.INIT);
+ if (authSchemes == null) {
+ throw new NullPointerException("authSchemes");
+ }
+ this.authSchemes = authSchemes;
+ }
+
+ /**
+ * Returns the List<{@link AuthScheme}> of this {@link SocksInitRequest}
+ *
+ * @return The List<{@link AuthScheme}> of this {@link SocksInitRequest}
+ */
+ public List getAuthSchemes() {
+ return Collections.unmodifiableList(authSchemes);
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(authSchemes.size());
+ for (AuthScheme authScheme : authSchemes) {
+ byteBuf.writeByte(authScheme.getByteValue());
+ }
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequestDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequestDecoder.java
new file mode 100644
index 0000000000..04a51bf8e5
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitRequestDecoder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksInitRequest}.
+ * Before returning SocksRequest decoder removes itself from pipeline.
+ */
+public class SocksInitRequestDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_INIT_REQUEST_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private final List authSchemes = new ArrayList();
+ private SocksMessage.ProtocolVersion version;
+ private byte authSchemeNum;
+ private SocksRequest msg = SocksCommonUtils.UNKNOWN_SOCKS_REQUEST;
+
+ public SocksInitRequestDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksRequest decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte(byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_AUTH_SCHEMES);
+ }
+ case READ_AUTH_SCHEMES: {
+ authSchemes.clear();
+ authSchemeNum = byteBuf.readByte();
+ for (int i = 0; i < authSchemeNum; i++) {
+ authSchemes.add(SocksMessage.AuthScheme.fromByte(byteBuf.readByte()));
+ }
+ msg = new SocksInitRequest(authSchemes);
+ break;
+ }
+ }
+ ctx.pipeline().remove(this);
+ return msg;
+ }
+
+ enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_AUTH_SCHEMES
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponse.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponse.java
new file mode 100644
index 0000000000..77f78caa46
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponse.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An socks init response.
+ *
+ * @see SocksInitRequest
+ * @see SocksInitResponseDecoder
+ */
+public final class SocksInitResponse extends SocksResponse {
+ private final AuthScheme authScheme;
+
+ /**
+ *
+ * @param authScheme
+ * @throws NullPointerException
+ */
+ public SocksInitResponse(AuthScheme authScheme) {
+ super(SocksResponseType.INIT);
+ if (authScheme == null) {
+ throw new NullPointerException("authScheme");
+ }
+ this.authScheme = authScheme;
+ }
+
+ /**
+ * Returns the {@link AuthScheme} of this {@link SocksInitResponse}
+ *
+ * @return The {@link AuthScheme} of this {@link SocksInitResponse}
+ */
+ public AuthScheme getAuthScheme() {
+ return authScheme;
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ byteBuf.writeByte(getProtocolVersion().getByteValue());
+ byteBuf.writeByte(authScheme.getByteValue());
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponseDecoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponseDecoder.java
new file mode 100644
index 0000000000..b1bf2c4c1f
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksInitResponseDecoder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link SocksInitResponse}.
+ * Before returning SocksResponse decoder removes itself from pipeline.
+ */
+public class SocksInitResponseDecoder extends ReplayingDecoder {
+ private static final String name = "SOCKS_INIT_RESPONSE_DECODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private SocksMessage.ProtocolVersion version;
+ private SocksMessage.AuthScheme authScheme;
+
+ private SocksResponse msg = SocksCommonUtils.UNKNOWN_SOCKS_RESPONSE;
+
+ public SocksInitResponseDecoder() {
+ super(State.CHECK_PROTOCOL_VERSION);
+ }
+
+ @Override
+ public SocksResponse decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+ switch (state()) {
+ case CHECK_PROTOCOL_VERSION: {
+ version = SocksMessage.ProtocolVersion.fromByte(byteBuf.readByte());
+ if (version != SocksMessage.ProtocolVersion.SOCKS5) {
+ break;
+ }
+ checkpoint(State.READ_PREFFERED_AUTH_TYPE);
+ }
+ case READ_PREFFERED_AUTH_TYPE: {
+ authScheme = SocksMessage.AuthScheme.fromByte(byteBuf.readByte());
+ msg = new SocksInitResponse(authScheme);
+ break;
+ }
+ }
+ ctx.pipeline().remove(this);
+ return msg;
+ }
+
+ public enum State {
+ CHECK_PROTOCOL_VERSION,
+ READ_PREFFERED_AUTH_TYPE
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksMessage.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksMessage.java
new file mode 100644
index 0000000000..34303ed547
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksMessage.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An abstract class that defines a SocksMessage, providing common properties for
+ * {@link SocksRequest} and {@link SocksResponse}.
+ *
+ * @see SocksRequest
+ * @see SocksResponse
+ */
+
+public abstract class SocksMessage {
+ private final MessageType messageType;
+ private final ProtocolVersion protocolVersion = ProtocolVersion.SOCKS5;
+
+ /**
+ *
+ * @param messageType
+ * @throws NullPointerException
+ */
+ public SocksMessage(MessageType messageType) {
+ if (messageType == null) {
+ throw new NullPointerException("messageType");
+ }
+ this.messageType = messageType;
+ }
+
+ /**
+ * Returns the {@link MessageType} of this {@link SocksMessage}
+ *
+ * @return The {@link MessageType} of this {@link SocksMessage}
+ */
+ public MessageType getMessageType() {
+ return messageType;
+ }
+
+ public enum MessageType {
+ REQUEST,
+ RESPONSE,
+ UNKNOWN
+ }
+
+ public enum AuthScheme {
+ NO_AUTH((byte) 0x00),
+ AUTH_GSSAPI((byte) 0x01),
+ AUTH_PASSWORD((byte) 0x02),
+ UNKNOWN((byte) 0xff);
+
+ private final byte b;
+
+ private AuthScheme(byte b) {
+ this.b = b;
+ }
+
+ public static AuthScheme fromByte(byte b) {
+ for (AuthScheme code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ public enum CmdType {
+ CONNECT((byte) 0x01),
+ BIND((byte) 0x02),
+ UDP((byte) 0x03),
+ UNKNOWN((byte) 0xff);
+
+ private final byte b;
+
+ private CmdType(byte b) {
+ this.b = b;
+ }
+
+ public static CmdType fromByte(byte b) {
+ for (CmdType code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ public enum AddressType {
+ IPv4((byte) 0x01),
+ DOMAIN((byte) 0x03),
+ IPv6((byte) 0x04),
+ UNKNOWN((byte) 0xff);
+
+ private final byte b;
+
+ private AddressType(byte b) {
+ this.b = b;
+ }
+
+ public static AddressType fromByte(byte b) {
+ for (AddressType code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ public enum AuthStatus {
+ SUCCESS((byte) 0x00),
+ FAILURE((byte) 0xff);
+
+ private final byte b;
+
+ private AuthStatus(byte b) {
+ this.b = b;
+ }
+
+ public static AuthStatus fromByte(byte b) {
+ for (AuthStatus code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return FAILURE;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ public enum CmdStatus {
+ SUCCESS((byte) 0x00),
+ FAILURE((byte) 0x01),
+ FORBIDDEN((byte) 0x02),
+ NETWORK_UNREACHABLE((byte) 0x03),
+ HOST_UNREACHABLE((byte) 0x04),
+ REFUSED((byte) 0x05),
+ TTL_EXPIRED((byte) 0x06),
+ COMMAND_NOT_SUPPORTED((byte) 0x07),
+ ADDRESS_NOT_SUPPORTED((byte) 0x08),
+ UNASSIGNED((byte) 0xff);
+
+ private final byte b;
+
+ private CmdStatus(byte b) {
+ this.b = b;
+ }
+
+ public static CmdStatus fromByte(byte b) {
+ for (CmdStatus code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return UNASSIGNED;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ public enum ProtocolVersion {
+ SOCKS4a((byte) 0x04),
+ SOCKS5((byte) 0x05),
+ UNKNOWN((byte) 0xff);
+
+ private final byte b;
+
+ private ProtocolVersion(byte b) {
+ this.b = b;
+ }
+
+ public static ProtocolVersion fromByte(byte b) {
+ for (ProtocolVersion code : values()) {
+ if (code.b == b) {
+ return code;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public byte getByteValue() {
+ return this.b;
+ }
+ }
+
+ /**
+ * Returns the {@link ProtocolVersion} of this {@link SocksMessage}
+ *
+ * @return The {@link ProtocolVersion} of this {@link SocksMessage}
+ */
+ public ProtocolVersion getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ /**
+ * Encode socks message into its byte representation and write it into byteBuf
+ *
+ * @param byteBuf
+ * @see ByteBuf
+ */
+ public abstract void encodeAsByteBuf(ByteBuf byteBuf);
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksMessageEncoder.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksMessageEncoder.java
new file mode 100644
index 0000000000..9432ada2fe
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksMessageEncoder.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+/**
+ * Encodes an {@link SocksMessage} into a {@link ByteBuf}.
+ * {@link MessageToByteEncoder} implementation.
+ * Use this with {@link SocksInitRequest}, {@link SocksInitResponse}, {@link SocksAuthRequest},
+ * {@link SocksAuthResponse}, {@link SocksCmdRequest} and {@link SocksCmdResponse}
+ */
+@ChannelHandler.Sharable
+public class SocksMessageEncoder extends MessageToByteEncoder {
+ private static final String name = "SOCKS_MESSAGE_ENCODER";
+
+ public static String getName() {
+ return name;
+ }
+
+ public SocksMessageEncoder() {
+ super(SocksMessage.class);
+ }
+
+ @Override
+ public void encode(ChannelHandlerContext ctx, SocksMessage msg, ByteBuf out) throws Exception {
+ msg.encodeAsByteBuf(out);
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksRequest.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksRequest.java
new file mode 100644
index 0000000000..4197915678
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksRequest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+/**
+ * An abstract class that defines a SocksRequest, providing common properties for
+ * {@link SocksInitRequest}, {@link SocksAuthRequest}, {@link SocksCmdRequest} and {@link UnknownSocksRequest}.
+ *
+ * @see SocksInitRequest
+ * @see SocksAuthRequest
+ * @see SocksCmdRequest
+ * @see UnknownSocksRequest
+ */
+public abstract class SocksRequest extends SocksMessage {
+ private final SocksRequestType socksRequestType;
+
+ /**
+ *
+ * @param socksRequestType
+ * @throws NullPointerException
+ */
+ public SocksRequest(SocksRequestType socksRequestType) {
+ super(MessageType.REQUEST);
+ if (socksRequestType == null) {
+ throw new NullPointerException("socksRequestType");
+ }
+ this.socksRequestType = socksRequestType;
+ }
+
+ /**
+ * Returns socks request type
+ *
+ * @return socks request type
+ */
+ public SocksRequestType getSocksRequestType() {
+ return socksRequestType;
+ }
+
+ /**
+ * Type of socks request
+ */
+ public enum SocksRequestType {
+ INIT,
+ AUTH,
+ CMD,
+ UNKNOWN
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/SocksResponse.java b/codec-socks/src/main/java/io/netty/codec/socks/SocksResponse.java
new file mode 100644
index 0000000000..2b55e90d22
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/SocksResponse.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+/**
+ * An abstract class that defines a SocksResponse, providing common properties for
+ * {@link SocksInitResponse}, {@link SocksAuthResponse}, {@link SocksCmdResponse} and {@link UnknownSocksResponse}.
+ *
+ * @see SocksInitResponse
+ * @see SocksAuthResponse
+ * @see SocksCmdResponse
+ * @see UnknownSocksResponse
+ */
+public abstract class SocksResponse extends SocksMessage {
+ private final SocksResponseType socksResponseType;
+
+ /**
+ *
+ * @param socksResponseType
+ * @throws NullPointerException
+ */
+ public SocksResponse(SocksResponseType socksResponseType) {
+ super(MessageType.RESPONSE);
+ if (socksResponseType == null) {
+ throw new NullPointerException("socksResponseType");
+ }
+ this.socksResponseType = socksResponseType;
+ }
+
+ /**
+ * Returns socks response type
+ *
+ * @return socks response type
+ */
+ public SocksResponseType getSocksResponseType() {
+ return socksResponseType;
+ }
+
+ /**
+ * Type of socks response
+ */
+ public enum SocksResponseType {
+ INIT,
+ AUTH,
+ CMD,
+ UNKNOWN
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksMessage.java b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksMessage.java
new file mode 100644
index 0000000000..bbf04ff8bf
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksMessage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An unknown socks message.
+ *
+ * @see UnknownSocksRequest
+ * @see UnknownSocksResponse
+ */
+public final class UnknownSocksMessage extends SocksMessage {
+
+ public UnknownSocksMessage() {
+ super(MessageType.UNKNOWN);
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ // NOOP
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksRequest.java b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksRequest.java
new file mode 100644
index 0000000000..477b1d939e
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksRequest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An unknown socks request.
+ *
+ * @see SocksInitRequestDecoder
+ * @see SocksAuthRequestDecoder
+ * @see SocksCmdRequestDecoder
+ */
+public final class UnknownSocksRequest extends SocksRequest {
+
+ public UnknownSocksRequest() {
+ super(SocksRequestType.UNKNOWN);
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ // NOOP
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksResponse.java b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksResponse.java
new file mode 100644
index 0000000000..6f6b27ef16
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/UnknownSocksResponse.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * An unknown socks response.
+ *
+ * @see SocksInitResponseDecoder
+ * @see SocksAuthResponseDecoder
+ * @see SocksCmdResponseDecoder
+ */
+public final class UnknownSocksResponse extends SocksResponse {
+
+ public UnknownSocksResponse() {
+ super(SocksResponseType.UNKNOWN);
+ }
+
+ @Override
+ public void encodeAsByteBuf(ByteBuf byteBuf) {
+ // NOOP
+ }
+}
diff --git a/codec-socks/src/main/java/io/netty/codec/socks/package-info.java b/codec-socks/src/main/java/io/netty/codec/socks/package-info.java
new file mode 100644
index 0000000000..fd07765ff4
--- /dev/null
+++ b/codec-socks/src/main/java/io/netty/codec/socks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Encoder, decoder and their related message types for Socks.
+ */
+package io.netty.codec.socks;
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestDecoderTest.java
new file mode 100644
index 0000000000..e7dbdc517a
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestDecoderTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.channel.embedded.EmbeddedByteChannel;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class SocksAuthRequestDecoderTest {
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocksAuthRequestDecoderTest.class);
+ @Test
+ public void testAuthRequestDecoder() {
+ String username = "test";
+ String password = "test";
+ SocksAuthRequest msg = new SocksAuthRequest(username, password);
+ SocksAuthRequestDecoder decoder = new SocksAuthRequestDecoder();
+ EmbeddedByteChannel embedder = new EmbeddedByteChannel(decoder);
+ SocksCommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
+ msg = (SocksAuthRequest) embedder.readInbound();
+ assertTrue(msg.getUsername().equals(username));
+ assertTrue(msg.getUsername().equals(password));
+ assertNull(embedder.readInbound());
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestTest.java
new file mode 100644
index 0000000000..79ae1adb8d
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthRequestTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksAuthRequestTest {
+ @Test
+ public void testConstructorParamsAreNotNull() {
+ try {
+ new SocksAuthRequest(null, "");
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ try {
+ new SocksAuthRequest("", null);
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+
+ @Test
+ public void testUsernameOrPasswordIsNotAscii() {
+ try {
+ new SocksAuthRequest("παράδειγμα.δοκιμή", "password");
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ try {
+ new SocksAuthRequest("username", "παράδειγμα.δοκιμή");
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void testUsernameOrPasswordLengthIsLessThan255Chars() {
+ try {
+ new SocksAuthRequest(
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword",
+ "password");
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ try {
+ new SocksAuthRequest("password",
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
+ "passwordpasswordpasswordpasswordpasswordpasswordpassword");
+ } catch (Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseDecoderTest.java
new file mode 100644
index 0000000000..e82794738f
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseDecoderTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.channel.embedded.EmbeddedByteChannel;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class SocksAuthResponseDecoderTest {
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocksAuthResponseDecoderTest.class);
+ private void testSocksAuthResponseDecoderWithDifferentParams(SocksMessage.AuthStatus authStatus){
+ logger.debug("Testing SocksAuthResponseDecoder with authStatus: "+ authStatus);
+ SocksAuthResponse msg = new SocksAuthResponse(authStatus);
+ SocksAuthResponseDecoder decoder = new SocksAuthResponseDecoder();
+ EmbeddedByteChannel embedder = new EmbeddedByteChannel(decoder);
+ SocksCommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
+ msg = (SocksAuthResponse) embedder.readInbound();
+ assertTrue(msg.getAuthStatus().equals(authStatus));
+ assertNull(embedder.readInbound());
+ }
+
+ @Test
+ public void testSocksCmdResponseDecoder(){
+ for (SocksMessage.AuthStatus authStatus: SocksMessage.AuthStatus.values()){
+ testSocksAuthResponseDecoderWithDifferentParams(authStatus);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseTest.java
new file mode 100644
index 0000000000..ae9f43669b
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksAuthResponseTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksAuthResponseTest {
+ @Test
+ public void testConstructorParamsAreNotNull() {
+ try {
+ new SocksAuthResponse(null);
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestDecoderTest.java
new file mode 100644
index 0000000000..9d89e59102
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestDecoderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.channel.embedded.EmbeddedByteChannel;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import sun.net.util.IPAddressUtil;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class SocksCmdRequestDecoderTest {
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocksCmdRequestDecoderTest.class);
+
+ private void testSocksCmdRequestDecoderWithDifferentParams(SocksMessage.CmdType cmdType, SocksMessage.AddressType addressType, String host, int port) {
+ logger.debug("Testing cmdType: " + cmdType + " addressType: " + addressType + " host: " + host + " port: " + port);
+ SocksCmdRequest msg = new SocksCmdRequest(cmdType, addressType, host, port);
+ SocksCmdRequestDecoder decoder = new SocksCmdRequestDecoder();
+ EmbeddedByteChannel embedder = new EmbeddedByteChannel(decoder);
+ SocksCommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
+ if (msg.getAddressType() == SocksMessage.AddressType.UNKNOWN) {
+ assertTrue(embedder.readInbound() instanceof UnknownSocksRequest);
+ } else {
+ msg = (SocksCmdRequest) embedder.readInbound();
+ assertTrue(msg.getCmdType().equals(cmdType));
+ assertTrue(msg.getAddressType().equals(addressType));
+ assertTrue(msg.getHost().equals(host));
+ assertTrue(msg.getPort() == port);
+ }
+ assertNull(embedder.readInbound());
+ }
+
+ @Test
+ public void testCmdRequestDecoderIPv4() {
+ String[] hosts = {"127.0.0.1",};
+ int[] ports = {0, 32769, 65535 };
+ for (SocksMessage.CmdType cmdType : SocksMessage.CmdType.values()) {
+ for (String host : hosts) {
+ for (int port : ports) {
+ testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksMessage.AddressType.IPv4, host, port);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCmdRequestDecoderIPv6() {
+ String[] hosts = {SocksCommonUtils.ipv6toStr(IPAddressUtil.textToNumericFormatV6("::1"))};
+ int[] ports = {0, 32769, 65535};
+ for (SocksMessage.CmdType cmdType : SocksMessage.CmdType.values()) {
+ for (String host : hosts) {
+ for (int port : ports) {
+ testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksMessage.AddressType.IPv6, host, port);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCmdRequestDecoderDomain() {
+ String[] hosts = {"google.com" ,
+ "مثال.إختبار",
+ "παράδειγμα.δοκιμή",
+ "مثال.آزمایشی",
+ "пример.испытание",
+ "בײַשפּיל.טעסט",
+ "例子.测试",
+ "例子.測試",
+ "उदाहरण.परीक्षा",
+ "例え.テスト",
+ "실례.테스트",
+ "உதாரணம்.பரிட்சை"};
+ int[] ports = {0, 32769, 65535};
+ for (SocksMessage.CmdType cmdType : SocksMessage.CmdType.values()) {
+ for (String host : hosts) {
+ for (int port : ports) {
+ testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksMessage.AddressType.DOMAIN, host, port);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCmdRequestDecoderUnknown() {
+ String host = "google.com";
+ int port = 80;
+ for (SocksMessage.CmdType cmdType : SocksMessage.CmdType.values()) {
+ testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksMessage.AddressType.UNKNOWN, host, port);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestTest.java
new file mode 100644
index 0000000000..cf6fa6a374
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdRequestTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksCmdRequestTest {
+ @Test
+ public void testConstructorParamsAreNotNull(){
+ try {
+ new SocksCmdRequest(null, SocksMessage.AddressType.UNKNOWN, "", 0);
+ } catch (Exception e){
+ assertTrue(e instanceof NullPointerException);
+ }
+
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.UNKNOWN, null, "", 0);
+ } catch (Exception e){
+ assertTrue(e instanceof NullPointerException);
+ }
+
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.UNKNOWN, SocksMessage.AddressType.UNKNOWN, null, 0);
+ } catch (Exception e){
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+
+ @Test
+ public void testIPv4CorrectAddress(){
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.BIND, SocksMessage.AddressType.IPv4, "54.54.1111.253", 0);
+ } catch (Exception e){
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void testIPv6CorrectAddress(){
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.BIND, SocksMessage.AddressType.IPv6, "xxx:xxx:xxx", 0);
+ } catch (Exception e){
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void testIDNNotExceeds255CharsLimit(){
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.BIND, SocksMessage.AddressType.DOMAIN,
+ "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
+ "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
+ "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
+ "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή", 0);
+ } catch (Exception e){
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void testValidPortRange(){
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.BIND, SocksMessage.AddressType.DOMAIN,
+ "παράδειγμα.δοκιμήπαράδει", -1);
+ } catch (Exception e){
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+
+ try {
+ new SocksCmdRequest(SocksMessage.CmdType.BIND, SocksMessage.AddressType.DOMAIN,
+ "παράδειγμα.δοκιμήπαράδει", 65536);
+ } catch (Exception e){
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseDecoderTest.java
new file mode 100644
index 0000000000..bac94b8353
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseDecoderTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.channel.embedded.EmbeddedByteChannel;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class SocksCmdResponseDecoderTest {
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocksCmdResponseDecoderTest.class);
+
+ private void testSocksCmdResponseDecoderWithDifferentParams(SocksMessage.CmdStatus cmdStatus, SocksMessage.AddressType addressType){
+ logger.debug("Testing cmdStatus: " + cmdStatus + " addressType: " + addressType);
+ SocksResponse msg = new SocksCmdResponse(cmdStatus, addressType);
+ SocksCmdResponseDecoder decoder = new SocksCmdResponseDecoder();
+ EmbeddedByteChannel embedder = new EmbeddedByteChannel(decoder);
+ SocksCommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
+ if (addressType == SocksMessage.AddressType.UNKNOWN){
+ assertTrue(embedder.readInbound() instanceof UnknownSocksResponse);
+ } else {
+ msg = (SocksCmdResponse) embedder.readInbound();
+ assertTrue(((SocksCmdResponse) msg).getCmdStatus().equals(cmdStatus));
+ }
+ assertNull(embedder.readInbound());
+ }
+
+ @Test
+ public void testSocksCmdResponseDecoder(){
+ for (SocksMessage.CmdStatus cmdStatus: SocksMessage.CmdStatus.values()){
+ for (SocksMessage.AddressType addressType: SocksMessage.AddressType.values()){
+ testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, addressType);
+ }
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseTest.java
new file mode 100644
index 0000000000..a3e7fcfd23
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksCmdResponseTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksCmdResponseTest {
+ @Test
+ public void testConstructorParamsAreNotNull() {
+ try {
+ new SocksCmdResponse(null, SocksMessage.AddressType.UNKNOWN);
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ try {
+ new SocksCmdResponse(SocksMessage.CmdStatus.UNASSIGNED, null);
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksCommonTestUtils.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksCommonTestUtils.java
new file mode 100644
index 0000000000..bfb7f93cb1
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksCommonTestUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.embedded.EmbeddedByteChannel;
+
+class SocksCommonTestUtils {
+ /**
+ * A constructor to stop this class being constructed.
+ */
+ private SocksCommonTestUtils() {
+ //NOOP
+ }
+
+ public static void writeMessageIntoEmbedder(EmbeddedByteChannel embedder, SocksMessage msg) {
+ ByteBuf buf = Unpooled.buffer();
+ msg.encodeAsByteBuf(buf);
+ embedder.writeInbound(buf);
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksInitRequestTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksInitRequestTest.java
new file mode 100644
index 0000000000..737a5f07ec
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksInitRequestTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksInitRequestTest {
+ @Test
+ public void testConstructorParamsAreNotNull(){
+ try {
+ new SocksInitRequest(null);
+ } catch (Exception e){
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+}
diff --git a/codec-socks/src/test/java/io/netty/codec/socks/SocksInitResponseTest.java b/codec-socks/src/test/java/io/netty/codec/socks/SocksInitResponseTest.java
new file mode 100644
index 0000000000..91c971f5b0
--- /dev/null
+++ b/codec-socks/src/test/java/io/netty/codec/socks/SocksInitResponseTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.codec.socks;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class SocksInitResponseTest {
+ @Test
+ public void testConstructorParamsAreNotNull() {
+ try {
+ new SocksInitResponse(null);
+ } catch (Exception e) {
+ assertTrue(e instanceof NullPointerException);
+ }
+ }
+}
diff --git a/example/pom.xml b/example/pom.xml
index b0543b6a3e..e5a199c1bf 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -14,41 +14,47 @@
~ License for the specific language governing permissions and limitations
~ under the License.
-->
-
+
- 4.0.0
-
- io.netty
- netty-parent
- 4.0.0.Alpha8-SNAPSHOT
-
+ 4.0.0
+
+ io.netty
+ netty-parent
+ 4.0.0.Alpha8-SNAPSHOT
+
- netty-example
- jar
+ netty-example
+ jar
- Netty/Example
+ Netty/Example
-
-
- ${project.groupId}
- netty-transport
- ${project.version}
-
-
- ${project.groupId}
- netty-handler
- ${project.version}
-
-
- ${project.groupId}
- netty-codec-http
- ${project.version}
-
+
+
+ ${project.groupId}
+ netty-transport
+ ${project.version}
+
+
+ ${project.groupId}
+ netty-handler
+ ${project.version}
+
+
+ ${project.groupId}
+ netty-codec-http
+ ${project.version}
+
+
+ ${project.groupId}
+ netty-codec-socks
+ ${project.version}
+
-
- com.google.protobuf
- protobuf-java
-
-
+
+ com.google.protobuf
+ protobuf-java
+
+
diff --git a/example/src/main/java/io/netty/example/socksproxy/CallbackNotifier.java b/example/src/main/java/io/netty/example/socksproxy/CallbackNotifier.java
new file mode 100644
index 0000000000..1d19b4bfbd
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/CallbackNotifier.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.channel.ChannelHandlerContext;
+
+public interface CallbackNotifier {
+ void onSuccess(final ChannelHandlerContext outboundCtx);
+
+ void onFailure(final ChannelHandlerContext outboundCtx, final Throwable cause);
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/DirectClientHandler.java b/example/src/main/java/io/netty/example/socksproxy/DirectClientHandler.java
new file mode 100644
index 0000000000..06a6a449a3
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/DirectClientHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundByteHandlerAdapter;
+
+
+public final class DirectClientHandler extends ChannelInboundByteHandlerAdapter {
+ private static final String name = "DIRECT_CLIENT_HANDLER";
+
+ public static String getName() {
+ return name;
+ }
+ private final CallbackNotifier cb;
+
+ public DirectClientHandler(CallbackNotifier cb) {
+ this.cb = cb;
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) {
+ ctx.pipeline().remove(this);
+ cb.onSuccess(ctx);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
+ cb.onFailure(ctx, throwable);
+ }
+
+ @Override
+ public void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/DirectClientInitializer.java b/example/src/main/java/io/netty/example/socksproxy/DirectClientInitializer.java
new file mode 100644
index 0000000000..678a7d715a
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/DirectClientInitializer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+
+
+public final class DirectClientInitializer extends ChannelInitializer {
+
+ private final CallbackNotifier callbackNotifier;
+
+ public DirectClientInitializer(CallbackNotifier callbackNotifier) {
+ this.callbackNotifier = callbackNotifier;
+ }
+
+ @Override
+ public void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline channelPipeline = socketChannel.pipeline();
+ channelPipeline.addLast(DirectClientHandler.getName(), new DirectClientHandler(callbackNotifier));
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/RelayHandler.java b/example/src/main/java/io/netty/example/socksproxy/RelayHandler.java
new file mode 100644
index 0000000000..8d0aa39200
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/RelayHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundByteHandlerAdapter;
+
+
+public final class RelayHandler extends ChannelInboundByteHandlerAdapter {
+ private static final String name = "RELAY_HANDLER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private final Channel relayChannel;
+
+ public RelayHandler(Channel relayChannel) {
+ this.relayChannel = relayChannel;
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ ctx.flush();
+ }
+
+ @Override
+ public void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
+ ByteBuf out = relayChannel.outboundByteBuffer();
+ out.discardReadBytes();
+ out.writeBytes(in);
+ in.clear();
+ if (relayChannel.isActive()) {
+ relayChannel.flush();
+ }
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ if (relayChannel.isActive()) {
+ SocksServerUtils.closeOnFlush(relayChannel);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServer.java b/example/src/main/java/io/netty/example/socksproxy/SocksServer.java
new file mode 100644
index 0000000000..91f6344cf8
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/SocksServer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.socket.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+
+public final class SocksServer {
+ private final int localPort;
+
+ public SocksServer(int localPort) {
+ this.localPort = localPort;
+ }
+
+ public void run() throws Exception {
+ System.err.println(
+ "Listening on*:" + localPort + "...");
+ ServerBootstrap b = new ServerBootstrap();
+ try {
+ b.group(new NioEventLoopGroup(), new NioEventLoopGroup())
+ .channel(NioServerSocketChannel.class)
+ .localAddress(localPort)
+ .childHandler(new SocksServerInitializer());
+ b.bind().sync().channel().closeFuture().sync();
+ } finally {
+ b.shutdown();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new SocksServer(1080).run();
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java
new file mode 100644
index 0000000000..581558dc00
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundMessageHandlerAdapter;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.codec.socks.SocksCmdRequest;
+import io.netty.codec.socks.SocksCmdResponse;
+import io.netty.codec.socks.SocksMessage;
+
+
+@ChannelHandler.Sharable
+public final class SocksServerConnectHandler extends ChannelInboundMessageHandlerAdapter {
+ private static final String name = "SOCKS_SERVER_CONNECT_HANDLER";
+
+ public static String getName() {
+ return name;
+ }
+
+ private final Bootstrap b;
+
+ public SocksServerConnectHandler() {
+ super(SocksCmdRequest.class);
+ b = new Bootstrap();
+ }
+
+ @Override
+ public void messageReceived(final ChannelHandlerContext ctx, final SocksCmdRequest request) throws Exception {
+ CallbackNotifier cb = new CallbackNotifier() {
+ public void onSuccess(final ChannelHandlerContext outboundCtx) {
+ ctx.channel().write(new SocksCmdResponse(SocksMessage.CmdStatus.SUCCESS, request.getAddressType()))
+ .addListener(new ChannelFutureListener() {
+ public void operationComplete(ChannelFuture channelFuture) throws Exception {
+ ctx.pipeline().remove(SocksServerConnectHandler.getName());
+ outboundCtx.channel().pipeline().addLast(new RelayHandler(ctx.channel()));
+ ctx.channel().pipeline().addLast(new RelayHandler(outboundCtx.channel()));
+ }
+ });
+ }
+
+ public void onFailure(ChannelHandlerContext outboundCtx, Throwable cause) {
+ ctx.channel().write(new SocksCmdResponse(SocksMessage.CmdStatus.FAILURE, request.getAddressType()));
+ SocksServerUtils.closeOnFlush(ctx.channel());
+ }
+ };
+
+ final Channel inboundChannel = ctx.channel();
+ b.group(inboundChannel.eventLoop())
+ .channel(NioSocketChannel.class)
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
+ .option(ChannelOption.SO_KEEPALIVE, true)
+ .handler(new DirectClientInitializer(cb))
+ .remoteAddress(request.getHost(), request.getPort());
+ ChannelFuture f = b.connect();
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ SocksServerUtils.closeOnFlush(ctx.channel());
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java
new file mode 100644
index 0000000000..cc502a8906
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundMessageHandlerAdapter;
+import io.netty.codec.socks.SocksCmdRequestDecoder;
+import io.netty.codec.socks.SocksInitResponse;
+import io.netty.codec.socks.SocksMessage;
+import io.netty.codec.socks.SocksRequest;
+import io.netty.codec.socks.SocksAuthResponse;
+import io.netty.codec.socks.SocksCmdRequest;
+import io.netty.codec.socks.SocksCmdResponse;
+
+
+
+@ChannelHandler.Sharable
+public final class SocksServerHandler extends ChannelInboundMessageHandlerAdapter {
+ private static final String name = "SOCKS_SERVER_HANDLER";
+
+ public static String getName() {
+ return name;
+ }
+
+ public SocksServerHandler() {
+ super(SocksRequest.class);
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, SocksRequest socksRequest) throws Exception {
+ switch (socksRequest.getSocksRequestType()) {
+ case INIT: {
+// auth support example
+// ctx.pipeline().addFirst("socksAuthRequestDecoder",new SocksAuthRequestDecoder());
+// ctx.write(new SocksInitResponse(SocksMessage.AuthScheme.AUTH_PASSWORD));
+ ctx.pipeline().addFirst(SocksCmdRequestDecoder.getName(), new SocksCmdRequestDecoder());
+ ctx.write(new SocksInitResponse(SocksMessage.AuthScheme.NO_AUTH));
+ break;
+ }
+ case AUTH:
+ ctx.pipeline().addFirst(SocksCmdRequestDecoder.getName(), new SocksCmdRequestDecoder());
+ ctx.write(new SocksAuthResponse(SocksMessage.AuthStatus.SUCCESS));
+ break;
+ case CMD:
+ SocksCmdRequest req = (SocksCmdRequest) socksRequest;
+ if (req.getCmdType() == SocksMessage.CmdType.CONNECT) {
+ ctx.pipeline().addLast(SocksServerConnectHandler.getName(), new SocksServerConnectHandler());
+ ctx.pipeline().remove(this);
+ ctx.nextInboundMessageBuffer().add(socksRequest);
+ ctx.fireInboundBufferUpdated();
+ } else {
+ ctx.close();
+ }
+ break;
+ case UNKNOWN:
+ ctx.close();
+ break;
+ }
+ }
+
+ @Override
+ public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable throwable) throws Exception {
+ throwable.printStackTrace();
+ SocksServerUtils.closeOnFlush(ctx.channel());
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerInitializer.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerInitializer.java
new file mode 100644
index 0000000000..10c454c760
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerInitializer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.codec.socks.SocksInitRequestDecoder;
+import io.netty.codec.socks.SocksMessageEncoder;
+
+public final class SocksServerInitializer extends ChannelInitializer {
+ private SocksMessageEncoder socksMessageEncoder = new SocksMessageEncoder();
+ private SocksServerHandler socksServerHandler = new SocksServerHandler();
+
+ public SocksServerInitializer() {
+ super();
+ }
+
+ @Override
+ public void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline channelPipeline = socketChannel.pipeline();
+ channelPipeline.addLast(SocksInitRequestDecoder.getName(), new SocksInitRequestDecoder());
+ channelPipeline.addLast(SocksMessageEncoder.getName(), socksMessageEncoder);
+ channelPipeline.addLast(SocksServerHandler.getName(), socksServerHandler);
+ }
+}
diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerUtils.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerUtils.java
new file mode 100644
index 0000000000..4e0e7b147d
--- /dev/null
+++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.socksproxy;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+
+public final class SocksServerUtils {
+
+ private SocksServerUtils() {
+ //NOOP
+ }
+
+ /**
+ * Closes the specified channel after all queued write requests are flushed.
+ */
+ public static void closeOnFlush(Channel ch) {
+ if (ch.isActive()) {
+ ch.flush().addListener(ChannelFutureListener.CLOSE);
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 187ba69cce..cf9a189627 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,7 @@
buffer
codec
codec-http
+ codec-socks
transport
handler
metrics-yammer
@@ -278,6 +279,7 @@
sun.misc.Unsafe
sun.misc.Cleaner
+ sun.net.util.IPAddressUtil
java.util.zip.Deflater