📄 sendimpacket.java
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.qq.packets.out;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;
import edu.tsinghua.lumaqq.qq.PacketParseException;
import edu.tsinghua.lumaqq.qq.QQ;
import edu.tsinghua.lumaqq.qq.Utils;
import edu.tsinghua.lumaqq.qq.packets.OutPacket;
/**
* <pre>
* 发送消息的包,格式为
* 1. 头部
* 2. 发送者QQ号,4个字节
* 3. 接收者的QQ号,4个字节
* 4. 发送者QQ版本,2字节
* 5. 发送者QQ号,4字节
* 6. 接收者QQ号,4个字节(奇怪,为什么要搞两个在里面)
* 7. 发送者QQ号和session key合在一起用md5处理一次的结果,16字节
* 8. 消息类型,2字节
* 9. 会话ID,2字节,如果是一个操作需要发送多个包才能完成,则这个id必须一致
* 10. 发送时间,4字节
* 11. 1字节,为0
* 12. 发送者头像
* 13. 3字节,为0
* 14. 字体信息,1字节,设成0x1吧,不懂具体意思
* 15. 4字节,为0
* 16. 消息方式,是发送的,还是自动回复的,1字节
* 17. 以空格和0x0结束的消息
* 18. 消息的尾部,包含一些消息的参数,比如字体颜色啦,等等等等,顺序是
* 1. 字体修饰属性,bold,italic之类的,1字节,具体的设置是
* i. bit0-bit4用来表示字体大小,所以最大是32
* ii. bit5表示是否bold
* iii. bit6表示是否italic
* iv. bit7表示是否underline
* 2. 颜色Red,1字节
* 3. 颜色Green,1字节
* 4. 颜色Blue,1字节
* 5. 1个未知字节,置0先
* 6. 消息编码,2字节,0x8602为GB,0x0000为EN,其他未知,好像可以自定义,因为服务器好像不干涉
* 7. 可变长度的一段信息,字体名后面跟一个回车符,比如0xcb, 0xce, 0xcc, 0xe5, 0x0d,表示宋体
* 19. 包尾部
*
* 请求传送文件的包,这是这个包的另一种用法,其格式为
* 1 - 15. 1到15部分均与发送消息包相同,只有第8部分不同,对于UDP的请求,8部分是0x0035,对于TCP,是0x0001
* 16 - 18. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
* 19. 一个固定字节 0x65,含义未知
* 20. 连接方式字节,UDP是0, TCP是3
* 21. 4个字节的发送者外部ip地址(也就是可能为代理地址)
* 22. 2个字节的发送者端口
* 23. 2个字节的端口,第一个监听端口,TCP没有这个部分
* 24. 4个字节的地址,真实IP
* 25. 2个字节的端口,第二个而监听端口
* 26. 空格符号做为上述信息的结束,一个字节,0x20
* 27. 分隔符0x1F
* 28. 要传送的文件名
* 29. 分隔符0x1F
* 30. 字节数的字符串形式后跟“ 字节”,比如文件大小3字节的话,就是“3 字节”这个字符串的编码形式
* 31. 尾部
*
* 同意传送文件的包,格式为
* 1 - 25. 除了8部分,其他均与发送消息包相同。对于UDP的情况,8部分是0x0037,TCP是0x0003。
* UDP时,最后的本地ip和端口都是0;TCP时没有22部分
* 26. 尾部
*
* 拒绝接收文件的包,格式为
* 1 - 20. 除了8部分,均与同意传送文件包相同。对于UDP的情况,8部分是0x0039,对于TCP,是0x0005
*
* 通知我的IP信息,格式为
* 1 - 25. 除了8部分,均与请求传送文件包相同。8部分是0x003B
* 26. 尾部
*
* 取消传送文件,格式为
* 1 - 19. 除了8部分,均与请求传送文件包相同。8部分是0x0049
* 20. 尾部
*
* 要求别人主动连接我的包,格式为
* 1 - 19. 除了8部分,均与请求传送文件包相同。8部分是0x003F
* 20. 尾部
* </pre>
*
* @author 马若劼
*/
public class SendIMPacket extends OutPacket {
// 下面为发送普通消息需要设置的变量
private byte red, green, blue;
private String fontName;
private boolean bold, italic, underline;
private byte fontSize;
private byte fontFlag; // 用来表示bold, italic, underline, fontSize的组合结果
private char encoding;
private int receiver;
private String message;
private char messageType;
private byte replyType;
// 下面为发送文件时需要设置的变量
private String fileName;
private String fileSize;
private char directPort;
private char localPort;
private byte[] localIp;
private char sessionId;
private static final byte DELIMIT = 0x1F;
/** 字体属性 */
private static final byte NONE = 0x00;
private static final byte BOLD = 0x20;
private static final byte ITALIC = 0x40;
private static final byte UNDERLINE = (byte)0x80;
/**
* 构造函数
*/
public SendIMPacket() {
super(QQ.QQ_CMD_SEND_IM, true);
encoding = QQ.QQ_IM_ENCODING_GB;
fontName = "宋体";
red = green = blue = 0;
bold = italic = underline = false;
fontSize = 0x9;
fontFlag = 0x9;
message = "";
messageType = QQ.QQ_IM_NORMAL_TEXT;
replyType = QQ.QQ_IM_NORMAL_REPLY;
}
/**
* @param buf
* @param length
* @throws PacketParseException
*/
public SendIMPacket(ByteBuffer buf, int length) throws PacketParseException {
super(buf, length);
}
/* (non-Javadoc)
* @see edu.tsinghua.lumaqq.qq.packets.OutPacket#putBody(java.nio.ByteBuffer)
*/
protected void putBody(ByteBuffer buf) {
// 发送者QQ号
buf.putInt(user.getQQ());
// 接收者QQ号
buf.putInt(receiver);
// 发送者QQ版本
buf.putChar(source);
// 发送者QQ号
buf.putInt(user.getQQ());
// 接收者QQ号
buf.putInt(receiver);
// 文件传输会话密钥
buf.put(user.getFileSessionKey());
// 消息类型
buf.putChar(messageType);
// 顺序号
if(sessionId == 0)
buf.putChar(sequence);
else
buf.putChar(sessionId);
// 发送时间
int time = (int)(System.currentTimeMillis() / 1000);
buf.putInt(time);
// 1字节为0
buf.put((byte)0);
// 发送者头像
byte face = Utils.getByte(user.getContactInfo().infos[user.getContactInfo().face], 0);
buf.put(face);
// 3字节为0
buf.putChar((char)0);
buf.put((byte)0);
// 字体信息,设成1
buf.put((byte)1);
// 4字节为0
buf.putInt(0);
// 判断消息类型
switch(messageType) {
case QQ.QQ_IM_NORMAL_TEXT:
initTextContent(buf);
break;
case QQ.QQ_IM_UDP_REQUEST:
initSendFileContent(buf);
break;
case QQ.QQ_IM_ACCEPT_UDP_REQUEST:
initSendFileAcceptContent(buf);
break;
case QQ.QQ_IM_REJECT_UDP_REQUEST:
case QQ.QQ_IM_REJECT_TCP_REQUEST:
initSendFileRejectContent(buf);
break;
case QQ.QQ_IM_NOTIFY_IP:
initNotifyFilePortUDP(buf);
break;
case QQ.QQ_IM_REQUEST_CANCELED:
initConnectionCanceled(buf);
break;
case QQ.QQ_IM_ARE_YOU_BEHIND_FIREWALL:
initPleaseConnectMe(buf);
break;
}
}
/* (non-Javadoc)
* @see edu.tsinghua.lumaqq.qq.packets.OutPacket#parseBody(java.nio.ByteBuffer)
*/
protected void parseBody(ByteBuffer buf) throws PacketParseException {
// sender
buf.getInt();
// receiver
receiver = buf.getInt();
// source
buf.getChar();
// sender
buf.getInt();
// receiver
buf.getInt();
// file session key
buf.position(buf.position() + QQ.QQ_KEY_LENGTH);
// message type
messageType = buf.getChar();
// session id
sessionId = buf.getChar();
// time
buf.getInt();
// unknown
buf.get();
// face
buf.get();
// unknown 3 bytes
buf.getChar();
buf.get();
// font info
buf.get();
// unknown 4 bytes
buf.getInt();
// 判断消息类型
switch(messageType) {
case QQ.QQ_IM_NORMAL_TEXT:
parseTextContent(buf);
break;
case QQ.QQ_IM_UDP_REQUEST:
parseSendFileContent(buf);
break;
case QQ.QQ_IM_ACCEPT_UDP_REQUEST:
parseSendFileAcceptContent(buf);
break;
case QQ.QQ_IM_REJECT_UDP_REQUEST:
case QQ.QQ_IM_REJECT_TCP_REQUEST:
parseSendFileRejectContent(buf);
break;
case QQ.QQ_IM_NOTIFY_IP:
parseNotifyFilePortUDP(buf);
break;
case QQ.QQ_IM_REQUEST_CANCELED:
parseConnectionCanceled(buf);
break;
case QQ.QQ_IM_ARE_YOU_BEHIND_FIREWALL:
parsePleaseConnectMe(buf);
break;
}
}
/**
* @param buf
*/
private void parsePleaseConnectMe(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
buf.getLong();
buf.getChar();
buf.get();
// 我们先尝试UDP方式
buf.get();
buf.get();
// 四个字节的发送者IP,这是外部IP
byte[] ip = new byte[4];
buf.get(ip);
user.setIp(ip);
// 发送者端口
user.setPort(buf.getChar());
// 监听端口,含义未知,为连接服务器的端口,先随便写一个值
directPort = buf.getChar();
// 后面全0
buf.getInt();
buf.getChar();
}
/**
* @param buf
*/
private void parseConnectionCanceled(ByteBuffer buf) {
}
/**
* @param buf
*/
private void parseNotifyFilePortUDP(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
buf.getLong();
buf.getChar();
buf.get();
// 我们先尝试UDP方式
buf.get();
buf.get();
// 四个字节的发送者IP,这是外部IP
byte[] ip = new byte[4];
buf.get(ip);
user.setIp(ip);
// 发送者端口
user.setPort(buf.getChar());
// 监听端口,含义未知,为连接服务器的端口,先随便写一个值
directPort = buf.getChar();
// 真实IP和第二个端口
localIp = new byte[4];
buf.get(localIp);
localPort = buf.getChar();
}
/**
* @param buf
*/
private void parseSendFileRejectContent(ByteBuffer buf) {
}
/**
* @param buf
*/
private void parseSendFileAcceptContent(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
buf.getLong();
buf.getChar();
buf.get();
// 我们先尝试UDP方式
buf.get();
buf.get();
// 四个字节的发送者IP,这是外部IP
byte[] ip = new byte[4];
buf.get(ip);
user.setIp(ip);
// 发送者端口
user.setPort(buf.getChar());
// 监听端口,含义未知,为连接服务器的端口,先随便写一个值
directPort = buf.getChar();
// 后面全0
buf.getInt();
buf.getChar();
}
/**
* 解析传送文件请求
*
* @param buf
*/
private void parseSendFileContent(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
buf.getLong();
buf.getChar();
buf.get();
// 我们先尝试UDP方式
buf.get();
buf.get();
// 四个字节的发送者IP,这是外部IP
byte[] ip = new byte[4];
buf.get(ip);
user.setIp(ip);
// 发送者端口
user.setPort(buf.getChar());
// 直接端口
directPort = buf.getChar();
buf.getInt();
buf.getChar();
buf.get();
buf.get();
fileName = Utils.getString(buf, DELIMIT);
fileSize = Utils.getString(buf);
}
/**
* 解析文本内容
* @param buf
*/
private void parseTextContent(ByteBuffer buf) {
// 消息方式,是发送的,还是自动回复的,1字节
replyType = buf.get();
message = Utils.getString(buf, 0x0);
fontFlag = buf.get();
red = buf.get();
green = buf.get();
blue = buf.get();
buf.get();
encoding = buf.getChar();
fontName = Utils.getString(buf, 0x0D);
bold = (fontFlag & BOLD) != 0;
italic = (fontFlag & ITALIC) != 0;
underline = (fontFlag & UNDERLINE) != 0;
fontSize = (byte)(fontFlag & 0x1F);
}
/**
* 初始化请求对方主动连接的包
* @param buf
*/
private void initPleaseConnectMe(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
buf.putLong(0);
buf.putChar((char)0);
buf.put((byte)0);
// 我们先尝试UDP方式
buf.put((byte)0x65);
buf.put((byte)0x0);
// 四个字节的发送者IP,这是外部IP
buf.put(user.getIp());
// 发送者端口
buf.putChar((char)user.getPort());
// 监听端口,含义未知,为连接服务器的端口,先随便写一个值
buf.putChar(directPort);
// 后面全0
buf.putInt(0);
buf.putChar((char)0);
}
/**
* 初始化取消发送文件包
* @param buf
*/
private void initConnectionCanceled(ByteBuffer buf) {
// 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -