Task 167: .EC File Format

Task 167: .EC File Format

1. List of all the properties of this file format intrinsic to its file system

The .EC file format is the execution data format used by JaCoCo (Java Code Coverage tool), often named coverage.ec in Android contexts (equivalent to .exec in other contexts). It is a binary format for storing code coverage data, consisting of a header and a sequence of blocks for session information and execution data. The properties (fields and structures) are as follows:

Header:

  • Block type: byte (value 0x01)
  • Magic number: char (value 0xC0C0, bytes 0xC0 0xC0)
  • Format version: char (value 0x1007, bytes 0x10 0x07)

Session Info Block (zero or more):

  • Block type: byte (value 0x10)
  • ID: UTF string (2-byte length + UTF-8 bytes)
  • Start timestamp: long (8 bytes, big-endian)
  • Dump timestamp: long (8 bytes, big-endian)

Execution Data Block (zero or more):

  • Block type: byte (value 0x11)
  • Class ID: long (8 bytes, big-endian)
  • Class name: UTF string (2-byte length + UTF-8 bytes)
  • Probes: boolean array (varint length + packed bytes; varint is variable-length int with 7 bits per byte, continuation bit in MSB; packed bytes where each byte holds 8 booleans, LSB to MSB)

The file is a stream of these blocks after the header. All multi-byte values are big-endian.

I was unable to find public direct download links for .EC files during my search. However, the format is identical to JaCoCo's .exec files. Here are two sample .exec files from public GitHub repositories (raw links):

3. Ghost blog embedded HTML JavaScript for drag n drop .EC file to dump properties

.EC File Parser
Drag and drop .EC file here

4. Python class for .EC file

import struct
import io

