/*
* Copyright (c) 2009, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
package org.contikios.cooja.contikimote;
import java.awt.Container;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JComponent;
import javax.swing.JLabel;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jdom.Element;
import org.contikios.cooja.AbstractionLevelDescription;
import org.contikios.cooja.ClassDescription;
import org.contikios.cooja.CoreComm;
import org.contikios.cooja.Cooja;
import org.contikios.cooja.Mote;
import org.contikios.cooja.MoteInterface;
import org.contikios.cooja.MoteType;
import org.contikios.cooja.ProjectConfig;
import org.contikios.cooja.mote.memory.SectionMoteMemory;
import org.contikios.cooja.Simulation;
import org.contikios.cooja.dialogs.CompileContiki;
import org.contikios.cooja.dialogs.ContikiMoteCompileDialog;
import org.contikios.cooja.dialogs.MessageList;
import org.contikios.cooja.dialogs.MessageList.MessageContainer;
import org.contikios.cooja.mote.memory.ArrayMemory;
import org.contikios.cooja.mote.memory.MemoryInterface;
import org.contikios.cooja.mote.memory.MemoryInterface.Symbol;
import org.contikios.cooja.mote.memory.MemoryLayout;
import org.contikios.cooja.mote.memory.UnknownVariableException;
import org.contikios.cooja.mote.memory.VarMemory;
import org.contikios.cooja.util.StringUtils;
/**
* The Cooja mote type holds the native library used to communicate with an
* underlying Contiki system. All communication with that system should always
* pass through this mote type.
* <p>
* This type also contains information about sensors and mote interfaces a mote
* of this type has.
* <p>
* All core communication with the Cooja mote should be via this class. When a
* mote type is created it allocates a CoreComm to be used with this type, and
* loads the variable and segments addresses.
* <p>
* When a new mote type is created an initialization function is run on the
* Contiki system in order to create the initial memory. When a new mote is
* created the createInitialMemory() method should be called to get this initial
* memory for the mote.
*
* @author Fredrik Osterlind
*/
@ClassDescription("Cooja mote")
@AbstractionLevelDescription("OS level")
public class ContikiMoteType implements MoteType {
private static final Logger logger = Logger.getLogger(ContikiMoteType.class);
public static final String ID_PREFIX = "mtype";
/**
* Library file suffix
*/
final static public String librarySuffix = ".cooja";
/**
* Map file suffix
*/
final static public String mapSuffix = ".map";
/**
* Make archive file suffix
*/
final static public String dependSuffix = ".a";
/**
* Temporary output directory
*/
final static public File tempOutputDirectory = new File("obj_cooja");
/**
* Communication stacks in Contiki.
*/
public enum NetworkStack {
DEFAULT, MANUAL;
public String manualHeader = "netstack-conf-example.h";
@Override
public String toString() {
if (this == DEFAULT) {
return "Default (from contiki-conf.h)";
} else if (this == MANUAL) {
return "Manual netstack header";
}
return "[unknown]";
}
public String getHeaderFile() {
if (this == DEFAULT) {
return null;
} else if (this == MANUAL) {
return manualHeader;
}
return null;
}
public String getConfig() {
if (this == DEFAULT) {
return "DEFAULT";
} else if (this == MANUAL) {
return "MANUAL:" + manualHeader;
}
return "[unknown]";
}
public static NetworkStack parseConfig(String config) {
if (config.equals("DEFAULT")) {
return DEFAULT;
} else if (config.startsWith("MANUAL")) {
NetworkStack st = MANUAL;
st.manualHeader = config.split(":")[1];
return st;
}
/* TODO Backwards compatibility */
logger.warn("Bad network stack config: '" + config + "', using default");
return DEFAULT;
}
}
private final String[] sensors = {"button_sensor", "pir_sensor", "vib_sensor"};
private String identifier = null;
private String description = null;
private File fileSource = null;
private File fileFirmware = null;
private String compileCommands = null;
/* For internal use only: using during Contiki compilation. */
private File contikiApp = null; /* Contiki application: hello-world.c */
public File libSource = null; /* JNI library: obj_cooja/mtype1.c */
public File libFile = null; /* JNI library: obj_cooja/mtype1.lib */
public File archiveFile = null; /* Contiki archive: obj_cooja/mtype1.a */
public File mapFile = null; /* Contiki map: obj_cooja/mtype1.map */
public String javaClassName = null; /* Loading Java class name: Lib1 */
private String[] coreInterfaces = null;
private ArrayList<Class<? extends MoteInterface>> moteInterfacesClasses = null;
private boolean hasSystemSymbols = false;
private NetworkStack netStack = NetworkStack.DEFAULT;
// Type specific class configuration
private ProjectConfig myConfig = null;
private CoreComm myCoreComm = null;
// Initial memory for all motes of this type
private SectionMoteMemory initialMemory = null;
/** Offset between native (cooja) and contiki address space */
long offset;
/**
* Creates a new uninitialized Cooja mote type. This mote type needs to load
* a library file and parse a map file before it can be used.
*/
public ContikiMoteType() {
}
@Override
public Mote generateMote(Simulation simulation) {
return new ContikiMote(this, simulation);
}
@Override
public boolean configureAndInit(Container parentContainer, Simulation simulation,
boolean visAvailable) throws MoteTypeCreationException {
myConfig = simulation.getCooja().getProjectConfig().clone();
if (visAvailable) {
if (getDescription() == null) {
setDescription("Cooja Mote Type #" + (simulation.getMoteTypes().length + 1));
}
/* Compile Contiki from dialog */
boolean compileOK
= ContikiMoteCompileDialog.showDialog(parentContainer, simulation, this);
if (!compileOK) {
return false;
}
} else {
if (getIdentifier() == null) {
throw new MoteTypeCreationException("No identifier specified");
}
if (getContikiSourceFile() == null) {
throw new MoteTypeCreationException("No Contiki application specified");
}
/* Create variables used for compiling Contiki */
contikiApp = getContikiSourceFile();
libSource = new File(
contikiApp.getParentFile(),
"obj_cooja/" + getIdentifier() + ".c");
libFile = new File(
contikiApp.getParentFile(),
"obj_cooja/" + getIdentifier() + librarySuffix);
archiveFile = new File(
contikiApp.getParentFile(),
"obj_cooja/" + getIdentifier() + dependSuffix);
mapFile = new File(
contikiApp.getParentFile(),
"obj_cooja/" + getIdentifier() + mapSuffix);
javaClassName = CoreComm.getAvailableClassName();
if (javaClassName == null) {
throw new MoteTypeCreationException("Could not allocate a core communicator.");
}
/* Delete output files */
libSource.delete();
libFile.delete();
archiveFile.delete();
mapFile.delete();
/* Generate Contiki main source */
/*try {
CompileContiki.generateSourceFile(
libSource,
javaClassName,
getSensors(),
getCoreInterfaces()
);
} catch (Exception e) {
throw (MoteTypeCreationException) new MoteTypeCreationException(
"Error when generating Contiki main source").initCause(e);
}*/
/* Prepare compiler environment */
String[][] env;
try {
env = CompileContiki.createCompilationEnvironment(
getIdentifier(),
contikiApp,
mapFile,
libFile,
archiveFile,
javaClassName);
CompileContiki.redefineCOOJASources(
this,
env
);
} catch (Exception e) {
throw new MoteTypeCreationException("Error when creating environment: " + e.getMessage(), e);
}
String[] envOneDimension = new String[env.length];
for (int i = 0; i < env.length; i++) {
envOneDimension[i] = env[i][0] + "=" + env[i][1];
}
/* Compile Contiki (may consist of several commands) */
if (getCompileCommands() == null) {
throw new MoteTypeCreationException("No compile commands specified");
}
final MessageList compilationOutput = new MessageList();
String[] arr = getCompileCommands().split("\n");
for (String cmd : arr) {
if (cmd.trim().isEmpty()) {
continue;
}
try {
CompileContiki.compile(
cmd,
envOneDimension,
null /* Do not observe output firmware file */,
getContikiSourceFile().getParentFile(),
null,
null,
compilationOutput,
true
);
} catch (Exception e) {
MoteTypeCreationException newException
= new MoteTypeCreationException("Mote type creation failed: " + e.getMessage());
newException = (MoteTypeCreationException) newException.initCause(e);
newException.setCompilationOutput(compilationOutput);
/* Print last 10 compilation errors to console */
MessageContainer[] messages = compilationOutput.getMessages();
for (int i = messages.length - 10; i < messages.length; i++) {
if (i < 0) {
continue;
}
logger.fatal(">> " + messages[i]);
}
logger.fatal("Compilation error: " + e.getMessage());
throw newException;
}
}
/* Make sure compiled firmware exists */
if (getContikiFirmwareFile() == null
|| !getContikiFirmwareFile().exists()) {
throw new MoteTypeCreationException("Contiki firmware file does not exist: " + getContikiFirmwareFile());
}
}
/* Load compiled library */
doInit();
return true;
}
public static File getExpectedFirmwareFile(File source) {
File parentDir = source.getParentFile();
String sourceNoExtension = source.getName().substring(0, source.getName().length() - 2);
return new File(parentDir, sourceNoExtension + librarySuffix);
}
/**
* For internal use.
*
* This method creates a core communicator linking a Contiki library and a
* Java class.
* It furthermore parses library Contiki memory addresses and creates the
* initial memory.
*
* @throws MoteTypeCreationException
*/
private void doInit() throws MoteTypeCreationException {
if (myCoreComm != null) {
throw new MoteTypeCreationException(
"Core communicator already used: " + myCoreComm.getClass().getName());
}
if (getContikiFirmwareFile() == null
|| !getContikiFirmwareFile().exists()) {
throw new MoteTypeCreationException("Library file could not be found: " + getContikiFirmwareFile());
}
if (this.javaClassName == null) {
throw new MoteTypeCreationException("Unknown Java class library: " + this.javaClassName);
}
// Allocate core communicator class
logger.info("Creating core communicator between Java class " + javaClassName + " and Contiki library '" + getContikiFirmwareFile().getPath() + "");
myCoreComm = CoreComm.createCoreComm(this.javaClassName, getContikiFirmwareFile());
/* Parse addresses using map file
* or output of command specified in external tools settings (e.g. nm -a )
*/
boolean useCommand = Boolean.parseBoolean(Cooja.getExternalToolsSetting("PARSE_WITH_COMMAND", "false"));
SectionParser dataSecParser;
SectionParser bssSecParser;
SectionParser commonSecParser;
SectionParser readonlySecParser = null;
HashMap<String, Symbol> variables = new HashMap<>();
if (useCommand) {
/* Parse command output */
String[] output = loadCommandData(getContikiFirmwareFile());
if (output == null) {
throw new MoteTypeCreationException("No parse command output loaded");
}
dataSecParser = new CommandSectionParser(
output,
Cooja.getExternalToolsSetting("COMMAND_DATA_START"),
Cooja.getExternalToolsSetting("COMMAND_DATA_END"),
Cooja.getExternalToolsSetting("COMMAND_VAR_SEC_DATA"));
bssSecParser = new CommandSectionParser(
output,
Cooja.getExternalToolsSetting("COMMAND_BSS_START"),
Cooja.getExternalToolsSetting("COMMAND_BSS_END"),
Cooja.getExternalToolsSetting("COMMAND_VAR_SEC_BSS"));
commonSecParser = new CommandSectionParser(
output,
Cooja.getExternalToolsSetting("COMMAND_COMMON_START"),
Cooja.getExternalToolsSetting("COMMAND_COMMON_END"),
Cooja.getExternalToolsSetting("COMMAND_VAR_SEC_COMMON"));
/* XXX Currently Cooja tries to sync readonly memory */
readonlySecParser = null;/* new CommandSectionParser(
output,
Cooja.getExternalToolsSetting("COMMAND_READONLY_START"),
Cooja.getExternalToolsSetting("COMMAND_READONLY_END"),
Cooja.getExternalToolsSetting("COMMAND_VAR_SEC_READONLY"));*/
} else {
/* Parse map file */
if (mapFile == null
|| !mapFile.exists()) {
throw new MoteTypeCreationException("Map file " + mapFile + " could not be found");
}
String[] mapData = loadMapFile(mapFile);
if (mapData == null) {
logger.fatal("No map data could be loaded");
throw new MoteTypeCreationException("No map data could be loaded: " + mapFile);
}
dataSecParser = new MapSectionParser(
mapData,
Cooja.getExternalToolsSetting("MAPFILE_DATA_START"),
Cooja.getExternalToolsSetting("MAPFILE_DATA_SIZE"));
bssSecParser = new MapSectionParser(
mapData,
Cooja.getExternalToolsSetting("MAPFILE_BSS_START"),
Cooja.getExternalToolsSetting("MAPFILE_BSS_SIZE"));
commonSecParser = new MapSectionParser(
mapData,
Cooja.getExternalToolsSetting("MAPFILE_COMMON_START"),
Cooja.getExternalToolsSetting("MAPFILE_COMMON_SIZE"));
readonlySecParser = null;
}
/* We first need the value of Contiki's referenceVar, which tells us the
* memory offset between Contiki's variable and the relative addresses that
* were calculated directly from the library file.
*
* This offset will be used in Cooja in the memory abstraction to match
* Contiki's and Cooja's address spaces */
{
SectionMoteMemory tmp = new SectionMoteMemory(variables);
VarMemory varMem = new VarMemory(tmp);
tmp.addMemorySection("tmp.data", dataSecParser.parse(0));
tmp.addMemorySection("tmp.bss", bssSecParser.parse(0));
try {
int referenceVar = (int) varMem.getVariable("referenceVar").addr;
myCoreComm.setReferenceAddress(referenceVar);
} catch (UnknownVariableException e) {
throw new MoteTypeCreationException("Error setting reference variable: " + e.getMessage(), e);
} catch (RuntimeException e) {
throw new MoteTypeCreationException("Error setting reference variable: " + e.getMessage(), e);
}
getCoreMemory(tmp);
offset = varMem.getIntValueOf("referenceVar") & 0xFFFFFFFFL;
logger.info(getContikiFirmwareFile().getName()
+ ": offsetting Cooja mote address space: 0x" + Long.toHexString(offset));
}
/* Create initial memory: data+bss+optional common */
initialMemory = new SectionMoteMemory(variables);
initialMemory.addMemorySection("data", dataSecParser.parse(offset));
initialMemory.addMemorySection("bss", bssSecParser.parse(offset));
initialMemory.addMemorySection("common", commonSecParser.parse(offset));
if (readonlySecParser != null) {
initialMemory.addMemorySection("readonly", readonlySecParser.parse(offset));
}
getCoreMemory(initialMemory);
}
/**
* Abstract base class for concrete section parser class.
*/
public static abstract class SectionParser {
private final String[] mapFileData;
protected int startAddr;
protected int size;
public SectionParser(String[] mapFileData) {
this.mapFileData = mapFileData;
}
public String[] getData() {
return mapFileData;
}
public int getStartAddr() {
return startAddr;
}
public int getSize() {
return size;
}
protected abstract void parseStartAddr();
protected abstract void parseSize();
abstract Map<String, Symbol> parseSymbols(long offset);
protected int parseFirstHexInt(String regexp, String[] data) {
String retString = getFirstMatchGroup(data, regexp, 1);
if (retString == null || retString.equals("")) {
return -1;
}
return Integer.parseInt(retString.trim(), 16);
}
public MemoryInterface parse(long offset) {
/* Parse start address and size of section */
parseStartAddr();
parseSize();
if (getStartAddr() < 0 || getSize() <= 0) {
return null;
}
Map<String, Symbol> variables = parseSymbols(offset);
logger.info(String.format("Parsed section at 0x%x ( %d == 0x%x bytes)",
getStartAddr() + offset,
getSize(),
getSize()));
if (logger.isDebugEnabled()) {
for (String var : variables.keySet()) {
logger.debug(String.format("Found Symbol: %s, 0x%x, %d",
var,
variables.get(var).addr,
variables.get(var).size));
}
}
return new ArrayMemory(
getStartAddr() + offset,
getSize(),
MemoryLayout.getNative(),
variables);
}
}
/**
* Parses Map file for seciton data.
*/
public static class MapSectionParser extends SectionParser {
private final String startRegExp;
private final String sizeRegExp;
public MapSectionParser(String[] mapFileData, String startRegExp, String sizeRegExp) {
super(mapFileData);
this.startRegExp = startRegExp;
this.sizeRegExp = sizeRegExp;
}
@Override
protected void parseStartAddr() {
if (startRegExp == null || startRegExp.equals("")) {
startAddr = -1;
return;
}
startAddr = parseFirstHexInt(startRegExp, getData());
}
@Override
protected void parseSize() {
if (sizeRegExp == null || sizeRegExp.equals("")) {
size = -1;
return;
}
size = parseFirstHexInt(sizeRegExp, getData());
}
@Override
public Map<String, Symbol> parseSymbols(long offset) {
Map<String, Symbol> varNames = new HashMap<>();
Pattern pattern = Pattern.compile(Cooja.getExternalToolsSetting("MAPFILE_VAR_NAME"));
for (String line : getData()) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
if (Integer.decode(matcher.group(1)).intValue() >= getStartAddr()
&& Integer.decode(matcher.group(1)).intValue() <= getStartAddr() + getSize()) {
String varName = matcher.group(2);
varNames.put(varName, new Symbol(
Symbol.Type.VARIABLE,
varName,
getMapFileVarAddress(getData(), varName) + offset,
getMapFileVarSize(getData(), varName)));
}
}
}
return varNames;
}
/**
* Get relative address of variable with given name.
*
* @param varName Name of variable
* @return Relative memory address of variable or -1 if not found
*/
private int getMapFileVarAddress(String[] mapFileData, String varName) {
String regExp = Cooja.getExternalToolsSetting("MAPFILE_VAR_ADDRESS_1")
+ varName
+ Cooja.getExternalToolsSetting("MAPFILE_VAR_ADDRESS_2");
String retString = getFirstMatchGroup(mapFileData, regExp, 1);
if (retString != null) {
return Integer.parseInt(retString.trim(), 16);
} else {
return -1;
}
}
private int getMapFileVarSize(String[] mapFileData, String varName) {
Pattern pattern = Pattern.compile(
Cooja.getExternalToolsSetting("MAPFILE_VAR_SIZE_1")
+ varName
+ Cooja.getExternalToolsSetting("MAPFILE_VAR_SIZE_2"));
for (int idx = 0; idx < mapFileData.length; idx++) {
String parseString = mapFileData[idx];
Matcher matcher = pattern.matcher(parseString);
if (matcher.find()) {
return Integer.decode(matcher.group(1));
}
// second approach with lines joined
if (idx < mapFileData.length - 1) {
parseString += mapFileData[idx + 1];
}
matcher = pattern.matcher(parseString);
if (matcher.find()) {
return Integer.decode(matcher.group(1));
}
}
return -1;
}
}
/**
* Parses command output for section data.
*/
public static class CommandSectionParser extends SectionParser {
private final String startRegExp;
private final String endRegExp;
private final String sectionRegExp;
/**
* Creates SectionParser based on output of configurable command.
*
* @param mapFileData Map file lines as array of String
* @param startRegExp Regular expression for parsing start of section
* @param endRegExp Regular expression for parsing end of section
* @param sectionRegExp Reqular expression describing symbol table section identifier (e.g. '[Rr]' for readonly)
* Will be used to replaced '<SECTION>'in 'COMMAND_VAR_NAME_ADDRESS_SIZE'
*/
public CommandSectionParser(String[] mapFileData, String startRegExp, String endRegExp, String sectionRegExp) {
super(mapFileData);
this.startRegExp = startRegExp;
this.endRegExp = endRegExp;
this.sectionRegExp = sectionRegExp;
}
@Override
protected void parseStartAddr() {
if (startRegExp == null || startRegExp.equals("")) {
startAddr = -1;
return;
}
startAddr = parseFirstHexInt(startRegExp, getData());
}
@Override
public void parseSize() {
if (endRegExp == null || endRegExp.equals("")) {
size = -1;
return;
}
if (getStartAddr() < 0) {
size = -1;
return;
}
int end = parseFirstHexInt(endRegExp, getData());
if (end < 0) {
size = -1;
return;
}
size = end - getStartAddr();
}
@Override
public Map<String, Symbol> parseSymbols(long offset) {
HashMap<String, Symbol> addresses = new HashMap<>();
/* Replace "<SECTION>" in regexp by section specific regex */
Pattern pattern = Pattern.compile(
Cooja.getExternalToolsSetting("COMMAND_VAR_NAME_ADDRESS_SIZE")
.replace("<SECTION>", sectionRegExp));
for (String line : getData()) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
/* Line matched variable address */
String symbol = matcher.group(1);
long varAddr = Integer.parseInt(matcher.group(2), 16) + offset;
int varSize;
if (matcher.group(3) != null) {
varSize = Integer.parseInt(matcher.group(3), 16);
} else {
varSize = -1;
}
/* XXX needs to be checked */
if (!addresses.containsKey(symbol)) {
addresses.put(symbol, new Symbol(Symbol.Type.VARIABLE, symbol, varAddr, varSize));
} else {
int oldAddress = (int) addresses.get(symbol).addr;
if (oldAddress != varAddr) {
/*logger.warn("Warning, command response not matching previous entry of: "
+ varName);*/
}
}
}
}
return addresses;
}
}
/**
* Ticks the currently loaded mote. This should not be used directly, but
* rather via {@link ContikiMote#execute(long)}.
*/
public void tick() {
myCoreComm.tick();
}
/**
* Creates and returns a copy of this mote type's initial memory (just after
* the init function has been run). When a new mote is created it should get
* it's memory from here.
*
* @return Initial memory of a mote type
*/
public SectionMoteMemory createInitialMemory() {
return initialMemory.clone();
}
/**
* Copy core memory to given memory. This should not be used directly, but
* instead via ContikiMote.getMemory().
*
* @param mem
* Memory to set
*/
public void getCoreMemory(SectionMoteMemory mem) {
for (MemoryInterface section : mem.getSections().values()) {
getCoreMemory(
(int) (section.getStartAddr() - offset),
section.getTotalSize(),
section.getMemory());
}
}
private void getCoreMemory(int relAddr, int length, byte[] data) {
myCoreComm.getMemory(relAddr, length, data);
}
/**
* Copy given memory to the Contiki system. This should not be used directly,
* but instead via ContikiMote.setMemory().
*
* @param mem
* New memory
*/
public void setCoreMemory(SectionMoteMemory mem) {
for (MemoryInterface section : mem.getSections().values()) {
setCoreMemory(
(int) (section.getStartAddr() - offset),
section.getTotalSize(),
section.getMemory());
}
}
private void setCoreMemory(int relAddr, int length, byte[] mem) {
myCoreComm.setMemory(relAddr, length, mem);
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
@Override
public File getContikiSourceFile() {
return fileSource;
}
@Override
public void setContikiSourceFile(File file) {
fileSource = file;
}
@Override
public File getContikiFirmwareFile() {
return fileFirmware;
}
@Override
public void setContikiFirmwareFile(File file) {
fileFirmware = file;
}
@Override
public String getCompileCommands() {
return compileCommands;
}
@Override
public void setCompileCommands(String commands) {
this.compileCommands = commands;
}
/**
* @param symbols Core library has system symbols information
*/
public void setHasSystemSymbols(boolean symbols) {
hasSystemSymbols = symbols;
}
/**
* @return Whether core library has system symbols information
*/
public boolean hasSystemSymbols() {
return hasSystemSymbols;
}
/**
* @param netStack Contiki network stack
*/
public void setNetworkStack(NetworkStack netStack) {
this.netStack = netStack;
}
/**
* @return Contiki network stack
*/
public NetworkStack getNetworkStack() {
return netStack;
}
private static String getFirstMatchGroup(String[] lines, String regexp, int groupNr) {
if (regexp == null) {
return null;
}
Pattern pattern = Pattern.compile(regexp);
for (String line : lines) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return matcher.group(groupNr);
}
}
return null;
}
public static String[] loadMapFile(File mapFile) {
String contents = StringUtils.loadFromFile(mapFile);
if (contents == null) {
return null;
}
return contents.split("\n");
}
/**
* Executes configured command on given file and returns the result.
*
* @param libraryFile Contiki library
* @return Execution response, or null at failure
*/
public static String[] loadCommandData(File libraryFile) {
ArrayList<String> output = new ArrayList<>();
try {
String command = Cooja.getExternalToolsSetting("PARSE_COMMAND");
if (command == null) {
return null;
}
/* Prepare command */
command = command.replace("$(LIBFILE)",
libraryFile.getName().replace(File.separatorChar, '/'));
/* Execute command, read response */
String line;
Process p = Runtime.getRuntime().exec(
command.split(" "),
null,
libraryFile.getParentFile()
);
BufferedReader input = new BufferedReader(
new InputStreamReader(p.getInputStream())
);
p.getErrorStream().close();
while ((line = input.readLine()) != null) {
output.add(line);
}
input.close();
if (output == null || output.isEmpty()) {
return null;
}
return output.toArray(new String[0]);
} catch (IOException err) {
logger.fatal("Command error: " + err.getMessage(), err);
return null;
}
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String newDescription) {
description = newDescription;
}
@Override
public ProjectConfig getConfig() {
return myConfig;
}
/**
* Sets mote type project configuration. This may differ from the general
* simulator project configuration.
*
* @param moteTypeConfig
* Project configuration
*/
public void setConfig(ProjectConfig moteTypeConfig) {
myConfig = moteTypeConfig;
}
/**
* Returns all sensors of this mote type
*
* @return All sensors
*/
public String[] getSensors() {
return sensors;
}
/**
* Returns all core interfaces of this mote type
*
* @return All core interfaces
*/
public String[] getCoreInterfaces() {
return coreInterfaces;
}
/**
* Set core interfaces
*
* @param coreInterfaces
* New core interfaces
*/
public void setCoreInterfaces(String[] coreInterfaces) {
this.coreInterfaces = coreInterfaces;
}
@Override
public Class<? extends MoteInterface>[] getMoteInterfaceClasses() {
if (moteInterfacesClasses == null) {
return null;
}
Class<? extends MoteInterface>[] arr = new Class[moteInterfacesClasses.size()];
moteInterfacesClasses.toArray(arr);
return arr;
}
@Override
public void setMoteInterfaceClasses(Class<? extends MoteInterface>[] moteInterfaces) {
this.moteInterfacesClasses = new ArrayList<>();
this.moteInterfacesClasses.addAll(Arrays.asList(moteInterfaces));
}
/**
* Create a checksum of file. Used for checking if needed files are unchanged
* when loading a saved simulation.
*
* @param file
* File containg data to checksum
* @return Checksum
*/
protected byte[] createChecksum(File file) {
int bytesRead = 1;
byte[] readBytes = new byte[128];
MessageDigest messageDigest;
try {
InputStream fileInputStream = new FileInputStream(file);
messageDigest = MessageDigest.getInstance("MD5");
while (bytesRead > 0) {
bytesRead = fileInputStream.read(readBytes);
if (bytesRead > 0) {
messageDigest.update(readBytes, 0, bytesRead);
}
}
fileInputStream.close();
} catch (NoSuchAlgorithmException | IOException e) {
return null;
}
return messageDigest.digest();
}
/**
* Generates a unique Cooja mote type ID.
*
* @param existingTypes Already existing mote types, may be null
* @param reservedIdentifiers Already reserved identifiers, may be null
* @return Unique mote type ID.
*/
public static String generateUniqueMoteTypeID(MoteType[] existingTypes, Collection reservedIdentifiers) {
String testID = "";
boolean okID = false;
while (!okID) {
testID = ID_PREFIX + (new Random().nextInt(1000));
okID = true;
// Check if identifier is reserved
if (reservedIdentifiers != null && reservedIdentifiers.contains(testID)) {
okID = false;
}
if (!okID) {
continue;
}
// Check if identifier is used
if (existingTypes != null) {
for (MoteType existingMoteType : existingTypes) {
if (existingMoteType.getIdentifier().equals(testID)) {
okID = false;
break;
}
}
}
if (!okID) {
continue;
}
// Check if identifier library has been loaded
/* XXX Currently only checks the build directory! */
File libraryFile = new File(
ContikiMoteType.tempOutputDirectory,
testID + ContikiMoteType.librarySuffix);
if (libraryFile.exists() || CoreComm.hasLibraryFileBeenLoaded(libraryFile)) {
okID = false;
}
}
return testID;
}
/**
* Returns a panel with interesting data for this mote type.
*
* @return Mote type visualizer
*/
@Override
public JComponent getTypeVisualizer() {
StringBuilder sb = new StringBuilder();
// Identifier
sb.append("<html><table><tr><td>Identifier</td><td>")
.append(getIdentifier()).append("</td></tr>");
// Description
sb.append("<tr><td>Description</td><td>")
.append(getDescription()).append("</td></tr>");
/* Contiki application */
sb.append("<tr><td>Contiki application</td><td>")
.append(getContikiSourceFile().getAbsolutePath()).append("</td></tr>");
/* Contiki firmware */
sb.append("<tr><td>Contiki firmware</td><td>")
.append(getContikiFirmwareFile().getAbsolutePath()).append("</td></tr>");
/* JNI class */
sb.append("<tr><td>JNI library</td><td>")
.append(this.javaClassName).append("</td></tr>");
/* Contiki sensors */
sb.append("<tr><td valign=\"top\">Contiki sensors</td><td>");
for (String sensor : sensors) {
sb.append(sensor).append("<br>");
}
sb.append("</td></tr>");
/* Mote interfaces */
sb.append("<tr><td valign=\"top\">Mote interface</td><td>");
for (Class<? extends MoteInterface> moteInterface : moteInterfacesClasses) {
sb.append(moteInterface.getSimpleName()).append("<br>");
}
sb.append("</td></tr>");
/* Contiki core mote interfaces */
sb.append("<tr><td valign=\"top\">Contiki's mote interface</td><td>");
for (String coreInterface : getCoreInterfaces()) {
sb.append(coreInterface).append("<br>");
}
sb.append("</td></tr>");
JLabel label = new JLabel(sb.append("</table></html>").toString());
label.setVerticalTextPosition(JLabel.TOP);
return label;
}
@Override
public Collection<Element> getConfigXML(Simulation simulation) {
ArrayList<Element> config = new ArrayList<>();
Element element;
element = new Element("identifier");
element.setText(getIdentifier());
config.add(element);
element = new Element("description");
element.setText(getDescription());
config.add(element);
element = new Element("source");
File file = simulation.getCooja().createPortablePath(getContikiSourceFile());
element.setText(file.getPath().replaceAll("\\\\", "/"));
config.add(element);
element = new Element("commands");
element.setText(compileCommands);
config.add(element);
for (Class<? extends MoteInterface> moteInterface : getMoteInterfaceClasses()) {
element = new Element("moteinterface");
element.setText(moteInterface.getName());
config.add(element);
}
element = new Element("symbols");
element.setText(new Boolean(hasSystemSymbols()).toString());
config.add(element);
if (getNetworkStack() != NetworkStack.DEFAULT) {
element = new Element("netstack");
element.setText(getNetworkStack().getConfig());
config.add(element);
}
return config;
}
@Override
public boolean setConfigXML(Simulation simulation,
Collection<Element> configXML, boolean visAvailable)
throws MoteTypeCreationException {
boolean warnedOldVersion = false;
File oldVersionSource = null;
moteInterfacesClasses = new ArrayList<Class<? extends MoteInterface>>();
for (Element element : configXML) {
String name = element.getName();
switch (name) {
case "identifier":
identifier = element.getText();
break;
case "description":
description = element.getText();
break;
case "contikiapp":
case "source":
File file = new File(element.getText());
if (!file.exists()) {
file = simulation.getCooja().restorePortablePath(file);
} setContikiSourceFile(file);
/* XXX Do not load the generated firmware. Instead, load the unique library file directly */
File contikiFirmware = new File(
getContikiSourceFile().getParentFile(),
"obj_cooja/" + getIdentifier() + librarySuffix);
setContikiFirmwareFile(contikiFirmware);
break;
case "commands":
compileCommands = element.getText();
break;
case "symbols":
hasSystemSymbols = Boolean.parseBoolean(element.getText());
break;
case "commstack":
logger.warn("The Cooja communication stack config was removed: " + element.getText());
logger.warn("Instead assuming default network stack.");
netStack = NetworkStack.DEFAULT;
break;
case "netstack":
netStack = NetworkStack.parseConfig(element.getText());
break;
case "moteinterface":
String intfClass = element.getText().trim();
/* Backwards compatibility: se.sics -> org.contikios */
if (intfClass.startsWith("se.sics")) {
intfClass = intfClass.replaceFirst("se\\.sics", "org.contikios");
} Class<? extends MoteInterface> moteInterfaceClass
= simulation.getCooja().tryLoadClass(
this, MoteInterface.class, intfClass);
if (moteInterfaceClass == null) {
logger.warn("Can't find mote interface class: " + intfClass);
} else {
moteInterfacesClasses.add(moteInterfaceClass);
} break;
case "contikibasedir":
case "contikicoredir":
case "projectdir":
case "compilefile":
case "process":
case "sensor":
case "coreinterface":
/* Backwards compatibility: old cooja mote type is being loaded */
if (!warnedOldVersion) {
warnedOldVersion = true;
logger.warn("Old simulation config detected: Cooja mote types may not load correctly");
} if (name.equals("compilefile")) {
if (element.getText().endsWith(".c")) {
File potentialFile = new File(element.getText());
if (potentialFile.exists()) {
oldVersionSource = potentialFile;
}
}
} break;
default:
logger.fatal("Unrecognized entry in loaded configuration: " + name);
break;
}
}
/* Create initial core interface dependencies */
Class<? extends MoteInterface>[] arr
= new Class[moteInterfacesClasses.size()];
moteInterfacesClasses.toArray(arr);
setCoreInterfaces(ContikiMoteType.getRequiredCoreInterfaces(arr));
/* Backwards compatibility: old cooja mote type is being loaded */
if (getContikiSourceFile() == null
&& warnedOldVersion
&& oldVersionSource != null) {
/* Guess Contiki source */
setContikiSourceFile(oldVersionSource);
logger.info("Guessing Contiki source: " + oldVersionSource.getAbsolutePath());
setContikiFirmwareFile(getExpectedFirmwareFile(oldVersionSource));
logger.info("Guessing Contiki firmware: " + getContikiFirmwareFile().getAbsolutePath());
/* Guess compile commands */
String compileCommands
= "make " + getExpectedFirmwareFile(oldVersionSource).getName() + " TARGET=cooja";
logger.info("Guessing compile commands: " + compileCommands);
setCompileCommands(compileCommands);
}
boolean createdOK = configureAndInit(Cooja.getTopParentContainer(), simulation, visAvailable);
return createdOK;
}
public static String[] getRequiredCoreInterfaces(
Class<? extends MoteInterface>[] moteInterfaces) {
/* Extract Contiki dependencies from currently selected mote interfaces */
ArrayList<String> coreInterfacesList = new ArrayList<>();
for (Class<? extends MoteInterface> intf : moteInterfaces) {
if (!ContikiMoteInterface.class.isAssignableFrom(intf)) {
continue;
}
String[] deps;
try {
/* Call static method */
Method m = intf.getDeclaredMethod("getCoreInterfaceDependencies", (Class[]) null);
deps = (String[]) m.invoke(null, (Object[]) null);
} catch (Exception e) {
logger.warn("Could not extract Contiki dependencies of mote interface: " + intf.getName());
e.printStackTrace();
continue;
}
if (deps == null || deps.length == 0) {
continue;
}
coreInterfacesList.addAll(Arrays.asList(deps));
}
String[] coreInterfaces = new String[coreInterfacesList.size()];
coreInterfacesList.toArray(coreInterfaces);
return coreInterfaces;
}
}