com.comphenix.protocol.PacketLogging.java Source code

Java tutorial

Introduction

Here is the source code for com.comphenix.protocol.PacketLogging.java

Source

/**
 *  ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
 *  Copyright (C) 2017 Dan Mulloy
 *
 *  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 com.comphenix.protocol;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.apache.commons.io.HexDump;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;

import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.google.common.base.Charsets;

/**
 * Logs packets to a given stream
 * @author dmulloy2
 */
public class PacketLogging implements CommandExecutor, PacketListener {
    public static final String NAME = "packetlog";

    private List<PacketType> sendingTypes = new ArrayList<>();
    private List<PacketType> receivingTypes = new ArrayList<>();

    private ListeningWhitelist sendingWhitelist;
    private ListeningWhitelist receivingWhitelist;

    private Logger fileLogger;
    private LogLocation location = LogLocation.FILE;

    private final ProtocolManager manager;
    private final Plugin plugin;

    PacketLogging(Plugin plugin, ProtocolManager manager) {
        this.plugin = plugin;
        this.manager = manager;
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        PacketType type = null;

        try {
            if (args.length > 2) {
                Protocol protocol;

                try {
                    protocol = Protocol.valueOf(args[0].toUpperCase());
                } catch (IllegalArgumentException ex) {
                    sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
                    return true;
                }

                Sender pSender;

                try {
                    pSender = Sender.valueOf(args[1].toUpperCase());
                } catch (IllegalArgumentException ex) {
                    sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
                    return true;
                }

                try {
                    try { // Try IDs first
                        int id = Integer.parseInt(args[2]);
                        type = PacketType.findCurrent(protocol, pSender, id);
                    } catch (NumberFormatException ex) { // Check packet names
                        String name = args[2];
                        outer: for (PacketType packet : PacketType.values()) {
                            if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
                                if (packet.name().equalsIgnoreCase(name)) {
                                    type = packet;
                                    break outer;
                                }
                                for (String className : packet.getClassNames()) {
                                    if (className.equalsIgnoreCase(name)) {
                                        type = packet;
                                        break outer;
                                    }
                                }
                            }
                        }
                    }
                } catch (IllegalArgumentException ex) { // RIP
                    type = null;
                }

                if (type == null) {
                    sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
                    return true;
                }

                if (args.length > 3) {
                    if (args[3].equalsIgnoreCase("console")) {
                        this.location = LogLocation.CONSOLE;
                    } else {
                        this.location = LogLocation.FILE;
                    }
                }

                if (pSender == Sender.CLIENT) {
                    if (receivingTypes.contains(type)) {
                        receivingTypes.remove(type);
                    } else {
                        receivingTypes.add(type);
                    }
                } else {
                    if (sendingTypes.contains(type)) {
                        sendingTypes.remove(type);
                    } else {
                        sendingTypes.add(type);
                    }
                }

                startLogging();
                sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
                return true;
            }

            sender.sendMessage(
                    ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
            return true;
        } catch (Throwable ex) {
            sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
            return true;
        }
    }

    private void startLogging() {
        manager.removePacketListener(this);

        if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
            return;
        }

        this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
        this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();

        // Setup the file logger if it hasn't been already
        if (location == LogLocation.FILE && fileLogger == null) {
            fileLogger = Logger.getLogger("ProtocolLib-FileLogging");

            for (Handler handler : fileLogger.getHandlers())
                fileLogger.removeHandler(handler);
            fileLogger.setUseParentHandlers(false);

            try {
                File logFile = new File(plugin.getDataFolder(), "log.log");
                FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
                handler.setFormatter(new LogFormatter());
                fileLogger.addHandler(handler);
            } catch (IOException ex) {
                plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
                return;
            }
        }

        manager.addPacketListener(this);
    }

    @Override
    public void onPacketSending(PacketEvent event) {
        log(event);
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        log(event);
    }

    // Here's where the magic happens

    private static String hexDump(byte[] bytes) throws IOException {
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            HexDump.dump(bytes, 0, output, 0);
            return new String(output.toByteArray(), Charsets.UTF_8);
        }
    }

    private void log(PacketEvent event) {
        try {
            byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
            String hexDump = hexDump(bytes);

            if (location == LogLocation.FILE) {
                fileLogger.log(Level.INFO, event.getPacketType() + ":");
                fileLogger.log(Level.INFO, hexDump);
                fileLogger.log(Level.INFO, "");
            } else {
                System.out.println(event.getPacketType() + ":");
                System.out.println(hexDump);
                System.out.println();
            }
        } catch (Throwable ex) {
            plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
            plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");

            sendingTypes.clear();
            receivingTypes.clear();
            startLogging();
        }
    }

    @Override
    public ListeningWhitelist getSendingWhitelist() {
        return sendingWhitelist;
    }

    @Override
    public ListeningWhitelist getReceivingWhitelist() {
        return receivingWhitelist;
    }

    @Override
    public Plugin getPlugin() {
        return plugin;
    }

    private static enum LogLocation {
        CONSOLE, FILE;
    }

    private static class LogFormatter extends Formatter {
        private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        private static final String LINE_SEPARATOR = System.getProperty("line.separator");
        private static final String FORMAT = "[{0}] {1}";

        @Override
        public String format(LogRecord record) {
            String string = formatMessage(record);
            if (string.isEmpty()) {
                return LINE_SEPARATOR;
            }

            StringBuilder message = new StringBuilder();
            message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
            message.append(LINE_SEPARATOR);
            return message.toString();
        }
    }
}