/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.vm.program;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.program.listener.ProgramListener;
import org.ethereum.vm.program.listener.ProgramListenerAware;
import java.util.LinkedList;
import java.util.List;
import static java.lang.Math.ceil;
import static java.lang.Math.min;
import static java.lang.String.format;
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
import static org.ethereum.util.ByteUtil.oneByteToHexString;
public class Memory implements ProgramListenerAware {
private static final int CHUNK_SIZE = 1024;
private static final int WORD_SIZE = 32;
private List<byte[]> chunks = new LinkedList<>();
private int softSize;
private ProgramListener programListener;
@Override
public void setProgramListener(ProgramListener traceListener) {
this.programListener = traceListener;
}
public byte[] read(int address, int size) {
if (size <= 0) return EMPTY_BYTE_ARRAY;
extend(address, size);
byte[] data = new byte[size];
int chunkIndex = address / CHUNK_SIZE;
int chunkOffset = address % CHUNK_SIZE;
int toGrab = data.length;
int start = 0;
while (toGrab > 0) {
int copied = grabMax(chunkIndex, chunkOffset, toGrab, data, start);
// read next chunk from the start
++chunkIndex;
chunkOffset = 0;
// mark remind
toGrab -= copied;
start += copied;
}
return data;
}
public void write(int address, byte[] data, int dataSize, boolean limited) {
if (data.length < dataSize)
dataSize = data.length;
if (!limited)
extend(address, dataSize);
int chunkIndex = address / CHUNK_SIZE;
int chunkOffset = address % CHUNK_SIZE;
int toCapture = 0;
if (limited)
toCapture = (address + dataSize > softSize) ? softSize - address : dataSize;
else
toCapture = dataSize;
int start = 0;
while (toCapture > 0) {
int captured = captureMax(chunkIndex, chunkOffset, toCapture, data, start);
// capture next chunk
++chunkIndex;
chunkOffset = 0;
// mark remind
toCapture -= captured;
start += captured;
}
if (programListener != null) programListener.onMemoryWrite(address, data, dataSize);
}
public void extendAndWrite(int address, int allocSize, byte[] data) {
extend(address, allocSize);
write(address, data, data.length, false);
}
public void extend(int address, int size) {
if (size <= 0) return;
final int newSize = address + size;
int toAllocate = newSize - internalSize();
if (toAllocate > 0) {
addChunks((int) ceil((double) toAllocate / CHUNK_SIZE));
}
toAllocate = newSize - softSize;
if (toAllocate > 0) {
toAllocate = (int) ceil((double) toAllocate / WORD_SIZE) * WORD_SIZE;
softSize += toAllocate;
if (programListener != null) programListener.onMemoryExtend(toAllocate);
}
}
public DataWord readWord(int address) {
return new DataWord(read(address, 32));
}
// just access expecting all data valid
public byte readByte(int address) {
int chunkIndex = address / CHUNK_SIZE;
int chunkOffset = address % CHUNK_SIZE;
byte[] chunk = chunks.get(chunkIndex);
return chunk[chunkOffset];
}
@Override
public String toString() {
StringBuilder memoryData = new StringBuilder();
StringBuilder firstLine = new StringBuilder();
StringBuilder secondLine = new StringBuilder();
for (int i = 0; i < softSize; ++i) {
byte value = readByte(i);
// Check if value is ASCII
String character = ((byte) 0x20 <= value && value <= (byte) 0x7e) ? new String(new byte[]{value}) : "?";
firstLine.append(character).append("");
secondLine.append(oneByteToHexString(value)).append(" ");
if ((i + 1) % 8 == 0) {
String tmp = format("%4s", Integer.toString(i - 7, 16)).replace(" ", "0");
memoryData.append("").append(tmp).append(" ");
memoryData.append(firstLine).append(" ");
memoryData.append(secondLine);
if (i + 1 < softSize) memoryData.append("\n");
firstLine.setLength(0);
secondLine.setLength(0);
}
}
return memoryData.toString();
}
public int size() {
return softSize;
}
public int internalSize() {
return chunks.size() * CHUNK_SIZE;
}
public List<byte[]> getChunks() {
return new LinkedList<>(chunks);
}
private int captureMax(int chunkIndex, int chunkOffset, int size, byte[] src, int srcPos) {
byte[] chunk = chunks.get(chunkIndex);
int toCapture = min(size, chunk.length - chunkOffset);
System.arraycopy(src, srcPos, chunk, chunkOffset, toCapture);
return toCapture;
}
private int grabMax(int chunkIndex, int chunkOffset, int size, byte[] dest, int destPos) {
byte[] chunk = chunks.get(chunkIndex);
int toGrab = min(size, chunk.length - chunkOffset);
System.arraycopy(chunk, chunkOffset, dest, destPos, toGrab);
return toGrab;
}
private void addChunks(int num) {
for (int i = 0; i < num; ++i) {
chunks.add(new byte[CHUNK_SIZE]);
}
}
}