package openeye.logic;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader;
import openeye.Log;
import openeye.Proxy;
import openeye.logic.ModMetaCollector.ClassSource;
import openeye.protocol.reports.ReportAnalytics;
import openeye.protocol.reports.ReportCrash;
import openeye.protocol.reports.ReportCrash.ExceptionInfo;
import openeye.protocol.reports.ReportCrash.StackTrace;
import openeye.protocol.reports.ReportEnvironment;
import openeye.protocol.reports.ReportFileContents;
import openeye.protocol.reports.ReportFileContents.ArchiveDirEntry;
import openeye.protocol.reports.ReportFileContents.ArchiveEntry;
import openeye.protocol.reports.ReportFileContents.ArchiveFileEntry;
import openeye.protocol.reports.ReportKnownFiles;
public class ReportBuilders {
private static final TagsCollector TAGS_COLLECTOR = new TagsCollector();
private static final Random RANDOM = new Random();
private static String getSide() {
return FMLCommonHandler.instance().getSide().toString().toLowerCase(Locale.ENGLISH);
}
public static ReportKnownFiles buildKnownFilesReport(ModMetaCollector data) {
ReportKnownFiles result = new ReportKnownFiles();
result.signatures = data.getAllFiles();
return result;
}
private static String getJavaVersion() {
String vendor = Strings.nullToEmpty(System.getProperty("java.vendor"));
String version = Strings.nullToEmpty(System.getProperty("java.version"));
return vendor + " " + version;
}
private static Map<String, String> getEnvVersions() {
ImmutableMap.Builder<String, String> versions = ImmutableMap.builder();
versions.put("mcp", Loader.instance().getMCPVersionString());
versions.put("fml", Loader.instance().getFMLVersionString());
versions.put("forge", ForgeVersion.getVersion());
return versions.build();
}
private static void fillEnvInfo(ReportEnvironment report) {
report.branding = FMLCommonHandler.instance().getBrandings(true);
report.runtime = getEnvVersions();
report.minecraft = Loader.instance().getMCVersionString();
report.javaVersion = getJavaVersion();
report.side = getSide();
report.obfuscated = Bootstrap.instance.isRuntimeDeobfuscationEnabled();
Set<String> tags = TAGS_COLLECTOR.getTags();
if (!tags.isEmpty()) report.tags = tags;
}
public static ReportAnalytics buildAnalyticsReport(ModMetaCollector data, Set<String> prevSignatures) {
ReportAnalytics analytics = new ReportAnalytics();
fillEnvInfo(analytics);
String language = Proxy.instance().getLanguage();
analytics.language = Strings.isNullOrEmpty(language)? "invalid" : language;
analytics.locale = Locale.getDefault().toString();
TimeZone tz = Calendar.getInstance().getTimeZone();
analytics.timezone = tz.getID();
analytics.workTime = data.getCollectingDuration() / 1000.0f;
analytics.signatures = data.getAllFiles();
Set<String> currentSignatures = data.getAllSignatures();
analytics.installedSignatures = Sets.difference(currentSignatures, prevSignatures);
analytics.uninstalledSignatures = Sets.difference(prevSignatures, currentSignatures);
return analytics;
}
private static StackTrace createStackTraceElement(StackTraceElement e, ModMetaCollector collector) {
StackTrace el = new StackTrace();
final String clsName = e.getClassName();
el.className = clsName;
el.fileName = e.getFileName();
el.methodName = e.getMethodName();
el.lineNumber = e.getLineNumber();
if (collector != null) {
ClassSource source = collector.identifyClassSource(clsName);
if (source != null) {
el.signatures = source.containingClasses;
if (source.loadedFrom != null) {
el.source = source.loadedFrom;
el.signatures.add(source.loadedFrom);
}
}
}
return el;
}
private static ExceptionInfo createStackTrace(Throwable throwable, StackTraceElement[] prevStacktrace, Set<Throwable> alreadySerialized, ModMetaCollector collector) {
if (alreadySerialized.contains(throwable)) return null; // cyclical reference
ExceptionInfo info = new ExceptionInfo();
info.exceptionCls = throwable.getClass().getName();
info.message = Sanitizers.getSanitizerForThrowable(throwable.getClass()).sanitize(throwable.getMessage());
alreadySerialized.add(throwable);
info.stackTrace = Lists.newArrayList();
StackTraceElement[] stackTrace = throwable.getStackTrace();
int m = stackTrace.length - 1;
int n = prevStacktrace.length - 1;
while (m >= 0 && n >= 0 && stackTrace[m].equals(prevStacktrace[n])) {
m--;
n--;
}
for (int i = 0; i <= m; i++)
info.stackTrace.add(createStackTraceElement(stackTrace[i], collector));
Throwable cause = throwable.getCause();
if (cause != null) info.cause = createStackTrace(cause, stackTrace, alreadySerialized, collector);
return info;
}
public static ReportCrash buildCrashReport(Throwable throwable, String location, ModMetaCollector collector) {
ReportCrash crash = new ReportCrash();
fillEnvInfo(crash);
crash.timestamp = new Date().getTime();
crash.location = location;
Set<Throwable> blacklist = Sets.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
crash.exception = createStackTrace(throwable, new StackTraceElement[0], blacklist, collector);
if (collector != null) crash.states = collector.collectStates();
crash.random = RANDOM.nextInt();
crash.resolved = collector != null;
return crash;
}
public static void fillFileContents(File container, ReportFileContents report) {
try {
ZipFile jarFile = new ZipFile(container);
List<ArchiveDirEntry> dirs = Lists.newArrayList();
List<ArchiveFileEntry> files = Lists.newArrayList();
try {
Enumeration<? extends ZipEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.isDirectory()) {
ArchiveDirEntry resultEntry = new ArchiveDirEntry();
fillCommonFields(zipEntry, resultEntry);
dirs.add(resultEntry);
} else {
ArchiveFileEntry resultEntry = new ArchiveFileEntry();
fillCommonFields(zipEntry, resultEntry);
resultEntry.size = zipEntry.getSize();
resultEntry.crc = Long.toHexString(zipEntry.getCrc());
resultEntry.signature = createSignature(jarFile.getInputStream(zipEntry));
files.add(resultEntry);
}
}
} finally {
jarFile.close();
}
report.dirs = dirs;
report.files = files;
} catch (Exception e) {
Log.warn(e, "Failed to get contents of file %s", container);
}
}
private static void fillCommonFields(ZipEntry zipEntry, ArchiveEntry resultEntry) {
resultEntry.filename = zipEntry.getName();
resultEntry.timestamp = zipEntry.getTime();
resultEntry.comment = zipEntry.getComment();
}
private static String createSignature(InputStream is) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
DigestInputStream dis = new DigestInputStream(is, md);
byte[] buffer = new byte[1024];
while (dis.read(buffer) != -1) {}
dis.close();
byte[] digest = md.digest();
return "sha256:" + bytesToHex(digest);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(2 * bytes.length);
for (byte b : bytes)
sb.append(HEX[(b >> 4) & 0xf]).append(HEX[b & 0xf]);
return sb.toString();
}
private static final char[] HEX = "0123456789abcdef".toCharArray();
}