/*
* Copyright 2006-2010 Daniel Henninger. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package net.sf.kraken.protocols.oscar;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import net.kano.joscar.BinaryTools;
import net.kano.joscar.ByteBlock;
import net.kano.joscar.flap.FlapCommand;
import net.kano.joscar.flap.FlapPacketEvent;
import net.kano.joscar.flapcmd.LoginFlapCmd;
import net.kano.joscar.flapcmd.SnacCommand;
import net.kano.joscar.net.ConnDescriptor;
import net.kano.joscar.ratelim.RateLimitingQueueMgr;
import net.kano.joscar.snac.SnacPacketEvent;
import net.kano.joscar.snac.SnacRequest;
import net.kano.joscar.snac.SnacRequestAdapter;
import net.kano.joscar.snac.SnacRequestListener;
import net.kano.joscar.snac.SnacRequestTimeoutEvent;
import net.kano.joscar.snac.SnacResponseEvent;
import net.kano.joscar.snaccmd.ExtraInfoBlock;
import net.kano.joscar.snaccmd.ExtraInfoData;
import net.kano.joscar.snaccmd.FullUserInfo;
import net.kano.joscar.snaccmd.MiniUserInfo;
import net.kano.joscar.snaccmd.SnacFamilyInfoFactory;
import net.kano.joscar.snaccmd.buddy.BuddyOfflineCmd;
import net.kano.joscar.snaccmd.buddy.BuddyStatusCmd;
import net.kano.joscar.snaccmd.conn.ClientReadyCmd;
import net.kano.joscar.snaccmd.conn.ClientVersionsCmd;
import net.kano.joscar.snaccmd.conn.ExtraInfoAck;
import net.kano.joscar.snaccmd.conn.RateAck;
import net.kano.joscar.snaccmd.conn.RateClassInfo;
import net.kano.joscar.snaccmd.conn.RateInfoCmd;
import net.kano.joscar.snaccmd.conn.RateInfoRequest;
import net.kano.joscar.snaccmd.conn.ServerReadyCmd;
import net.kano.joscar.snaccmd.conn.SnacFamilyInfo;
import net.kano.joscar.snaccmd.conn.WarningNotification;
import net.kano.joscar.snaccmd.error.SnacError;
import net.kano.joscar.snaccmd.icbm.InstantMessage;
import net.kano.joscar.snaccmd.icbm.OldIcbm;
import net.kano.joscar.snaccmd.icbm.RecvImIcbm;
import net.kano.joscar.snaccmd.icbm.TypingCmd;
import net.kano.joscar.snaccmd.icon.IconDataCmd;
import net.kano.joscar.snaccmd.icon.IconRequest;
import net.kano.joscar.snaccmd.icon.UploadIconAck;
import net.kano.joscar.snaccmd.icon.UploadIconCmd;
import net.sf.kraken.avatars.Avatar;
import net.sf.kraken.type.PresenceType;
import net.sf.kraken.type.TransportType;
import net.sf.kraken.util.StringUtils;
import net.sf.kraken.util.chatstate.ChatStateEventSource;
import org.apache.log4j.Logger;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
/**
* Handles incoming FLAP packets.
*
* @author Daniel Henninger
* Heavily inspired by joscardemo from the joscar project.
*/
public abstract class BasicFlapConnection extends AbstractFlapConnection {
static Logger Log = Logger.getLogger(BasicFlapConnection.class);
protected final ByteBlock cookie;
protected boolean sentClientReady = false;
protected int[] snacFamilies = null;
protected Collection<SnacFamilyInfo> snacFamilyInfos;
protected RateLimitingQueueMgr rateMgr = new RateLimitingQueueMgr();
public BasicFlapConnection(ConnDescriptor cd, OSCARSession mainSession, ByteBlock cookie) {
super(cd, mainSession);
this.cookie = cookie;
initBasicFlapConnection();
}
private void initBasicFlapConnection() {
sp.setSnacQueueManager(rateMgr);
}
protected DateFormat dateFormat
= DateFormat.getDateTimeInstance(DateFormat.SHORT,
DateFormat.SHORT);
@Override
protected void handleFlapPacket(FlapPacketEvent e) {
Log.debug("OSCAR flap packet received: "+e);
FlapCommand cmd = e.getFlapCommand();
if (cmd instanceof LoginFlapCmd) {
getFlapProcessor().sendFlap(new LoginFlapCmd(cookie));
}
}
@Override
protected void handleSnacPacket(SnacPacketEvent e) {
Log.debug("OSCAR snac packet received: "+e);
SnacCommand cmd = e.getSnacCommand();
if (cmd instanceof ServerReadyCmd) {
ServerReadyCmd src = (ServerReadyCmd) cmd;
setSnacFamilies(src.getSnacFamilies());
Collection<SnacFamilyInfo> familyInfos = SnacFamilyInfoFactory.getDefaultFamilyInfos(src.getSnacFamilies());
setSnacFamilyInfos(familyInfos);
getMainSession().registerSnacFamilies(this);
request(new ClientVersionsCmd(familyInfos));
request(new RateInfoRequest());
}
else if (cmd instanceof RecvImIcbm) {
RecvImIcbm icbm = (RecvImIcbm) cmd;
String sn = icbm.getSenderInfo().getScreenname();
InstantMessage message = icbm.getMessage();
String msg = StringUtils.convertFromHtml(message.getMessage());
getMainSession().getTransport().sendMessage(
getMainSession().getJID(),
getMainSession().getTransport().convertIDToJID(sn),
msg
);
}
else if (cmd instanceof OldIcbm) {
OldIcbm oicbm = (OldIcbm) cmd;
if (oicbm.getMessageType() == OldIcbm.MTYPE_PLAIN) {
String uin = String.valueOf(oicbm.getSender());
String msg = StringUtils.convertFromHtml(oicbm.getReason());
Log.debug("Got ICBM message "+uin+" with "+msg+"\n"+oicbm);
// InstantMessage message = oicbm.getMessage();
// Log.debug("Got ICBM message "+uin+" with "+message+"\n"+oicbm);
// String msg = StringUtils.unescapeFromXML(OscarTools.stripHtml(message.getMessage()));
getMainSession().getTransport().sendMessage(
getMainSession().getJID(),
getMainSession().getTransport().convertIDToJID(uin),
msg
);
}
}
else if (cmd instanceof WarningNotification) {
WarningNotification wn = (WarningNotification) cmd;
MiniUserInfo warner = wn.getWarner();
if (warner == null) {
getMainSession().getTransport().sendMessage(
getMainSession().getJID(),
getMainSession().getTransport().getJID(),
LocaleUtils.getLocalizedString("gateway.aim.warninganon", "kraken", Arrays.asList(wn.getNewLevel().toString())),
Message.Type.headline
);
}
else {
Log.debug("*** " + warner.getScreenname()
+ " warned you up to " + wn.getNewLevel() + "%");
getMainSession().getTransport().sendMessage(
getMainSession().getJID(),
getMainSession().getTransport().getJID(),
LocaleUtils.getLocalizedString("gateway.aim.warningdirect", "kraken", Arrays.asList(warner.getScreenname(), wn.getNewLevel().toString())),
Message.Type.headline
);
}
}
else if (cmd instanceof ExtraInfoAck) {
ExtraInfoAck eia = (ExtraInfoAck)cmd;
List<ExtraInfoBlock> extraInfo = eia.getExtraInfos();
if (extraInfo != null) {
for (ExtraInfoBlock i : extraInfo) {
ExtraInfoData data = i.getExtraData();
final byte[] pendingAvatar = getMainSession().getSsiHierarchy().getPendingAvatarData();
if (JiveGlobals.getBooleanProperty("plugin.gateway."+getMainSession().getTransport().getType()+".avatars", true) && (data.getFlags() & ExtraInfoData.FLAG_UPLOAD_ICON) != 0 && pendingAvatar != null) {
Log.debug("OSCAR: Server has indicated that it wants our icon.");
request(new UploadIconCmd(ByteBlock.wrap(pendingAvatar)), new SnacRequestAdapter() {
@Override
public void handleResponse(SnacResponseEvent e) {
SnacCommand cmd = e.getSnacCommand();
if (cmd instanceof UploadIconAck && pendingAvatar != null) {
UploadIconAck iconAck = (UploadIconAck) cmd;
if (iconAck.getCode() == UploadIconAck.CODE_DEFAULT || iconAck.getCode() == UploadIconAck.CODE_SUCCESS) {
ExtraInfoBlock iconInfo = iconAck.getIconInfo();
if (iconInfo == null) {
Log.debug("OSCAR: Got icon ack with no iconInfo: " + iconAck);
}
Log.debug("OSCAR: Successfully set icon.");
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(pendingAvatar);
getMainSession().getAvatar().setLegacyIdentifier(org.jivesoftware.util.StringUtils.encodeHex(md.digest()));
}
catch (NoSuchAlgorithmException ee) {
Log.error("No algorithm found for MD5!", ee);
}
}
else if (iconAck.getCode() == UploadIconAck.CODE_BAD_FORMAT) {
Log.debug("OSCAR: Uploaded icon was not in an unaccepted format.");
}
else if (iconAck.getCode() == UploadIconAck.CODE_TOO_LARGE) {
Log.debug("OSCAR: Uploaded icon was too large to be accepted.");
}
else {
Log.debug("OSCAR: Got unknown code from UploadIconAck: " + iconAck.getCode());
}
}
else if (cmd instanceof SnacError) {
Log.debug("Got SnacError while setting icon: " + cmd);
}
// Clear the pending binary data from Krakens memory.
getMainSession().getSsiHierarchy().clearPendingAvatar();
}
});
}
}
}
}
else if (cmd instanceof BuddyStatusCmd) {
BuddyStatusCmd bsc = (BuddyStatusCmd)cmd;
FullUserInfo info = bsc.getUserInfo();
PresenceType pType = PresenceType.available;
String vStatus = "";
if (info.getAwayStatus()) {
pType = PresenceType.away;
}
if ((info.getFlags() & FullUserInfo.MASK_WIRELESS) != 0) {
pType = PresenceType.xa;
vStatus = "Mobile: ";
}
if (getMainSession().getTransport().getType().equals(TransportType.icq) && info.getScreenname().matches("/^\\d+$/")) {
pType = ((OSCARTransport)getMainSession().getTransport()).convertICQStatusToXMPP(info.getIcqStatus());
}
List<ExtraInfoBlock> extraInfo = info.getExtraInfoBlocks();
if (extraInfo != null) {
for (ExtraInfoBlock i : extraInfo) {
ExtraInfoData data = i.getExtraData();
if (i.getType() == ExtraInfoBlock.TYPE_AVAILMSG) {
ByteBlock msgBlock = data.getData();
int len = BinaryTools.getUShort(msgBlock, 0);
if (len >= 0) {
byte[] msgBytes = msgBlock.subBlock(2, len).toByteArray();
String msg;
try {
msg = new String(msgBytes, "UTF-8");
}
catch (UnsupportedEncodingException e1) {
continue;
}
if (msg.length() > 0) {
vStatus = vStatus + msg;
}
}
}
else if (i.getType() == ExtraInfoBlock.TYPE_ICONHASH && JiveGlobals.getBooleanProperty("plugin.gateway."+getMainSession().getTransport().getType()+".avatars", true)) {
try {
OSCARBuddy oscarBuddy = getMainSession().getBuddyManager().getBuddy(getMainSession().getTransport().convertIDToJID(info.getScreenname()));
Avatar curAvatar = oscarBuddy.getAvatar();
if (curAvatar == null || !curAvatar.getLegacyIdentifier().equals(org.jivesoftware.util.StringUtils.encodeHex(i.getExtraData().getData().toByteArray()))) {
IconRequest req = new IconRequest(info.getScreenname(), i.getExtraData());
request(req, new SnacRequestAdapter() {
@Override
public void handleResponse(SnacResponseEvent e) {
SnacCommand cmd = e.getSnacCommand();
if (cmd instanceof IconDataCmd) {
IconDataCmd idc = (IconDataCmd)cmd;
if (idc.getIconData().getLength() > 0 && idc.getIconData().getLength() != 90) {
Log.debug("Got icon data: "+idc);
if (getMainSession().getBuddyManager().isActivated()) {
try {
OSCARBuddy oscarBuddy = getMainSession().getBuddyManager().getBuddy(getMainSession().getTransport().convertIDToJID(idc.getScreenname()));
oscarBuddy.setAvatar(new Avatar(getMainSession().getTransport().convertIDToJID(idc.getScreenname()), org.jivesoftware.util.StringUtils.encodeHex(idc.getIconInfo().getExtraData().getData().toByteArray()), idc.getIconData().toByteArray()));
}
catch (NotFoundException ee) {
// Apparently we don't care about this contact.
}
catch (IllegalArgumentException ee) {
Log.debug("OSCAR: Got null avatar, ignoring.");
}
}
}
}
}
@Override
public void handleTimeout(SnacRequestTimeoutEvent e) {
Log.debug("Time out while waiting for icon data.");
}
});
}
}
catch (NotFoundException ee) {
// Apparently we don't care about this contact.
}
}
}
}
if (getMainSession().getBuddyManager().isActivated()) {
try {
OSCARBuddy oscarBuddy = getMainSession().getBuddyManager().getBuddy(getMainSession().getTransport().convertIDToJID(info.getScreenname()));
oscarBuddy.setPresenceAndStatus(pType, vStatus);
}
catch (NotFoundException ee) {
// Apparently we don't care about this contact.
Log.debug("OSCAR: Received presense notification for contact we don't care about: "+info.getScreenname());
}
}
else {
getMainSession().getBuddyManager().storePendingStatus(getMainSession().getTransport().convertIDToJID(info.getScreenname()), pType, vStatus);
}
}
else if (cmd instanceof BuddyOfflineCmd) {
BuddyOfflineCmd boc = (BuddyOfflineCmd)cmd;
if (getMainSession().getBuddyManager().isActivated()) {
try {
OSCARBuddy oscarBuddy = getMainSession().getBuddyManager().getBuddy(getMainSession().getTransport().convertIDToJID(boc.getScreenname()));
oscarBuddy.setPresence(PresenceType.unavailable);
}
catch (NotFoundException ee) {
// Apparently we don't care about this contact.
}
}
else {
getMainSession().getBuddyManager().storePendingStatus(getMainSession().getTransport().convertIDToJID(boc.getScreenname()), PresenceType.unavailable, null);
}
}
else if (cmd instanceof TypingCmd) {
TypingCmd tc = (TypingCmd) cmd;
String sn = tc.getScreenname();
final ChatStateEventSource chatStateEventSource = getMainSession().getTransport().getChatStateEventSource();
final JID receiver = getMainSession().getJID();
final JID sender = getMainSession().getTransport().convertIDToJID(sn);
if (tc.getTypingState() == TypingCmd.STATE_TYPING) {
chatStateEventSource.isComposing(sender, receiver);
}
else if (tc.getTypingState() == TypingCmd.STATE_PAUSED) {
chatStateEventSource.sendIsPaused(sender, receiver);
}
else if (tc.getTypingState() == TypingCmd.STATE_NO_TEXT) {
chatStateEventSource.isInactive(sender, receiver);
}
}
}
@Override
protected void handleSnacResponse(SnacResponseEvent e) {
Log.debug("OSCAR snac packet response: "+e);
SnacCommand cmd = e.getSnacCommand();
if (cmd instanceof RateInfoCmd) {
RateInfoCmd ric = (RateInfoCmd) cmd;
List <RateClassInfo> rateClasses = ric.getRateClassInfos();
int[] classes = new int[rateClasses.size()];
for (int i = 0; i < rateClasses.size(); i++) {
classes[i] = rateClasses.get(i).getRateClass();
}
request(new RateAck(classes));
}
}
public int[] getSnacFamilies() { return snacFamilies; }
protected void setSnacFamilies(int[] families) {
this.snacFamilies = families.clone();
Arrays.sort(snacFamilies);
}
protected void setSnacFamilyInfos(Collection<SnacFamilyInfo> infos) {
snacFamilyInfos = infos;
}
protected boolean supportsFamily(int family) {
return Arrays.binarySearch(snacFamilies, family) >= 0;
}
protected void clientReady() {
if (!sentClientReady) {
sentClientReady = true;
request(new ClientReadyCmd(snacFamilyInfos));
}
}
protected SnacRequest dispatchRequest(SnacCommand cmd) {
return dispatchRequest(cmd, null);
}
protected SnacRequest dispatchRequest(SnacCommand cmd,
SnacRequestListener listener) {
SnacRequest req = new SnacRequest(cmd, listener);
dispatchRequest(req);
return req;
}
protected void dispatchRequest(SnacRequest req) {
getMainSession().handleRequest(req);
}
@Override
protected SnacRequest request(SnacCommand cmd,
SnacRequestListener listener) {
SnacRequest req = new SnacRequest(cmd, listener);
handleReq(req);
return req;
}
private void handleReq(SnacRequest request) {
int family = request.getCommand().getFamily();
if (snacFamilies == null || supportsFamily(family)) {
// this connection supports this snac, so we'll send it here
sendRequest(request);
}
else {
getMainSession().handleRequest(request);
}
}
}