SafeUsageChecker.java
package emissary.core;
import emissary.config.ConfigUtil;
import emissary.config.Configurator;
import emissary.core.channels.SeekableByteChannelFactory;
import emissary.util.ByteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Utility for validating that Places safely interact with IBDO payloads in byte array form. Specifically, this class
* helps with validating that changes to a IBDO's payload are followed by a call to the
* {@link IBaseDataObject#setData(byte[]) setData(byte[])}, {@link IBaseDataObject#setData(byte[], int, int)
* setData(byte[], int, int)}, or {@link IBaseDataObject#setChannelFactory(SeekableByteChannelFactory)
* setChannelFactory(SeekableByteChannelFactory)} method.
*/
public class SafeUsageChecker {
protected static final Logger LOGGER = LoggerFactory.getLogger(SafeUsageChecker.class);
public static final String ENABLED_KEY = "ENABLED";
public static final boolean ENABLED_FROM_CONFIGURATION;
public static final String UNSAFE_MODIFICATION_DETECTED = "Detected unsafe changes to IBDO byte array contents";
// to minimize I/O, we only want to read the config file once regardless of the number of instances created
static {
boolean enabledFromConfiguration = false;
try {
Configurator configurator = ConfigUtil.getConfigInfo(SafeUsageChecker.class);
enabledFromConfiguration = configurator.findBooleanEntry(ENABLED_KEY, enabledFromConfiguration);
} catch (IOException e) {
LOGGER.debug("Could not get configuration!", e);
}
ENABLED_FROM_CONFIGURATION = enabledFromConfiguration;
}
/**
* Cache that records each {@literal byte[]} reference made available to IBDO clients, along with a sha256 hash of the
* array contents. Used for determining whether the clients modify the array contents without explicitly pushing those
* changes back to the IBDO
*/
@SuppressWarnings("ArrayAsKeyOfSetOrMap")
private final Map<byte[], String> cache = new HashMap<>();
public final boolean enabled;
public SafeUsageChecker() {
enabled = ENABLED_FROM_CONFIGURATION;
}
public SafeUsageChecker(Configurator configurator) {
enabled = configurator.findBooleanEntry(ENABLED_KEY, ENABLED_FROM_CONFIGURATION);
}
/**
* Resets the snapshot cache
*/
public void reset() {
if (enabled) {
cache.clear();
}
}
/**
* Stores a new integrity snapshot
*
* @param bytes byte[] for which a snapshot should be captured
*/
public void recordSnapshot(final byte[] bytes) {
if (enabled) {
cache.put(bytes, ByteUtil.sha256Bytes(bytes));
}
}
/**
* Resets the cache and stores a new integrity snapshot
*
* @param bytes byte[] for which a snapshot should be captured
*/
public void resetCacheThenRecordSnapshot(final byte[] bytes) {
if (enabled) {
reset();
recordSnapshot(bytes);
}
}
/**
* Uses the snapshot cache to determine whether any of the byte arrays have unsaved changes
*/
public void checkForUnsafeDataChanges() {
if (enabled) {
boolean isUnsafe = cache.entrySet().stream().anyMatch(e -> !ByteUtil.sha256Bytes(e.getKey()).equals(e.getValue()));
if (isUnsafe) {
LOGGER.warn(UNSAFE_MODIFICATION_DETECTED);
}
reset();
}
}
}