class ECFile:
    def __init__(self, filepath=None):
        self.header = None
        self.sessions = []
        self.executions = []
        if filepath:
            self.read(filepath)

    def read_varint(self, f):
        length = 0
        shift = 0
        while True:
            byte = ord(f.read(1))
            length |= (byte & 0x7F) << shift
            if (byte & 0x80) == 0:
                break
            shift += 7
        return length

    def read_utf(self, f):
        length = struct.unpack('>H', f.read(2))[0]
        return f.read(length).decode('utf-8')

    def read(self, filepath):
        with open(filepath, 'rb') as f:
            data = f.read()
        f = io.BytesIO(data)
        block_type = ord(f.read(1))
        magic = struct.unpack('>H', f.read(2))[0]
        version = struct.unpack('>H', f.read(2))[0]
        self.header = (block_type, magic, version)

        while True:
            if f.tell() >= len(data):
                break
            block = ord(f.read(1))
            if block == 0x10:
                id = self.read_utf(f)
                start = struct.unpack('>Q', f.read(8))[0]
                dump = struct.unpack('>Q', f.read(8))[0]
                self.sessions.append((id, start, dump))
            elif block == 0x11:
                class_id = struct.unpack('>Q', f.read(8))[0]
                name = self.read_utf(f)
                length = self.read_varint(f)
                probes = []
                for _ in range((length + 7) // 8):
                    byte = ord(f.read(1))
                    for j in range(8):
                        if len(probes) < length:
                            probes.append(bool(byte & (1 << j)))
                self.executions.append((class_id, name, probes))
            else:
                break

    def print_properties(self):
        print("Header:")
        print(f"  Block Type: 0x{self.header[0]:02x}")
        print(f"  Magic: 0x{self.header[1]:04x}")
        print(f"  Version: 0x{self.header[2]:04x}")
        for i, session in enumerate(self.sessions):
            print(f"Session Info {i}:")
            print(f"  ID: {session[0]}")
            print(f"  Start: {session[1]}")
            print(f"  Dump: {session[2]}")
        for i, execution in enumerate(self.executions):
            print(f"Execution Data {i}:")
            print(f"  Class ID: {execution[0]}")
            print(f"  Class Name: {execution[1]}")
            print(f"  Probes: {execution[2]}")

    def write(self, filepath):
        with open(filepath, 'wb') as f:
            f.write(struct.pack('>BHH', self.header[0], self.header[1], self.header[2]))
            for session in self.sessions:
                f.write(b'\x10')
                id_bytes = session[0].encode('utf-8')
                f.write(struct.pack('>H', len(id_bytes)))
                f.write(id_bytes)
                f.write(struct.pack('>QQ', session[1], session[2]))
            for execution in self.executions:
                f.write(b'\x11')
                f.write(struct.pack('>Q', execution[0]))
                name_bytes = execution[1].encode('utf-8')
                f.write(struct.pack('>H', len(name_bytes)))
                f.write(name_bytes)
                length = len(execution[2])
                # Write varint
                while True:
                    byte = length & 0x7F
                    length >>= 7
                    if length:
                        byte |= 0x80
                    f.write(bytes([byte]))
                    if not length:
                        break
                # Write packed bytes
                for i in range(0, len(execution[2]), 8):
                    byte = 0
                    for j in range(8):
                        if i + j < len(execution[2]) and execution[2][i + j]:
                            byte |= (1 << j)
                    f.write(bytes([byte]))

Example usage:

ec = ECFile("coverage.ec")
ec.print_properties()
ec.write("new_coverage.ec")

5. Java class for .EC file

import java.io.*;
import java.math.BigInteger;

public class ECFile {
    private byte blockType;
    private char magic;
    private char version;
    private final java.util.List<SessionInfo> sessions = new java.util.ArrayList<>();
    private final java.util.List<ExecutionData> executions = new java.util.ArrayList<>();

    static class SessionInfo {
        String id;
        long start;
        long dump;
    }

    static class ExecutionData {
        long classId;
        String name;
        boolean[] probes;
    }

    public ECFile(String filepath) throws IOException {
        read(filepath);
    }

    private int readVarInt(DataInputStream in) throws IOException {
        int value = 0;
        int shift = 0;
        while (true) {
            int byteValue = in.readByte() & 0xFF;
            value |= (byteValue & 0x7F) << shift;
            if ((byteValue & 0x80) == 0) break;
            shift += 7;
        }
        return value;
    }

    public void read(String filepath) throws IOException {
        try (DataInputStream in = new DataInputStream(new FileInputStream(filepath))) {
            blockType = in.readByte();
            magic = in.readChar();
            version = in.readChar();

            while (true) {
                try {
                    byte block = in.readByte();
                    if (block == 0x10) {
                        SessionInfo session = new SessionInfo();
                        session.id = in.readUTF();
                        session.start = in.readLong();
                        session.dump = in.readLong();
                        sessions.add(session);
                    } else if (block == 0x11) {
                        ExecutionData exec = new ExecutionData();
                        exec.classId = in.readLong();
                        exec.name = in.readUTF();
                        int length = readVarInt(in);
                        exec.probes = new boolean[length];
                        for (int i = 0; i < length; i += 8) {
                            byte byteValue = in.readByte();
                            for (int j = 0; j  < 8 && (i + j) < length; j++) {
                                exec.probes[i + j] = (byteValue & (1 << j)) != 0;
                            }
                        }
                        executions.add(exec);
                    } else {
                        break;
                    }
                } catch (EOFException e) {
                    break;
                }
            }
        }
    }

    public void printProperties() {
        System.out.println("Header:");
        System.out.println("  Block Type: 0x" + Integer.toHexString(blockType & 0xFF));
        System.out.println("  Magic: 0x" + Integer.toHexString(magic));
        System.out.println("  Version: 0x" + Integer.toHexString(version));
        for (int i = 0; i < sessions.size(); i++) {
            SessionInfo session = sessions.get(i);
            System.out.println("Session Info " + i + ":");
            System.out.println("  ID: " + session.id);
            System.out.println("  Start: " + session.start);
            System.out.println("  Dump: " + session.dump);
        }
        for (int i = 0; i < executions.size(); i++) {
            ExecutionData exec = executions.get(i);
            System.out.println("Execution Data " + i + ":");
            System.out.println("  Class ID: " + exec.classId);
            System.out.println("  Class Name: " + exec.name);
            System.out.print("  Probes: ");
            System.out.println(java.util.Arrays.toString(exec.probes));
        }
    }

    public void write(String filepath) throws IOException {
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(filepath))) {
            out.writeByte(blockType);
            out.writeChar(magic);
            out.writeChar(version);
            for (SessionInfo session : sessions) {
                out.writeByte(0x10);
                out.writeUTF(session.id);
                out.writeLong(session.start);
                out.writeLong(session.dump);
            }
            for (ExecutionData exec : executions) {
                out.writeByte(0x11);
                out.writeLong(exec.classId);
                out.writeUTF(exec.name);
                // Write varint
                int length = exec.probes.length;
                while (true) {
                    int byteValue = length & 0x7F;
                    length >>= 7;
                    if (length != 0) byteValue |= 0x80;
                    out.writeByte(byteValue);
                    if (length == 0) break;
                }
                // Write packed bytes
                for (int i = 0; i < exec.probes.length; i += 8) {
                    byte byteValue = 0;
                    for (int j = 0; j < 8 && (i + j) < exec.probes.length; j++) {
                        if (exec.probes[i + j]) {
                            byteValue |= (1 << j);
                        }
                    }
                    out.writeByte(byteValue);
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ECFile ec = new ECFile("coverage.ec");
        ec.printProperties();
        ec.write("new_coverage.ec");
    }
}

6. JavaScript class for .EC file

class ECFile {
  constructor(filepath = null) {
    this.header = null;
    this.sessions = [];
    this.executions = [];
    if (filepath) {
      // For node.js, use fs to read
      const fs = require('fs');
      const buffer = fs.readFileSync(filepath);
      this.read(buffer);
    }
  }

  readVarInt(dataView, offsetRef) {
    let value = 0;
    let shift = 0;
    while (true) {
      const byte = dataView.getUint8(offsetRef.value++);
      value |= (byte & 0x7F) << shift;
      if ((byte & 0x80) === 0) break;
      shift += 7;
    }
    return value;
  }

  readUTF(dataView, offsetRef) {
    const length = dataView.getUint16(offsetRef.value, false);
    offsetRef.value += 2;
    const bytes = new Uint8Array(dataView.buffer, offsetRef.value, length);
    offsetRef.value += length;
    return new TextDecoder().decode(bytes);
  }

  read(buffer) {
    const dataView = new DataView(buffer.buffer || buffer);
    let offset = 0;

    const blockType = dataView.getUint8(offset++);
    const magic = dataView.getUint16(offset, false);
    offset += 2;
    const version = dataView.getUint16(offset, false);
    offset += 2;
    this.header = { blockType, magic, version };

    while (offset < dataView.byteLength) {
      const block = dataView.getUint8(offset++);
      if (block === 0x10) {
        const id = this.readUTF(dataView, { value: offset });
        offset = { value: offset }.value;
        const start = Number(dataView.getBigInt64(offset, false));
        offset += 8;
        const dump = Number(dataView.getBigInt64(offset, false));
        offset += 8;
        this.sessions.push({ id, start, dump });
      } else if (block === 0x11) {
        const classId = Number(dataView.getBigInt64(offset, false));
        offset += 8;
        const name = this.readUTF(dataView, { value: offset });
        offset = { value: offset }.value;
        const length = this.readVarInt(dataView, { value: offset });
        offset = { value: offset }.value;
        const probes = [];
        for (let i = 0; i < length; i += 8) {
          const byte = dataView.getUint8(offset++);
          for (let j = 0; j < 8 && (i + j) < length; j++) {
            probes.push(!!(byte & (1 << j)));
          }
        }
        this.executions.push({ classId, name, probes });
      } else {
        break;
      }
    }
  }

  printProperties() {
    console.log("Header:");
    console.log(`  Block Type: 0x${this.header.blockType.toString(16)}`);
    console.log(`  Magic: 0x${this.header.magic.toString(16)}`);
    console.log(`  Version: 0x${this.header.version.toString(16)}`);
    this.sessions.forEach((session, i) => {
      console.log(`Session Info ${i}:`);
      console.log(`  ID: ${session.id}`);
      console.log(`  Start: ${session.start}`);
      console.log(`  Dump: ${session.dump}`);
    });
    this.executions.forEach((execution, i) => {
      console.log(`Execution Data ${i}:`);
      console.log(`  Class ID: ${execution.classId}`);
      console.log(`  Class Name: ${execution.name}`);
      console.log(`  Probes: ${execution.probes}`);
    });
  }

  write() {
    const buffer = new ArrayBuffer(1024 * 1024); // Arbitrary size
    const dataView = new DataView(buffer);
    let offset = 0;

    dataView.setUint8(offset++, this.header.blockType);
    dataView.setUint16(offset, this.header.magic, false);
    offset += 2;
    dataView.setUint16(offset, this.header.version, false);
    offset += 2;

    this.sessions.forEach(session => {
      dataView.setUint8(offset++, 0x10);
      const idBytes = new TextEncoder().encode(session.id);
      dataView.setUint16(offset, idBytes.length, false);
      offset += 2;
      idBytes.forEach((b, i) => dataView.setUint8(offset + i, b));
      offset += idBytes.length;
      dataView.setBigInt64(offset, BigInt(session.start), false);
      offset += 8;
      dataView.setBigInt64(offset, BigInt(session.dump), false);
      offset += 8;
    });

    this.executions.forEach(execution => {
      dataView.setUint8(offset++, 0x11);
      dataView.setBigInt64(offset, BigInt(execution.classId), false);
      offset += 8;
      const nameBytes = new TextEncoder().encode(execution.name);
      dataView.setUint16(offset, nameBytes.length, false);
      offset += 2;
      nameBytes.forEach((b, i) => dataView.setUint8(offset + i, b));
      offset += nameBytes.length;
      let length = execution.probes.length;
      while (true) {
        let byte = length & 0x7F;
        length >>= 7;
        if (length) byte |= 0x80;
        dataView.setUint8(offset++, byte);
        if (!length) break;
      }
      for (let i = 0; i < execution.probes.length; i += 8) {
        let byte = 0;
        for (let j = 0; j < 8 && (i + j) < execution.probes.length; j++) {
          if (execution.probes[i + j]) byte |= (1 << j);
        }
        dataView.setUint8(offset++, byte);
      }
    });

    return new Uint8Array(buffer, 0, offset);
  }
}

// Example usage in Node.js
const ec = new ECFile('coverage.ec');
ec.printProperties();
const newBuffer = ec.write();
const fs = require('fs');
fs.writeFileSync('new_coverage.ec', newBuffer);

7. C class for .EC file

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h>

typedef struct {
  uint8_t blockType;
  uint16_t magic;
  uint16_t version;
} Header;

typedef struct {
  char* id;
  uint64_t start;
  uint64_t dump;
} SessionInfo;

typedef struct {
  uint64_t classId;
  char* name;
  uint8_t* probesPacked;
  uint32_t probesLength;
} ExecutionData;

typedef struct {
  Header header;
  SessionInfo* sessions;
  size_t sessionCount;
  ExecutionData* executions;
  size_t executionCount;
} ECFile;

uint32_t readVarInt(FILE* f) {
  uint32_t value = 0;
  int shift = 0;
  while (1) {
    uint8_t byte;
    fread(&byte, 1, 1, f);
    value |= (byte & 0x7F) << shift;
    if ((byte & 0x80) == 0) break;
    shift += 7;
  }
  return value;
}

char* readUTF(FILE* f) {
  uint16_t length;
  fread(&length, 2, 1, f);
  length = be16toh(length);
  char* str = malloc(length + 1);
  fread(str, 1, length, f);
  str[length] = '\0';
  return str;
}

void readECFile(ECFile* ec, const char* filepath) {
  FILE* f = fopen(filepath, "rb");
  if (!f) return;

  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  fseek(f, 0, SEEK_SET);

  fread(&ec->header.blockType, 1, 1, f);
  fread(&ec->header.magic, 2, 1, f);
  ec->header.magic = be16toh(ec->header.magic);
  fread(&ec->header.version, 2, 1, f);
  ec->header.version = be16toh(ec->header.version);

  ec->sessionCount = 0;
  ec->executionCount = 0;
  ec->sessions = NULL;
  ec->executions = NULL;

  while (ftell(f) < size) {
    uint8_t block;
    fread(&block, 1, 1, f);
    if (block == 0x10) {
      ec->sessions = realloc(ec->sessions, sizeof(SessionInfo) * (ec->sessionCount + 1));
      SessionInfo* session = &ec->sessions[ec->sessionCount++];
      session->id = readUTF(f);
      fread(&session->start, 8, 1, f);
      session->start = be64toh(session->start);
      fread(&session->dump, 8, 1, f);
      session->dump = be64toh(session->dump);
    } else if (block == 0x11) {
      ec->executions = realloc(ec->executions, sizeof(ExecutionData) * (ec->executionCount + 1));
      ExecutionData* exec = &ec->executions[ec->executionCount++];
      fread(&exec->classId, 8, 1, f);
      exec->classId = be64toh(exec->classId);
      exec->name = readUTF(f);
      exec->probesLength = readVarInt(f);
      size_t byteLength = (exec->probesLength + 7) / 8;
      exec->probesPacked = malloc(byteLength);
      fread(exec->probesPacked, 1, byteLength, f);
    } else {
      break;
    }
  }

  fclose(f);
}

void printProperties(const ECFile* ec) {
  printf("Header:\n");
  printf("  Block Type: 0x%02x\n", ec->header.blockType);
  printf("  Magic: 0x%04x\n", ec->header.magic);
  printf("  Version: 0x%04x\n", ec->header.version);
  for (size_t i = 0; i < ec->sessionCount; i++) {
    const SessionInfo* session = &ec->sessions[i];
    printf("Session Info %zu:\n", i);
    printf("  ID: %s\n", session->id);
    printf("  Start: %lu\n", session->start);
    printf("  Dump: %lu\n", session->dump);
  }
  for (size_t i = 0; i < ec->executionCount; i++) {
    const ExecutionData* exec = &ec->executions[i];
    printf("Execution Data %zu:\n", i);
    printf("  Class ID: %lu\n", exec->classId);
    printf("  Class Name: %s\n", exec->name);
    printf("  Probes (length %u): ", exec->probesLength);
    for (uint32_t j = 0; j < exec->probesLength; j++) {
      uint8_t byte = exec->probesPacked[j / 8];
      printf("%d ", (byte & (1 << (j % 8))) != 0);
    }
    printf("\n");
  }
}

void writeECFile(const ECFile* ec, const char* filepath) {
  FILE* f = fopen(filepath, "wb");
  if (!f) return;

  fwrite(&ec->header.blockType, 1, 1, f);
  uint16_t beMagic = htobe16(ec->header.magic);
  fwrite(&beMagic, 2, 1, f);
  uint16_t beVersion = htobe16(ec->header.version);
  fwrite(&beVersion, 2, 1, f);

  for (size_t i = 0; i < ec->sessionCount; i++) {
    const SessionInfo* session = &ec->sessions[i];
    uint8_t block = 0x10;
    fwrite(&block, 1, 1, f);
    uint16_t length = strlen(session->id);
    uint16_t beLength = htobe16(length);
    fwrite(&beLength, 2, 1, f);
    fwrite(session->id, 1, length, f);
    uint64_t beStart = htobe64(session->start);
    fwrite(&beStart, 8, 1, f);
    uint64_t beDump = htobe64(session->dump);
    fwrite(&beDump, 8, 1, f);
  }

  for (size_t i = 0; i < ec->executionCount; i++) {
    const ExecutionData* exec = &ec->executions[i];
    uint8_t block = 0x11;
    fwrite(&block, 1, 1, f);
    uint64_t beClassId = htobe64(exec->classId);
    fwrite(&beClassId, 8, 1, f);
    uint16_t length = strlen(exec->name);
    uint16_t beLength = htobe16(length);
    fwrite(&beLength, 2, 1, f);
    fwrite(exec->name, 1, length, f);
    // Write varint
    uint32_t probLength = exec->probesLength;
    while (1) {
      uint8_t byte = probLength & 0x7F;
      probLength >>= 7;
      if (probLength) byte |= 0x80;
      fwrite(&byte, 1, 1, f);
      if (!probLength) break;
    }
    // Write packed bytes
    size_t byteLength = (exec->probesLength + 7) / 8;
    fwrite(exec->probesPacked, 1, byteLength, f);
  }

  fclose(f);
}

int main(int argc, char** argv) {
  ECFile ec;
  readECFile(&ec, "coverage.ec");
  printProperties(&ec);
  writeECFile(&ec, "new_coverage.ec");

  // Free memory
  for (size_t i = 0; i < ec.sessionCount; i++) {
    free(ec.sessions[i].id);
  }
  free(ec.sessions);
  for (size_t i = 0; i < ec.executionCount; i++) {
    free(ec.executions[i].name);
    free(ec.executions[i].probesPacked);
  }
  free(ec.executions);

  return 0;
}