package org.jerlang.stdlib.beam_lib;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jerlang.stdlib.Lists;
import org.jerlang.type.Atom;
import org.jerlang.type.Binary;
import org.jerlang.type.Integer;
import org.jerlang.type.List;
import org.jerlang.type.Str;
import org.jerlang.type.Term;
import org.jerlang.type.Tuple;
/**
* Implementation of the beam_lib:md5/1 function.
*/
public class BeamLibMD5 {
public static final Atom beam_lib = Atom.of("beam_lib");
public static final Atom enoent = Atom.of("enoent");
public static final Atom eperm = Atom.of("eperm");
public static final Atom error = Atom.of("error");
public static final Atom file = Atom.of("file");
public static final Atom file_error = Atom.of("file_error");
public static final Atom invalid_beam_file = Atom.of("invalid_beam_file");
public static final Atom not_a_beam_file = Atom.of("not_a_beam_file");
public static Term dispatch(List params) {
switch (params.length()) {
case 1:
return md5_1(params.head().toStr());
default:
throw new org.jerlang.erts.erlang.Error("badarg");
}
}
/**
* The following chunks are significant when calculating the BeamLibMD5
* for a module. They are listed in the order that they should be BeamLibMD5:ed.
*/
public static List md5_chunks() {
return List.of(
ChunkId.ATOM.toStr(),
ChunkId.CODE.toStr(),
ChunkId.STRT.toStr(),
ChunkId.IMPT.toStr(),
ChunkId.EXPT.toStr(),
ChunkId.FUNT.toStr(),
ChunkId.LITT.toStr());
}
/**
* Calculates an BeamLibMD5 redundancy check for the code of the module
* (compilation date and other attributes are not included).
*/
public static Term md5_1(Str filename_term) {
File file = new File(filename_term.string());
Term result = new List();
if (!file.exists()) {
return Tuple.of(error, beam_lib, Tuple.of(file_error, filename_term, enoent));
}
if (!file.canRead()) {
return Tuple.of(error, beam_lib, Tuple.of(file_error, filename_term, eperm));
}
try {
byte[] bytes = Files.readAllBytes(file.toPath());
result = do_md5(filename_term, bytes);
} catch (FileNotFoundException fileNotFoundException) {
return Tuple.of(error, beam_lib, Tuple.of(file_error, filename_term, enoent));
} catch (EOFException eofException) {
return Tuple.of(error, beam_lib, Tuple.of(invalid_beam_file, filename_term, Integer.ZERO));
} catch (IOException ioException) {
System.err.println("IOException: " + ioException);
} catch (NoSuchAlgorithmException noSuchAlgorithmException) {
System.err.println("NoSuchAlgorithmException: " + noSuchAlgorithmException);
}
return result;
}
private static Term do_md5(Term filename, byte[] bytes) throws IOException, NoSuchAlgorithmException {
InputStream inputStream = new ByteArrayInputStream(bytes);
Term chunks = get_chunks(filename, inputStream);
MessageDigest md5 = init();
if (chunks instanceof List) {
List chunkList = (List) chunks;
while (chunkList.head() != null) {
Tuple chunk = (Tuple) chunkList.head();
if (is_md5_chunk(chunk)) {
Integer offset = (Integer) chunk.element(2);
Integer size = (Integer) chunk.element(3);
update(md5, bytes, offset.toInt(), size.toInt());
}
chunkList = chunkList.tail();
}
}
return Tuple.of(
Atom.of("ok"),
Tuple.of(
Atom.of("pid"),
new Binary(digest(md5))));
}
private static boolean is_md5_chunk(Tuple chunk) {
String chunk_name = chunk.element(1).toString();
switch (chunk_name) {
case "\"Atom\"":
case "\"Code\"":
case "\"StrT\"":
case "\"ImpT\"":
case "\"ExpT\"":
case "\"FunT\"":
case "\"LitT\"":
return true;
default:
return false;
}
}
private static Term get_chunks(Term filename, InputStream inputStream) throws IOException {
List chunks = List.nil;
DataInputStream dis = new DataInputStream(inputStream);
if (dis.readInt() != 0x464f5231) { // "FOR1"
return Tuple.of(not_a_beam_file, filename);
}
int length = dis.readInt();
if (dis.readInt() != 0x4245414d) { // "BEAM"
return Tuple.of(not_a_beam_file, filename);
}
int offset = 20;
while (offset < length) {
Chunk chunk = new Chunk(ChunkId.of(dis.readInt()), offset, dis.readInt());
chunks = new List(chunk.asTuple(), chunks);
offset += 8 + ((chunk.length() + 3) & ~3);
dis.skipBytes((chunk.length() + 3) & ~3);
}
return Lists.reverse(chunks);
}
private static MessageDigest init() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("MD5");
}
private static void update(MessageDigest md5, byte[] bytes, int offset, int len) {
md5.update(bytes, offset, len);
}
private static byte[] digest(MessageDigest md5) {
return md5.digest();
}
}