/*
* Copyright 2015-2025 the original author or authors.
*
* Licensed 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 sockslib.server.msg;
import sockslib.common.AddressType;
import sockslib.common.SocksCommand;
import sockslib.common.SocksException;
import sockslib.utils.SocksUtil;
import sockslib.utils.StreamUtil;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import static sockslib.utils.StreamUtil.checkEnd;
/**
* The class <code>RequestCommandMessage</code> represents a SOCKS5 command message.
*
* @author Youchao Feng
* @version 1.0
* @date Apr 6, 2015 11:10:12 AM
*/
public class CommandMessage implements ReadableMessage, WritableMessage {
/**
* Value of CONNECT command.
*/
protected static final int CMD_CONNECT = 0x01;
/**
* Value of BIND command.
*/
protected static final int CMD_BIND = 0x02;
/**
* Value of UDP ASSOCIATE command.
*/
protected static final int CMD_UDP_ASSOCIATE = 0x03;
/**
* Value of RESERVED field.
*/
private static final int RESERVED = 0x00;
/**
* Version.
*/
private int version;
/**
* IP address of destination.
*/
private InetAddress inetAddress;
/**
* Port of destination.
*/
private int port;
/**
* Host of destination.
*/
private String host;
/**
* SOCKS command.
*/
private SocksCommand command;
/**
* Reserved field.
*/
private int reserved;
/**
* Address type.
*/
private int addressType;
/**
* SOCKS exception in Command message.
*/
private SocksException socksException;
@Override
public int getLength() {
return getBytes().length;
}
@Override
public byte[] getBytes() {
byte[] bytes = null;
switch (addressType) {
case AddressType.IPV4:
bytes = new byte[10];
byte[] ipv4Bytes = inetAddress.getAddress();// todo
System.arraycopy(ipv4Bytes, 0, bytes, 4, ipv4Bytes.length);
bytes[8] = SocksUtil.getFirstByteFromInt(port);
bytes[9] = SocksUtil.getSecondByteFromInt(port);
break;
case AddressType.IPV6:
bytes = new byte[22];
byte[] ipv6Bytes = inetAddress.getAddress();// todo
System.arraycopy(ipv6Bytes, 0, bytes, 4, ipv6Bytes.length);
bytes[20] = SocksUtil.getFirstByteFromInt(port);
bytes[21] = SocksUtil.getSecondByteFromInt(port);
break;
case AddressType.DOMAIN_NAME:
final int hostLength = host.getBytes().length;
bytes = new byte[7 + hostLength];
bytes[4] = (byte) hostLength;
for (int i = 0; i < hostLength; i++) {
bytes[5 + i] = host.getBytes()[i];
}
bytes[5 + hostLength] = SocksUtil.getFirstByteFromInt(port);
bytes[6 + hostLength] = SocksUtil.getSecondByteFromInt(port);
break;
default:
break;
}
bytes[0] = (byte) version;
bytes[1] = (byte) command.getValue();
bytes[2] = RESERVED;
bytes[3] = (byte) addressType;
return bytes;
}
@Override
public void read(InputStream inputStream) throws SocksException, IOException {
version = checkEnd(inputStream.read());
int cmd = checkEnd(inputStream.read());
switch (cmd) {
case CMD_CONNECT:
command = SocksCommand.CONNECT;
break;
case CMD_BIND:
command = SocksCommand.BIND;
break;
case CMD_UDP_ASSOCIATE:
command = SocksCommand.UDP_ASSOCIATE;
break;
default:
socksException = SocksException.serverReplyException(ServerReply.COMMAND_NOT_SUPPORTED);
}
reserved = checkEnd(inputStream.read());
addressType = checkEnd(inputStream.read());
if (!AddressType.isSupport(addressType) && socksException == null) {
socksException = SocksException.serverReplyException(ServerReply.ADDRESS_TYPE_NOT_SUPPORTED);
}
// read address
switch (addressType) {
case AddressType.IPV4:
byte[] addressBytes = StreamUtil.read(inputStream, 4);
inetAddress = InetAddress.getByAddress(addressBytes);
break;
case AddressType.DOMAIN_NAME:
int domainLength = checkEnd(inputStream.read());
if (domainLength < 1) {
throw new SocksException("Length of domain must great than 0");
}
byte[] domainBytes = StreamUtil.read(inputStream, domainLength);
host = new String(domainBytes, Charset.forName("UTF-8"));
try {
inetAddress = InetAddress.getByName(host);
} catch (UnknownHostException e) {
if (socksException == null) {
socksException = SocksException.serverReplyException(ServerReply.HOST_UNREACHABLE);
}
}
break;
default:
// TODO Implement later.
break;
}
// Read port
byte[] portBytes = StreamUtil.read(inputStream, 2);
port = SocksUtil.bytesToInt(portBytes);
}
/**
* Returns version.
*
* @return Version.
*/
public int getVersion() {
return version;
}
/**
* Sets version.
*
* @param version Version.
*/
public void setVersion(int version) {
this.version = version;
}
public boolean hasSocksException() {
return socksException != null;
}
public InetAddress getInetAddress() {
return inetAddress;
}
public void setInetAddress(InetAddress inetAddress) {
this.inetAddress = inetAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public SocksCommand getCommand() {
return command;
}
public void setCommand(SocksCommand command) {
this.command = command;
}
public int getReserved() {
return reserved;
}
public void setReserved(int reserved) {
this.reserved = reserved;
}
public int getAddressType() {
return addressType;
}
public void setAddressType(int addressType) {
this.addressType = addressType;
}
public SocketAddress getSocketAddress() {
return new InetSocketAddress(inetAddress, port);
}
public SocksException getSocksException() {
return socksException;
}
public void setSocksException(SocksException socksException) {
this.socksException = socksException;
}
}