/*
* Copyrigth (C) 2010 Henrik Baastrup.
*
* Licensed under the GNU Lesser General Public License version 3;
* you may not use this file except in compliance with the License.
* You should have received a copy of the license together with this
* file but can obtain a copy of the License at:
*
* http://www.gnu.org/licenses/lgpl-3.0.txt
*
* 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 javax.net.stun;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Used internaly by the @{link StunClient}
*
* @author Henrik Baastrup
*/
public class MessageAttribute {
public enum MessageAttributeType {
MAPPED_ADDRESS,
RESPONSE_ADDRESS,
CHANGE_REQUEST,
SOURCE_ADDRESS,
CHANGED_ADDRESS,
USERNAME,
PASSWORD,
MESSAGE_INTEGRITY,
ERROR_CODE,
UNKNOWN_ATTRIBUTES,
REFLECTED_FROM
};
public static final int MAPPED_ADDRESS = 0x0001;
public static final int RESPONSE_ADDRESS = 0x0002;
public static final int CHANGE_REQUEST = 0x0003;
public static final int SOURCE_ADDRESS = 0x0004;
public static final int CHANGED_ADDRESS = 0x0005;
public static final int USERNAME = 0x0006;
public static final int PASSWORD = 0x0007;
public static final int MESSAGE_INTEGRITY = 0x0008;
public static final int ERROR_CODE = 0x0009;
public static final int UNKNOWN_ATTRIBUTES = 0x000a;
public static final int REFLECTED_FROM = 0x000b;
private MessageAttributeType type = MessageAttributeType.UNKNOWN_ATTRIBUTES;
private byte value[];
public MessageAttribute(final MessageAttributeType type, final byte value[]) {
this.type = type;
if (value==null) this.value = new byte[0];
else {
setValue(value);
}
}
public MessageAttribute(final int type, final byte value[]) {
setType(type);
if (value==null) this.value = new byte[0];
else {
setValue(value);
}
}
public MessageAttribute(MessageAttribute attr) {
this(attr.type, attr.value);
}
public MessageAttributeType getType() {return type;}
public byte[] getValue() {
if (value==null) return null;
byte retArr[] = new byte[value.length];
for (int i=0; i<value.length; i++) retArr[i] = value[i];
return retArr;
}
public void setValue(byte arg0[]) {
this.value = new byte[arg0.length];
for (int i=0; i<arg0.length; i++) this.value[i] = arg0[i];
}
public String getValueAsString() { return new String(value);}
public int getValueLength() {return value.length;}
public InetAddress getAddress() {
if (type==MessageAttributeType.MAPPED_ADDRESS || type==MessageAttributeType.CHANGED_ADDRESS) {
if (value.length<8) return null;
byte addrBuf[] = new byte[4];
for (int i=0; i<4; i++) addrBuf[i] = value[i+4];
InetAddress addr = null;
try {
addr = InetAddress.getByAddress(addrBuf);
} catch (UnknownHostException ex) {
return null;
}
return addr;
}
return null;
}
public String getAddressAsString() {
if (type==MessageAttributeType.MAPPED_ADDRESS || type==MessageAttributeType.CHANGED_ADDRESS) {
if (value.length<8) return null;
byte addrBuf[] = new byte[4];
for (int i=0; i<4; i++) addrBuf[i] = value[i+4];
String retStr = String.valueOf((0x0FF & ((int)addrBuf[0])))+"."+String.valueOf((0x0FF & ((int)addrBuf[1])))+"."+String.valueOf((0x0FF & ((int)addrBuf[2])))+"."+String.valueOf((0x0FF & ((int)addrBuf[3])));
return retStr;
}
return null;
}
public int getPort() {
if (type==MessageAttributeType.MAPPED_ADDRESS || type==MessageAttributeType.CHANGED_ADDRESS) {
if (value.length < 4) return 0;
int p = (0x000000FF & ((int)value[2])) << 8;
p += (0x000000FF & ((int)value[3]));
return p;
}
return 0;
}
public String getUsername() {
if (type==MessageAttributeType.USERNAME) {
if (value.length<1) return null;
String str = null;
try {
str = new String(value, "UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
return str;
}
return null;
}
public byte[] getPassword() {
if (type==MessageAttributeType.PASSWORD) {
if (value.length<1) return null;
return getValue();
}
return null;
}
public byte[] getHMAC() {
if (type==MessageAttributeType.MESSAGE_INTEGRITY) {
return getValue();
}
return null;
}
public void setHMAC(byte arg0[]) {
if (type==MessageAttributeType.MESSAGE_INTEGRITY) {
value = new byte[arg0.length];
for (int i=0; i<arg0.length; i++) value[i] = arg0[i];
}
}
public boolean isNothingChanged() {
if (type==MessageAttributeType.CHANGE_REQUEST) {
if (value.length<4) return false;
if (value[3]==0) return true;
}
return true;
}
public boolean isPortChanged() {
if (type==MessageAttributeType.CHANGE_REQUEST) {
if (value.length<4) return false;
if ((value[3] & 2)==2) return true;
}
return false;
}
public boolean isAddressChanged() {
if (type==MessageAttributeType.CHANGE_REQUEST) {
if (value.length<4) return false;
if ((value[3] & 4)==4) return true;
}
return false;
}
public final void setType(final int arg0) {
switch (arg0) {
case MAPPED_ADDRESS:
type = MessageAttributeType.MAPPED_ADDRESS;
break;
case RESPONSE_ADDRESS:
type = MessageAttributeType.RESPONSE_ADDRESS;
break;
case CHANGE_REQUEST:
type = MessageAttributeType.CHANGE_REQUEST;
break;
case SOURCE_ADDRESS:
type = MessageAttributeType.SOURCE_ADDRESS;
break;
case CHANGED_ADDRESS:
type = MessageAttributeType.CHANGED_ADDRESS;
break;
case USERNAME:
type = MessageAttributeType.USERNAME;
break;
case PASSWORD:
type = MessageAttributeType.PASSWORD;
break;
case MESSAGE_INTEGRITY:
type = MessageAttributeType.MESSAGE_INTEGRITY;
break;
case ERROR_CODE:
type = MessageAttributeType.ERROR_CODE;
break;
case UNKNOWN_ATTRIBUTES:
type = MessageAttributeType.UNKNOWN_ATTRIBUTES;
break;
case REFLECTED_FROM:
type = MessageAttributeType.REFLECTED_FROM;
break;
}
}
public int getTypeAsInt() {
switch (type) {
case MAPPED_ADDRESS: return MAPPED_ADDRESS;
case RESPONSE_ADDRESS: return RESPONSE_ADDRESS;
case CHANGE_REQUEST: return CHANGE_REQUEST;
case SOURCE_ADDRESS: return SOURCE_ADDRESS;
case CHANGED_ADDRESS: return CHANGED_ADDRESS;
case USERNAME: return USERNAME;
case PASSWORD: return PASSWORD;
case MESSAGE_INTEGRITY: return MESSAGE_INTEGRITY;
case ERROR_CODE: return ERROR_CODE;
case UNKNOWN_ATTRIBUTES: return UNKNOWN_ATTRIBUTES;
case REFLECTED_FROM: return REFLECTED_FROM;
}
return 0;
}
public byte[] toBytes() {
byte retBytes[] = new byte[value.length+4];
int intType = getTypeAsInt();
retBytes[0] = (byte) ((0xff00 & intType) >> 8);
retBytes[1] = (byte) (0x00ff & intType);
retBytes[2] = (byte) ((0xff00 & value.length) >> 8);
retBytes[3] = (byte) (0x00ff & value.length);
for (int i=0; i<value.length; i++) retBytes[i+4] = value[i];
return retBytes;
}
public static MessageAttribute create(MessageAttributeType type, int arg0) {
byte value[];
switch (type) {
case CHANGE_REQUEST:
value = new byte[4];
value[0] = 0;
value[1] = 0;
value[2] = 0;
value[3] = (byte)(arg0 & 0x06);
break;
default:
return null;
}
return new MessageAttribute(type, value);
}
public static MessageAttribute create(MessageAttributeType type, Object arg0, int arg1) {
byte value[];
switch (type) {
case ERROR_CODE:
StringBuilder errMesg = new StringBuilder((String)arg0);
for (int i=0; i<(errMesg.length()%4); i++) errMesg.append(' ');
byte errMesgBytes[] = errMesg.toString().getBytes();
value = new byte[errMesgBytes.length+4];
value[0] = 0;
value[1] = 0;
value[2] = (byte)(arg1/100);
value[3] = (byte)(arg1%100);
for (int i=0; i<errMesgBytes.length; i++) value[i+4] = errMesgBytes[i];
break;
case MAPPED_ADDRESS:
case RESPONSE_ADDRESS:
case CHANGED_ADDRESS:
case SOURCE_ADDRESS:
case REFLECTED_FROM:
value = new byte[8];
value[0] = 0; //Empty
value[1] = 0x01; //Family
value[2] = (byte) ((arg1 & 0xff00) >> 8);
value[3] = (byte) (arg1 & 0x00ff);
byte clientIp[] = ((InetAddress)arg0).getAddress();
for (int i=0; i<4; i++) value[4+i] = clientIp[i];
break;
case CHANGE_REQUEST:
return create(type, arg1);
case USERNAME:
StringBuilder user = new StringBuilder((String)arg0);
for (int i=0; i<(user.length()%4); i++) user.append(' ');
value = user.toString().getBytes();
break;
case PASSWORD:
byte password[] = (byte[])arg0;
value = new byte[password.length + password.length%4];
for (int i=0; i<password.length; i++) value[i] = password[i];
for (int i=password.length; i<(password.length + password.length%4); i++) value[i] = 0;
break;
default:
return null;
}
return new MessageAttribute(type, value);
}
public static MessageAttribute create(MessageAttributeType type, Object arg0, Object arg1) {
byte value[];
switch (type) {
case MESSAGE_INTEGRITY:
byte password[] = (byte[])arg0;
byte body[] = (byte[])arg1;
// We are going to patch the header length as we are going to
// add the Integrity attribute later on
int len = (0x000000FF & ((int)body[2])) << 8;
len += (0x000000FF & ((int)body[3]));
len += 24; //Length of Integrity attribute
body[2] = (byte) ((len & 0xff00) >> 8);
body[3] = (byte) (len & 0xff);
value = Utils.hmac(password, body);
break;
default:
return null;
}
return new MessageAttribute(type, value);
}
}