001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.util.datalog; 006 007import java.io.IOException; 008import java.io.RandomAccessFile; 009import java.nio.BufferUnderflowException; 010import java.nio.ByteBuffer; 011import java.nio.ByteOrder; 012import java.nio.channels.FileChannel; 013import java.nio.charset.StandardCharsets; 014import java.util.NoSuchElementException; 015import java.util.function.Consumer; 016 017/** Data log reader (reads logs written by the DataLog class). */ 018public class DataLogReader implements Iterable<DataLogRecord> { 019 /** 020 * Constructs from a byte buffer. 021 * 022 * @param buffer byte buffer 023 */ 024 public DataLogReader(ByteBuffer buffer) { 025 m_buf = buffer; 026 m_buf.order(ByteOrder.LITTLE_ENDIAN); 027 } 028 029 /** 030 * Constructs from a file. 031 * 032 * @param filename filename 033 * @throws IOException if unable to open/read file 034 */ 035 public DataLogReader(String filename) throws IOException { 036 RandomAccessFile f = new RandomAccessFile(filename, "r"); 037 FileChannel channel = f.getChannel(); 038 m_buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 039 m_buf.order(ByteOrder.LITTLE_ENDIAN); 040 channel.close(); 041 f.close(); 042 } 043 044 /** 045 * Returns true if the data log is valid (e.g. has a valid header). 046 * 047 * @return True if valid, false otherwise 048 */ 049 public boolean isValid() { 050 return m_buf.remaining() >= 12 051 && m_buf.get(0) == 'W' 052 && m_buf.get(1) == 'P' 053 && m_buf.get(2) == 'I' 054 && m_buf.get(3) == 'L' 055 && m_buf.get(4) == 'O' 056 && m_buf.get(5) == 'G' 057 && m_buf.getShort(6) >= 0x0100; 058 } 059 060 /** 061 * Gets the data log version. Returns 0 if data log is invalid. 062 * 063 * @return Version number; most significant byte is major, least significant is minor (so version 064 * 1.0 will be 0x0100) 065 */ 066 public short getVersion() { 067 if (m_buf.remaining() < 12) { 068 return 0; 069 } 070 return m_buf.getShort(6); 071 } 072 073 /** 074 * Gets the extra header data. 075 * 076 * @return Extra header data 077 */ 078 public String getExtraHeader() { 079 ByteBuffer buf = m_buf.duplicate(); 080 buf.order(ByteOrder.LITTLE_ENDIAN); 081 buf.position(8); 082 int size = buf.getInt(); 083 byte[] arr = new byte[size]; 084 buf.get(arr); 085 return new String(arr, StandardCharsets.UTF_8); 086 } 087 088 @Override 089 public void forEach(Consumer<? super DataLogRecord> action) { 090 int size = m_buf.remaining(); 091 for (int pos = 12 + m_buf.getInt(8); pos < size; pos = getNextRecord(pos)) { 092 DataLogRecord record; 093 try { 094 record = getRecord(pos); 095 } catch (NoSuchElementException ex) { 096 break; 097 } 098 action.accept(record); 099 } 100 } 101 102 @Override 103 public DataLogIterator iterator() { 104 return new DataLogIterator(this, 12 + m_buf.getInt(8)); 105 } 106 107 private long readVarInt(int pos, int len) { 108 long val = 0; 109 for (int i = 0; i < len; i++) { 110 val |= ((long) (m_buf.get(pos + i) & 0xff)) << (i * 8); 111 } 112 return val; 113 } 114 115 DataLogRecord getRecord(int pos) { 116 try { 117 int lenbyte = m_buf.get(pos) & 0xff; 118 int entryLen = (lenbyte & 0x3) + 1; 119 int sizeLen = ((lenbyte >> 2) & 0x3) + 1; 120 int timestampLen = ((lenbyte >> 4) & 0x7) + 1; 121 int headerLen = 1 + entryLen + sizeLen + timestampLen; 122 int entry = (int) readVarInt(pos + 1, entryLen); 123 int size = (int) readVarInt(pos + 1 + entryLen, sizeLen); 124 long timestamp = readVarInt(pos + 1 + entryLen + sizeLen, timestampLen); 125 // build a slice of the data contents 126 ByteBuffer data = m_buf.duplicate(); 127 data.position(pos + headerLen); 128 data.limit(pos + headerLen + size); 129 return new DataLogRecord(entry, timestamp, data.slice()); 130 } catch (BufferUnderflowException | IndexOutOfBoundsException ex) { 131 throw new NoSuchElementException(); 132 } 133 } 134 135 int getNextRecord(int pos) { 136 int lenbyte = m_buf.get(pos) & 0xff; 137 int entryLen = (lenbyte & 0x3) + 1; 138 int sizeLen = ((lenbyte >> 2) & 0x3) + 1; 139 int timestampLen = ((lenbyte >> 4) & 0x7) + 1; 140 int headerLen = 1 + entryLen + sizeLen + timestampLen; 141 142 int size = 0; 143 for (int i = 0; i < sizeLen; i++) { 144 size |= (m_buf.get(pos + 1 + entryLen + i) & 0xff) << (i * 8); 145 } 146 return pos + headerLen + size; 147 } 148 149 int size() { 150 return m_buf.remaining(); 151 } 152 153 private final ByteBuffer m_buf; 154}