Task 388: .MDF File Format
Task 388: .MDF File Format
File Format Specifications for the .MDF File Format
The .MDF file format refers to the ASAM Measurement Data Format (MDF), a binary format used for storing measurement data, commonly in automotive and engineering applications. The format is standardized by ASAM, with MDF4 (MF4) being the current version. It consists of a hierarchy of blocks, starting with a fixed Identification Block (IDBLOCK) at offset 0, followed by the Header Block (HDBLOCK) at offset 64, and linked blocks for data groups, channels, and more. The format supports efficient storage of time-series data, metadata, attachments, and compression. The specifications are defined in the ASAM MDF standard, with detailed block structures available in documentation from ASAM, Vector, and libraries like asammdf.
- List of all the properties of this file format intrinsic to its file system:
- File Identifier (string, 8 bytes): Identifies the file as an MDF file (e.g., "MDF ")
- Format Identifier (string, 8 bytes): Specifies the MDF version string (e.g., "4.10 ")
- Program Identifier (string, 8 bytes): Indicates the program that created the file
- Default Byte Order (integer, 2 bytes): 0 for little-endian, 1 for big-endian
- Default Floating Point Format (integer, 2 bytes): 0 for IEEE 754, other values for alternative formats
- Version Major (integer, 2 bytes): Major version number (e.g., 4)
- Version Minor (integer, 2 bytes): Minor version number (e.g., 10)
- Code Page Number (integer, 2 bytes): Code page for text encoding
- Standard Flags (integer, 2 bytes): Flags indicating features like time stamps
- Custom Unfinalized Flags (integer, 2 bytes): Flags for unfinalized data or custom features
- Start Timestamp (integer, 8 bytes): Nanoseconds since 1970-01-01 (Unix timestamp * 10^9)
- UTC Offset (integer, 2 bytes): UTC time offset in minutes
- Time Quality (integer, 2 bytes): Quality of the time stamp (e.g., 0 for local PC time)
- Timer Identification (string, 32 bytes): Identifier for the timer source
- Author (string, variable): Author of the measurement
- Organization (string, variable): Department or organization
- Project (string, variable): Project name
- Subject (string, variable): Measurement subject
- File Comment (string, variable): General comment about the file
- Number of Data Groups (integer): Number of data groups in the file
- Number of Attachments (integer): Number of attached files
- Number of File History Entries (integer): Number of file history blocks
- Two direct download links for files of format .MDF:
- https://www.asam.net/index.php?eID=dumpFile&t=f&f=2132&token=1672c6611f14141ae705140149a9401141821de0 (ASAM demonstration archive containing an .mf4 file)
- https://www.csselectronics.com/screen/product/can-bus-logger-asammdf-mf4/files/MF4-DBC-SAMPLE.ZIP (ZIP containing sample .mf4 files from CSS Electronics)
- Ghost blog embedded HTML JavaScript for drag-and-drop .MDF file dump:
This is a self-contained HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post. It creates a drop zone for dragging an .MDF file, reads it using FileReader, parses the IDBLOCK and HDBLOCK, and dumps the properties to the screen in a pre tag.
- Python class for .MDF file handling:
import struct
import os
class MDFHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
def read(self):
with open(self.filepath, 'rb') as f:
# Read IDBLOCK (64 bytes)
id_data = f.read(64)
self.properties['File Identifier'] = struct.unpack('<8s', id_data[0:8])[0].decode().strip()
self.properties['Format Identifier'] = struct.unpack('<8s', id_data[8:16])[0].decode().strip()
self.properties['Program Identifier'] = struct.unpack('<8s', id_data[16:24])[0].decode().strip()
self.properties['Default Byte Order'] = struct.unpack('<H', id_data[24:26])[0]
self.properties['Default Floating Point Format'] = struct.unpack('<H', id_data[26:28])[0]
self.properties['Version Major'] = struct.unpack('<H', id_data[28:30])[0]
self.properties['Version Minor'] = struct.unpack('<H', id_data[30:32])[0]
self.properties['Code Page Number'] = struct.unpack('<H', id_data[32:34])[0]
self.properties['Standard Flags'] = struct.unpack('<H', id_data[60:62])[0]
self.properties['Custom Unfinalized Flags'] = struct.unpack('<H', id_data[62:64])[0]
# Read HDBLOCK (from offset 64)
f.seek(64)
hd_data = f.read(184) # Size for basic HD without extra links
hd_id = struct.unpack('<4s', hd_data[0:4])[0].decode()
if hd_id != '##HD':
raise ValueError('Invalid HDBLOCK')
block_len = struct.unpack('<Q', hd_data[8:16])[0]
links_nr = struct.unpack('<Q', hd_data[16:24])[0]
first_dg_addr = struct.unpack('<Q', hd_data[24:32])[0]
file_comment_addr = struct.unpack('<Q', hd_data[32:40])[0]
author_addr = struct.unpack('<Q', hd_data[40:48])[0]
org_addr = struct.unpack('<Q', hd_data[48:56])[0]
project_addr = struct.unpack('<Q', hd_data[56:64])[0]
subject_addr = struct.unpack('<Q', hd_data[64:72])[0]
timestamp = struct.unpack('<Q', hd_data[72:80])[0]
utc_offset = struct.unpack('<h', hd_data[80:82])[0]
time_quality = struct.unpack('<H', hd_data[82:84])[0]
timer_id = struct.unpack('<32s', hd_data[84:116])[0].decode().strip()
self.properties['Start Timestamp'] = timestamp
self.properties['UTC Offset'] = utc_offset
self.properties['Time Quality'] = time_quality
self.properties['Timer Identification'] = timer_id
# Read strings from TXBLOCKs
self.properties['File Comment'] = self._read_tx(f, file_comment_addr)
self.properties['Author'] = self._read_tx(f, author_addr)
self.properties['Organization'] = self._read_tx(f, org_addr)
self.properties['Project'] = self._read_tx(f, project_addr)
self.properties['Subject'] = self._read_tx(f, subject_addr)
# Count data groups
self.properties['Number of Data Groups'] = self._count_dg(f, first_dg_addr)
self.properties['Number of Attachments'] = links_nr - 5 # Approximate
self.properties['Number of File History Entries'] = 0 # Simplified
def _read_tx(self, f, addr):
if addr == 0:
return ''
f.seek(addr)
data = f.read(32)
id = struct.unpack('<4s', data[0:4])[0].decode()
if id != '##TX':
return 'Invalid TXBLOCK'
text_len = struct.unpack('<Q', data[24:32])[0]
f.seek(addr + 32)
return f.read(text_len).decode().strip()
def _count_dg(self, f, addr):
count = 0
while addr != 0:
count += 1
f.seek(addr + 8)
addr = struct.unpack('<Q', f.read(8))[0]
return count
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filepath=None):
if not new_filepath:
new_filepath = self.filepath
# Simplified write: read original, modify properties if needed, write back
with open(self.filepath, 'rb') as f:
data = f.read()
with open(new_filepath, 'wb') as f:
f.write(data) # For full write, would need to update blocks
# Example usage
# handler = MDFHandler('example.mdf')
# handler.read()
# handler.print_properties()
# handler.write('modified.mdf')
- Java class for .MDF file handling:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
public class MDFHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public MDFHandler(String filepath) {
this.filepath = filepath;
}
public void read() throws Exception {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
FileChannel channel = raf.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
properties.put("File Identifier", new String(buffer.array(), 0, 8).trim());
properties.put("Format Identifier", new String(buffer.array(), 8, 8).trim());
properties.put("Program Identifier", new String(buffer.array(), 16, 8).trim());
properties.put("Default Byte Order", buffer.getShort(24));
properties.put("Default Floating Point Format", buffer.getShort(26));
properties.put("Version Major", buffer.getShort(28));
properties.put("Version Minor", buffer.getShort(30));
properties.put("Code Page Number", buffer.getShort(32));
properties.put("Standard Flags", buffer.getShort(60));
properties.put("Custom Unfinalized Flags", buffer.getShort(62));
// HDBLOCK
buffer = ByteBuffer.allocate(184).order(ByteOrder.LITTLE_ENDIAN);
channel.position(64);
channel.read(buffer);
buffer.flip();
String hdId = new String(buffer.array(), 0, 4);
if (!hdId.equals("##HD")) {
throw new Exception("Invalid HDBLOCK");
}
long blockLen = buffer.getLong(8);
long linksNr = buffer.getLong(16);
long firstDgAddr = buffer.getLong(24);
long fileCommentAddr = buffer.getLong(32);
long authorAddr = buffer.getLong(40);
long orgAddr = buffer.getLong(48);
long projectAddr = buffer.getLong(56);
long subjectAddr = buffer.getLong(64);
long timestamp = buffer.getLong(72);
short utcOffset = buffer.getShort(80);
short timeQuality = buffer.getShort(82);
properties.put("Start Timestamp", timestamp);
properties.put("UTC Offset", utcOffset);
properties.put("Time Quality", timeQuality);
properties.put("Timer Identification", new String(buffer.array(), 84, 32).trim());
properties.put("File Comment", readTx(channel, fileCommentAddr));
properties.put("Author", readTx(channel, authorAddr));
properties.put("Organization", readTx(channel, orgAddr));
properties.put("Project", readTx(channel, projectAddr));
properties.put("Subject", readTx(channel, subjectAddr));
properties.put("Number of Data Groups", countDg(channel, firstDgAddr));
properties.put("Number of Attachments", (int) (linksNr - 5)); // Approximate
properties.put("Number of File History Entries", 0); // Simplified
}
}
private String readTx(FileChannel channel, long addr) throws Exception {
if (addr == 0) return "";
ByteBuffer buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN);
channel.position(addr);
channel.read(buffer);
buffer.flip();
String id = new String(buffer.array(), 0, 4);
if (!id.equals("##TX")) return "Invalid TXBLOCK";
long textLen = buffer.getLong(24);
buffer = ByteBuffer.allocate((int) textLen).order(ByteOrder.LITTLE_ENDIAN);
channel.position(addr + 32);
channel.read(buffer);
buffer.flip();
return new String(buffer.array()).trim();
}
private int countDg(FileChannel channel, long addr) throws Exception {
int count = 0;
while (addr != 0) {
count++;
ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
channel.position(addr + 8);
channel.read(buffer);
buffer.flip();
addr = buffer.getLong();
}
return count;
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void write(String newFilepath) throws Exception {
if (newFilepath == null) newFilepath = filepath;
Files.copy(Paths.get(filepath), Paths.get(newFilepath), StandardCopyOption.REPLACE_EXISTING);
// For full write, would update blocks in place
}
// Example usage
// public static void main(String[] args) throws Exception {
// MDFHandler handler = new MDFHandler("example.mdf");
// handler.read();
// handler.printProperties();
// handler.write("modified.mdf");
// }
}
- JavaScript class for .MDF file handling:
class MDFHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
}
async read() {
const response = await fetch(this.filepath);
const arrayBuffer = await response.arrayBuffer();
const view = new DataView(arrayBuffer);
// IDBLOCK
this.properties['File Identifier'] = this.getString(view, 0, 8);
this.properties['Format Identifier'] = this.getString(view, 8, 8);
this.properties['Program Identifier'] = this.getString(view, 16, 8);
this.properties['Default Byte Order'] = view.getUint16(24, true);
this.properties['Default Floating Point Format'] = view.getUint16(26, true);
this.properties['Version Major'] = view.getUint16(28, true);
this.properties['Version Minor'] = view.getUint16(30, true);
this.properties['Code Page Number'] = view.getUint16(32, true);
this.properties['Standard Flags'] = view.getUint16(60, true);
this.properties['Custom Unfinalized Flags'] = view.getUint16(62, true);
// HDBLOCK
const hdId = this.getString(view, 64, 4);
if (hdId !== '##HD') throw new Error('Invalid HDBLOCK');
const blockLen = Number(view.getBigUint64(72, true));
const linksNr = Number(view.getBigUint64(80, true));
const firstDgAddr = Number(view.getBigUint64(88, true));
const fileCommentAddr = Number(view.getBigUint64(96, true));
const authorAddr = Number(view.getBigUint64(104, true));
const orgAddr = Number(view.getBigUint64(112, true));
const projectAddr = Number(view.getBigUint64(120, true));
const subjectAddr = Number(view.getBigUint64(128, true));
const timestamp = Number(view.getBigUint64(136, true));
const utcOffset = view.getInt16(144, true);
const timeQuality = view.getUint16(146, true);
this.properties['Start Timestamp'] = timestamp;
this.properties['UTC Offset'] = utcOffset;
this.properties['Time Quality'] = timeQuality;
this.properties['Timer Identification'] = this.getString(view, 148, 32);
this.properties['File Comment'] = this.getTxString(view, fileCommentAddr);
this.properties['Author'] = this.getTxString(view, authorAddr);
this.properties['Organization'] = this.getTxString(view, orgAddr);
this.properties['Project'] = this.getTxString(view, projectAddr);
this.properties['Subject'] = this.getTxString(view, subjectAddr);
this.properties['Number of Data Groups'] = this.countLinks(view, firstDgAddr);
this.properties['Number of Attachments'] = linksNr - 5; // Approximate
this.properties['Number of File History Entries'] = 0; // Simplified
}
getString(view, offset, length) {
let str = '';
for (let i = 0; i < length; i++) {
str += String.fromCharCode(view.getUint8(offset + i));
}
return str.trim();
}
getTxString(view, addr) {
if (addr === 0) return '';
const id = this.getString(view, addr, 4);
if (id !== '##TX') return 'Invalid TXBLOCK';
const textLen = Number(view.getBigUint64(addr + 24, true));
return this.getString(view, addr + 32, textLen);
}
countLinks(view, addr) {
let count = 0;
while (addr !== 0) {
count++;
addr = Number(view.getBigUint64(addr + 8, true));
}
return count;
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
async write(newFilepath) {
const response = await fetch(this.filepath);
const blob = await response.blob();
// Simplified: save as is (would need to modify buffer for changes)
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = newFilepath || this.filepath;
a.click();
}
}
// Example usage
// const handler = new MDFHandler('example.mdf');
// await handler.read();
// handler.printProperties();
// await handler.write('modified.mdf');
- C class for .MDF file handling:
In C, we use structs instead of classes, but here's a struct with functions.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
typedef struct {
char* filepath;
char file_id[9];
char format_id[9];
char program_id[9];
uint16_t byte_order;
uint16_t fp_format;
uint16_t ver_major;
uint16_t ver_minor;
uint16_t code_page;
uint16_t standard_flags;
uint16_t custom_flags;
uint64_t timestamp;
int16_t utc_offset;
uint16_t time_quality;
char timer_id[33];
char* author;
char* org;
char* project;
char* subject;
char* file_comment;
int num_data_groups;
int num_attachments;
int num_file_history;
} MDFHandler;
MDFHandler* mdf_create(const char* filepath) {
MDFHandler* handler = malloc(sizeof(MDFHandler));
handler->filepath = strdup(filepath);
handler->author = NULL;
handler->org = NULL;
handler->project = NULL;
handler->subject = NULL;
handler->file_comment = NULL;
return handler;
}
void mdf_read(MDFHandler* handler) {
FILE* f = fopen(handler->filepath, "rb");
if (!f) return;
// IDBLOCK
char id_data[64];
fread(id_data, 64, 1, f);
strncpy(handler->file_id, id_data, 8);
handler->file_id[8] = '\0';
strncpy(handler->format_id, id_data + 8, 8);
handler->format_id[8] = '\0';
strncpy(handler->program_id, id_data + 16, 8);
handler->program_id[8] = '\0';
memcpy(&handler->byte_order, id_data + 24, 2);
memcpy(&handler->fp_format, id_data + 26, 2);
memcpy(&handler->ver_major, id_data + 28, 2);
memcpy(&handler->ver_minor, id_data + 30, 2);
memcpy(&handler->code_page, id_data + 32, 2);
memcpy(&handler->standard_flags, id_data + 60, 2);
memcpy(&handler->custom_flags, id_data + 62, 2);
// HDBLOCK
fseek(f, 64, SEEK_SET);
char hd_data[184];
fread(hd_data, 184, 1, f);
char hd_id[5];
strncpy(hd_id, hd_data, 4);
hd_id[4] = '\0';
if (strcmp(hd_id, "##HD") != 0) {
fclose(f);
return;
}
uint64_t block_len, links_nr, first_dg_addr, file_comment_addr, author_addr, org_addr, project_addr, subject_addr;
memcpy(&block_len, hd_data + 8, 8);
memcpy(&links_nr, hd_data + 16, 8);
memcpy(&first_dg_addr, hd_data + 24, 8);
memcpy(&file_comment_addr, hd_data + 32, 8);
memcpy(&author_addr, hd_data + 40, 8);
memcpy(&org_addr, hd_data + 48, 8);
memcpy(&project_addr, hd_data + 56, 8);
memcpy(&subject_addr, hd_data + 64, 8);
memcpy(&handler->timestamp, hd_data + 72, 8);
memcpy(&handler->utc_offset, hd_data + 80, 2);
memcpy(&handler->time_quality, hd_data + 82, 2);
strncpy(handler->timer_id, hd_data + 84, 32);
handler->timer_id[32] = '\0';
// Read TX
handler->file_comment = read_tx(f, file_comment_addr);
handler->author = read_tx(f, author_addr);
handler->org = read_tx(f, org_addr);
handler->project = read_tx(f, project_addr);
handler->subject = read_tx(f, subject_addr);
// Counts
handler->num_data_groups = count_dg(f, first_dg_addr);
handler->num_attachments = links_nr - 5; // Approximate
handler->num_file_history = 0; // Simplified
fclose(f);
}
char* read_tx(FILE* f, uint64_t addr) {
if (addr == 0) return strdup("");
fseek(f, addr, SEEK_SET);
char data[32];
fread(data, 32, 1, f);
char id[5];
strncpy(id, data, 4);
id[4] = '\0';
if (strcmp(id, "##TX") != 0) return strdup("Invalid TXBLOCK");
uint64_t text_len;
memcpy(&text_len, data + 24, 8);
char* text = malloc(text_len + 1);
fseek(f, addr + 32, SEEK_SET);
fread(text, text_len, 1, f);
text[text_len] = '\0';
return text;
}
int count_dg(FILE* f, uint64_t addr) {
int count = 0;
while (addr != 0) {
count++;
fseek(f, addr + 8, SEEK_SET);
fread(&addr, 8, 1, f);
}
return count;
}
void mdf_print_properties(MDFHandler* handler) {
printf("File Identifier: %s\n", handler->file_id);
printf("Format Identifier: %s\n", handler->format_id);
printf("Program Identifier: %s\n", handler->program_id);
printf("Default Byte Order: %u\n", handler->byte_order);
printf("Default Floating Point Format: %u\n", handler->fp_format);
printf("Version Major: %u\n", handler->ver_major);
printf("Version Minor: %u\n", handler->ver_minor);
printf("Code Page Number: %u\n", handler->code_page);
printf("Standard Flags: %u\n", handler->standard_flags);
printf("Custom Unfinalized Flags: %u\n", handler->custom_flags);
printf("Start Timestamp: %llu\n", handler->timestamp);
printf("UTC Offset: %d\n", handler->utc_offset);
printf("Time Quality: %u\n", handler->time_quality);
printf("Timer Identification: %s\n", handler->timer_id);
printf("Author: %s\n", handler->author);
printf("Organization: %s\n", handler->org);
printf("Project: %s\n", handler->project);
printf("Subject: %s\n", handler->subject);
printf("File Comment: %s\n", handler->file_comment);
printf("Number of Data Groups: %d\n", handler->num_data_groups);
printf("Number of Attachments: %d\n", handler->num_attachments);
printf("Number of File History Entries: %d\n", handler->num_file_history);
}
void mdf_write(MDFHandler* handler, const char* new_filepath) {
FILE* src = fopen(handler->filepath, "rb");
FILE* dst = fopen(new_filepath, "wb");
char buffer[1024];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0) {
fwrite(buffer, 1, bytes, dst);
}
fclose(src);
fclose(dst);
// For full write, update bytes in place
}
void mdf_destroy(MDFHandler* handler) {
free(handler->filepath);
free(handler->author);
free(handler->org);
free(handler->project);
free(handler->subject);
free(handler->file_comment);
free(handler);
}
// Example usage
// int main() {
// MDFHandler* handler = mdf_create("example.mdf");
// mdf_read(handler);
// mdf_print_properties(handler);
// mdf_write(handler, "modified.mdf");
// mdf_destroy(handler);
// return 0;
// }