mirror of https://github.com/Qortal/AT
Browse Source
Note that this is unfinished, requiring fee-per-opcode support and some refactoring.master
catbref
6 years ago
36 changed files with 6657 additions and 0 deletions
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<classpath> |
||||
<classpathentry kind="src" output="target/classes" path="src"> |
||||
<attributes> |
||||
<attribute name="optional" value="true"/> |
||||
<attribute name="maven.pomderived" value="true"/> |
||||
</attributes> |
||||
</classpathentry> |
||||
<classpathentry kind="src" output="target/test-classes" path="tests"> |
||||
<attributes> |
||||
<attribute name="optional" value="true"/> |
||||
<attribute name="maven.pomderived" value="true"/> |
||||
</attributes> |
||||
</classpathentry> |
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> |
||||
<attributes> |
||||
<attribute name="maven.pomderived" value="true"/> |
||||
</attributes> |
||||
</classpathentry> |
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> |
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> |
||||
<attributes> |
||||
<attribute name="maven.pomderived" value="true"/> |
||||
</attributes> |
||||
</classpathentry> |
||||
<classpathentry kind="output" path="target/classes"/> |
||||
</classpath> |
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<projectDescription> |
||||
<name>CIYAM-AT-Java</name> |
||||
<comment></comment> |
||||
<projects> |
||||
</projects> |
||||
<buildSpec> |
||||
<buildCommand> |
||||
<name>org.eclipse.jdt.core.javabuilder</name> |
||||
<arguments> |
||||
</arguments> |
||||
</buildCommand> |
||||
<buildCommand> |
||||
<name>org.eclipse.m2e.core.maven2Builder</name> |
||||
<arguments> |
||||
</arguments> |
||||
</buildCommand> |
||||
</buildSpec> |
||||
<natures> |
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature> |
||||
<nature>org.eclipse.jdt.core.javanature</nature> |
||||
</natures> |
||||
</projectDescription> |
@ -0,0 +1,28 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<groupId>CIYAM-AT-Java</groupId> |
||||
<artifactId>CIYAM-AT-Java</artifactId> |
||||
<version>1.0</version> |
||||
<build> |
||||
<sourceDirectory>src</sourceDirectory> |
||||
<testSourceDirectory>tests</testSourceDirectory> |
||||
<plugins> |
||||
<plugin> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<version>3.5.1</version> |
||||
<configuration> |
||||
<source>1.8</source> |
||||
<target>1.8</target> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.bouncycastle</groupId> |
||||
<artifactId>bcprov-jdk15on</artifactId> |
||||
<version>1.60</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -0,0 +1,100 @@
|
||||
package org.ciyam.at; |
||||
|
||||
/** |
||||
* API for CIYAM AT "Function Codes" for blockchain-specific interactions. |
||||
* <p> |
||||
* For more information, see the specification document at:<br> |
||||
* <a href="http://ciyam.org/at/at_api.html">Automated Transactions API Specification</a> |
||||
* <p> |
||||
* Note that "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes two parts. The first part is a block height (32 |
||||
* bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable). |
||||
* |
||||
*/ |
||||
public interface API { |
||||
|
||||
/** Returns current blockchain's height */ |
||||
public int getCurrentBlockHeight(); |
||||
|
||||
/** Returns block height where AT was created */ |
||||
public int getATCreationBlockHeight(MachineState state); |
||||
|
||||
/** Returns previous block's height */ |
||||
default public int getPreviousBlockHeight() { |
||||
return getCurrentBlockHeight() - 1; |
||||
} |
||||
|
||||
/** Put previous block's signature hash in A */ |
||||
public void putPreviousBlockHashInA(MachineState state); |
||||
|
||||
/** Put next transaction to AT after timestamp in A */ |
||||
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state); |
||||
|
||||
/** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */ |
||||
public long getTypeFromTransactionInA(MachineState state); |
||||
|
||||
/** Return amount from transaction in A, after transaction fees have been deducted, or 0xffffffffffffffff if A not valid transaction */ |
||||
public long getAmountFromTransactionInA(MachineState state); |
||||
|
||||
/** Return timestamp from transaction in A, or 0xffffffffffffffff if A not valid transaction */ |
||||
public long getTimestampFromTransactionInA(MachineState state); |
||||
|
||||
/** |
||||
* Generate pseudo-random number using transaction in A. |
||||
* <p> |
||||
* AT should sleep so it can use next block as source of entropy. |
||||
* <p> |
||||
* Set <tt>state.isSleeping = true</tt> before exit on first call.<br> |
||||
* <tt>state.steps</tt> will be zero on second call after wake-up. |
||||
* <p> |
||||
* Returns 0xffffffffffffffff if A not valid transaction. |
||||
*/ |
||||
public long generateRandomUsingTransactionInA(MachineState state); |
||||
|
||||
/** Put 'message' from transaction in A into B */ |
||||
public void putMessageFromTransactionInAIntoB(MachineState state); |
||||
|
||||
/** Put sender/creator address from transaction in A into B */ |
||||
public void putAddressFromTransactionInAIntoB(MachineState state); |
||||
|
||||
/** Put AT's creator's address into B */ |
||||
public void putCreatorAddressIntoB(MachineState state); |
||||
|
||||
/** Return AT's current balance */ |
||||
public long getCurrentBalance(MachineState state); |
||||
|
||||
/** Return AT's previous balance at end of last execution round. Does not include any amounts sent to AT since */ |
||||
public long getPreviousBalance(MachineState state); |
||||
|
||||
/** Pay passed amount, or current balance if necessary, (fee inclusive) to address in B */ |
||||
public void payAmountToB(long value1, MachineState state); |
||||
|
||||
/** Pay AT's current balance to address in B */ |
||||
public void payCurrentBalanceToB(MachineState state); |
||||
|
||||
/** Pay AT's previous balance to address in B */ |
||||
public void payPreviousBalanceToB(MachineState state); |
||||
|
||||
/** Send 'message' in A to address in B */ |
||||
public void messageAToB(MachineState state); |
||||
|
||||
/** |
||||
* Returns <tt>minutes</tt> of blocks added to 'timestamp' |
||||
* <p> |
||||
* <tt>minutes</tt> is converted to rough number of blocks and added to 'timestamp' to create return value. |
||||
*/ |
||||
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state); |
||||
|
||||
/** AT has encountered fatal error. Return remaining funds to creator */ |
||||
public void onFatalError(MachineState state, ExecutionException e); |
||||
|
||||
/** Pre-execute checking of param requirements for platform-specific functions */ |
||||
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException; |
||||
|
||||
/** |
||||
* Platform-specific function execution |
||||
* |
||||
* @throws ExecutionException |
||||
*/ |
||||
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException; |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class CodeSegmentException extends ExecutionException { |
||||
|
||||
public CodeSegmentException() { |
||||
} |
||||
|
||||
public CodeSegmentException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public CodeSegmentException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public CodeSegmentException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class ExecutionException extends Exception { |
||||
|
||||
public ExecutionException() { |
||||
} |
||||
|
||||
public ExecutionException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public ExecutionException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public ExecutionException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,956 @@
|
||||
package org.ciyam.at; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.util.Arrays; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* This enum contains function codes for the CIYAM AT machine. |
||||
* <p> |
||||
* Function codes are represented by a short. Functions can take 0 to 2 additional long values and optionally return a value too. |
||||
* <p> |
||||
* FunctionCode instances can be obtained via the default <tt>FunctionCode.valueOf(String)</tt> or the additional <tt>FunctionCode.valueOf(int)</tt>. |
||||
* <p> |
||||
* Use the <tt>FunctionCode.execute</tt> method to perform the operation. |
||||
* <p> |
||||
* For more details, view the <a href="http://ciyam.org/at/at_api.html">API Specification</a>. |
||||
* |
||||
* @see FunctionCode#valueOf(int) |
||||
* @see FunctionCode#execute(FunctionData, MachineState) |
||||
*/ |
||||
public enum FunctionCode { |
||||
/** |
||||
* <b>ECHO</b> value to logger<br> |
||||
* <tt>0x0001 value</tt> |
||||
*/ |
||||
ECHO(0x0001, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
String message = String.valueOf(functionData.value1); |
||||
state.logger.echo(message); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0100</tt><br> |
||||
* Returns A1 value |
||||
*/ |
||||
GET_A1(0x0100, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.a1; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0101</tt><br> |
||||
* Returns A2 value |
||||
*/ |
||||
GET_A2(0x0101, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.a2; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0102</tt><br> |
||||
* Returns A3 value |
||||
*/ |
||||
GET_A3(0x0102, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.a3; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0103</tt><br> |
||||
* Returns A4 value |
||||
*/ |
||||
GET_A4(0x0103, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.a4; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0104</tt><br> |
||||
* Returns B1 value |
||||
*/ |
||||
GET_B1(0x0104, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.b1; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0105</tt><br> |
||||
* Returns B2 value |
||||
*/ |
||||
GET_B2(0x0105, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.b2; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0106</tt><br> |
||||
* Returns B3 value |
||||
*/ |
||||
GET_B3(0x0106, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.b3; |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0107</tt><br> |
||||
* Returns B4 value |
||||
*/ |
||||
GET_B4(0x0107, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A1<br> |
||||
* <tt>0x0110 value</tt> |
||||
*/ |
||||
SET_A1(0x0110, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A2<br> |
||||
* <tt>0x0111 value</tt> |
||||
*/ |
||||
SET_A2(0x0111, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a2 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A3<br> |
||||
* <tt>0x0112 value</tt> |
||||
*/ |
||||
SET_A3(0x0112, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a3 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A4<br> |
||||
* <tt>0x0113 value</tt> |
||||
*/ |
||||
SET_A4(0x0113, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a4 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A1 and A2<br> |
||||
* <tt>0x0114 value value</tt> |
||||
*/ |
||||
SET_A1_A2(0x0114, 2, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = functionData.value1; |
||||
state.a2 = functionData.value2; |
||||
} |
||||
}, |
||||
/** |
||||
* Set A3 and A4<br> |
||||
* <tt>0x0115 value value</tt> |
||||
*/ |
||||
SET_A3_A4(0x0115, 2, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a3 = functionData.value1; |
||||
state.a4 = functionData.value2; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B1<br> |
||||
* <tt>0x0116 value</tt> |
||||
*/ |
||||
SET_B1(0x0116, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B2<br> |
||||
* <tt>0x0117 value</tt> |
||||
*/ |
||||
SET_B2(0x0117, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b2 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B3<br> |
||||
* <tt>0x0118 value</tt> |
||||
*/ |
||||
SET_B3(0x0118, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b3 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B4<br> |
||||
* <tt>0x0119 value</tt> |
||||
*/ |
||||
SET_B4(0x0119, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b4 = functionData.value1; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B1 and B2<br> |
||||
* <tt>0x011a value value</tt> |
||||
*/ |
||||
SET_B1_B2(0x011a, 2, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = functionData.value1; |
||||
state.b2 = functionData.value2; |
||||
} |
||||
}, |
||||
/** |
||||
* Set B3 and B4<br> |
||||
* <tt>0x011b value value</tt> |
||||
*/ |
||||
SET_B3_B4(0x011b, 2, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b3 = functionData.value1; |
||||
state.b4 = functionData.value2; |
||||
} |
||||
}, |
||||
/** |
||||
* Clear A<br> |
||||
* <tt>0x0120</tt> |
||||
*/ |
||||
CLEAR_A(0x0120, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = 0; |
||||
state.a2 = 0; |
||||
state.a3 = 0; |
||||
state.a4 = 0; |
||||
} |
||||
}, |
||||
/** |
||||
* Clear B<br> |
||||
* <tt>0x0121</tt> |
||||
*/ |
||||
CLEAR_B(0x0121, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = 0; |
||||
state.b2 = 0; |
||||
state.b3 = 0; |
||||
state.b4 = 0; |
||||
} |
||||
}, |
||||
/** |
||||
* Clear A and B<br> |
||||
* <tt>0x0122</tt> |
||||
*/ |
||||
CLEAR_A_AND_B(0x0122, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = 0; |
||||
state.a2 = 0; |
||||
state.a3 = 0; |
||||
state.a4 = 0; |
||||
state.b1 = 0; |
||||
state.b2 = 0; |
||||
state.b3 = 0; |
||||
state.b4 = 0; |
||||
} |
||||
}, |
||||
/** |
||||
* Copy A from B<br> |
||||
* <tt>0x0123</tt> |
||||
*/ |
||||
COPY_A_FROM_B(0x0123, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = state.b1; |
||||
state.a2 = state.b2; |
||||
state.a3 = state.b3; |
||||
state.a4 = state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Copy B from A<br> |
||||
* <tt>0x0124</tt> |
||||
*/ |
||||
COPY_B_FROM_A(0x0124, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = state.a1; |
||||
state.b2 = state.a2; |
||||
state.b3 = state.a3; |
||||
state.b4 = state.a4; |
||||
} |
||||
}, |
||||
/** |
||||
* Check A is zero<br> |
||||
* <tt>0x0125</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_A_IS_ZERO(0x0125, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
if (state.a1 == 0 && state.a2 == 0 && state.a3 == 0 && state.a4 == 0) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} |
||||
}, |
||||
/** |
||||
* Check B is zero<br> |
||||
* <tt>0x0126</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_B_IS_ZERO(0x0126, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
if (state.b1 == 0 && state.b2 == 0 && state.b3 == 0 && state.b4 == 0) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} |
||||
}, |
||||
/** |
||||
* Check A equals B<br> |
||||
* <tt>0x0127</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_A_EQUALS_B(0x0127, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
if (state.a1 == state.b1 && state.a2 == state.b2 && state.a3 == state.b3 && state.a4 == state.b4) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} |
||||
}, |
||||
/** |
||||
* Swap A with B<br> |
||||
* <tt>0x0128</tt> |
||||
*/ |
||||
SWAP_A_AND_B(0x0128, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
long tmp1 = state.a1; |
||||
long tmp2 = state.a2; |
||||
long tmp3 = state.a3; |
||||
long tmp4 = state.a4; |
||||
|
||||
state.a1 = state.b1; |
||||
state.a2 = state.b2; |
||||
state.a3 = state.b3; |
||||
state.a4 = state.b4; |
||||
|
||||
state.b1 = tmp1; |
||||
state.b2 = tmp2; |
||||
state.b3 = tmp3; |
||||
state.b4 = tmp4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-OR A with B<br> |
||||
* <tt>0x0129</tt> |
||||
*/ |
||||
OR_A_WITH_B(0x0129, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = state.a1 | state.b1; |
||||
state.a2 = state.a2 | state.b2; |
||||
state.a3 = state.a3 | state.b3; |
||||
state.a4 = state.a4 | state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-OR B with A<br> |
||||
* <tt>0x012a</tt> |
||||
*/ |
||||
OR_B_WITH_A(0x012a, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = state.a1 | state.b1; |
||||
state.b2 = state.a2 | state.b2; |
||||
state.b3 = state.a3 | state.b3; |
||||
state.b4 = state.a4 | state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-AND A with B<br> |
||||
* <tt>0x012b</tt> |
||||
*/ |
||||
AND_A_WITH_B(0x012b, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = state.a1 & state.b1; |
||||
state.a2 = state.a2 & state.b2; |
||||
state.a3 = state.a3 & state.b3; |
||||
state.a4 = state.a4 & state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-AND B with A<br> |
||||
* <tt>0x012c</tt> |
||||
*/ |
||||
AND_B_WITH_A(0x012c, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = state.a1 & state.b1; |
||||
state.b2 = state.a2 & state.b2; |
||||
state.b3 = state.a3 & state.b3; |
||||
state.b4 = state.a4 & state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-XOR A with B<br> |
||||
* <tt>0x012d</tt> |
||||
*/ |
||||
XOR_A_WITH_B(0x012d, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.a1 = state.a1 ^ state.b1; |
||||
state.a2 = state.a2 ^ state.b2; |
||||
state.a3 = state.a3 ^ state.b3; |
||||
state.a4 = state.a4 ^ state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* Bitwise-XOR B with A<br> |
||||
* <tt>0x012e</tt> |
||||
*/ |
||||
XOR_B_WITH_A(0x012e, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.b1 = state.a1 ^ state.b1; |
||||
state.b2 = state.a2 ^ state.b2; |
||||
state.b3 = state.a3 ^ state.b3; |
||||
state.b4 = state.a4 ^ state.b4; |
||||
} |
||||
}, |
||||
/** |
||||
* MD5 A into B<br> |
||||
* <tt>0x0200</tt> |
||||
*/ |
||||
MD5_A_TO_B(0x0200, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("MD5"); |
||||
byte[] digest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
state.b1 = digestByteBuffer.getLong(); |
||||
state.b2 = digestByteBuffer.getLong(); |
||||
state.b3 = 0L; // XXX Or do we leave B3 untouched?
|
||||
state.b4 = 0L; // XXX Or do we leave B4 untouched?
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No MD5 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* Check MD5 of A matches B<br> |
||||
* <tt>0x0201</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_MD5_A_WITH_B(0x0201, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("MD5"); |
||||
byte[] actualDigest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
digestByteBuffer.putLong(state.b1); |
||||
digestByteBuffer.putLong(state.b2); |
||||
|
||||
byte[] expectedDigest = digestByteBuffer.array(); |
||||
|
||||
if (Arrays.equals(actualDigest, expectedDigest)) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No MD5 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* HASH160 A into B<br> |
||||
* <tt>0x0202</tt> |
||||
*/ |
||||
HASH160_A_TO_B(0x0202, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
messageByteBuffer.putLong(state.a3); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("RIPEMD160"); |
||||
byte[] digest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
state.b1 = digestByteBuffer.getLong(); |
||||
state.b2 = digestByteBuffer.getLong(); |
||||
state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL; |
||||
state.b4 = 0L; // XXX Or do we leave B4 untouched?
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No RIPEMD160 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* Check HASH160 of A matches B<br> |
||||
* <tt>0x0203</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_HASH160_A_WITH_B(0x0203, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
messageByteBuffer.putLong(state.a3); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("RIPEMD160"); |
||||
byte[] actualDigest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.allocate(digester.getDigestLength()); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
digestByteBuffer.putLong(state.b1); |
||||
digestByteBuffer.putLong(state.b2); |
||||
digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL)); |
||||
// XXX: b4 ignored
|
||||
|
||||
byte[] expectedDigest = digestByteBuffer.array(); |
||||
|
||||
if (Arrays.equals(actualDigest, expectedDigest)) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No RIPEMD160 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* SHA256 A into B<br> |
||||
* <tt>0x0204</tt> |
||||
*/ |
||||
SHA256_A_TO_B(0x0204, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
messageByteBuffer.putLong(state.a3); |
||||
messageByteBuffer.putLong(state.a4); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("SHA-256"); |
||||
byte[] digest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
state.b1 = digestByteBuffer.getLong(); |
||||
state.b2 = digestByteBuffer.getLong(); |
||||
state.b3 = digestByteBuffer.getLong(); |
||||
state.b4 = digestByteBuffer.getLong(); |
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No SHA-256 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* Check SHA256 of A matches B<br> |
||||
* <tt>0x0205</tt><br> |
||||
* Returns 1 if true, 0 if false |
||||
*/ |
||||
CHECK_SHA256_A_WITH_B(0x0205, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE); |
||||
messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
messageByteBuffer.putLong(state.a1); |
||||
messageByteBuffer.putLong(state.a2); |
||||
messageByteBuffer.putLong(state.a3); |
||||
messageByteBuffer.putLong(state.a4); |
||||
|
||||
byte[] message = messageByteBuffer.array(); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("SHA-256"); |
||||
byte[] actualDigest = digester.digest(message); |
||||
|
||||
ByteBuffer digestByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE); |
||||
digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
digestByteBuffer.putLong(state.b1); |
||||
digestByteBuffer.putLong(state.b2); |
||||
digestByteBuffer.putLong(state.b3); |
||||
digestByteBuffer.putLong(state.b4); |
||||
|
||||
byte[] expectedDigest = digestByteBuffer.array(); |
||||
|
||||
if (Arrays.equals(actualDigest, expectedDigest)) |
||||
functionData.returnValue = 1L; // true
|
||||
else |
||||
functionData.returnValue = 0L; // false
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No SHA256 message digest service available", e); |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0300</tt><br> |
||||
* Returns current block's "timestamp" |
||||
*/ |
||||
GET_BLOCK_TIMESTAMP(0x0300, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = Timestamp.toLong(state.api.getCurrentBlockHeight(), 0); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0301</tt><br> |
||||
* Returns AT's creation block's "timestamp" |
||||
*/ |
||||
GET_CREATION_TIMESTAMP(0x0301, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = Timestamp.toLong(state.api.getATCreationBlockHeight(state), 0); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0302</tt><br> |
||||
* Returns previous block's "timestamp" |
||||
*/ |
||||
GET_PREVIOUS_BLOCK_TIMESTAMP(0x0302, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = Timestamp.toLong(state.api.getPreviousBlockHeight(), 0); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0303</tt><br> |
||||
* Put previous block's hash in A |
||||
*/ |
||||
PUT_PREVIOUS_BLOCK_HASH_IN_A(0x0303, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.putPreviousBlockHashInA(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0304</tt><br> |
||||
* Put transaction after timestamp in A, or zero if none<br> |
||||
* a-k-a "A_To_Tx_After_Timestamp" |
||||
*/ |
||||
PUT_TX_AFTER_TIMESTAMP_IN_A(0x0304, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0305</tt><br> |
||||
* Return transaction type from transaction in A<br> |
||||
* Returns 0xffffffffffffffff in A not valid transaction |
||||
*/ |
||||
GET_TYPE_FROM_TX_IN_A(0x0305, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.getTypeFromTransactionInA(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0306</tt><br> |
||||
* Return transaction amount from transaction in A<br> |
||||
* Returns 0xffffffffffffffff in A not valid transaction |
||||
*/ |
||||
GET_AMOUNT_FROM_TX_IN_A(0x0306, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.getAmountFromTransactionInA(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0307</tt><br> |
||||
* Return transaction timestamp from transaction in A<br> |
||||
* Returns 0xffffffffffffffff in A not valid transaction |
||||
*/ |
||||
GET_TIMESTAMP_FROM_TX_IN_A(0x0307, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.getTimestampFromTransactionInA(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0308</tt><br> |
||||
* Generate random number using transaction in A<br> |
||||
* Returns 0xffffffffffffffff in A not valid transaction<br> |
||||
* Can sleep to use next block as source of entropy |
||||
*/ |
||||
GENERATE_RANDOM_USING_TX_IN_A(0x0308, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.generateRandomUsingTransactionInA(state); |
||||
|
||||
// If API set isSleeping then rewind program counter ready for being awoken
|
||||
if (state.isSleeping) { |
||||
state.programCounter -= 1 + 2 + 4; // EXT_FUN_RET(1) + our function code(2) + address(4)
|
||||
|
||||
// If specific sleep height not set, default to next block
|
||||
if (state.sleepUntilHeight == null) |
||||
state.sleepUntilHeight = state.currentBlockHeight + 1; |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0309</tt><br> |
||||
* Put 'message' from transaction in A into B<br> |
||||
* If transaction has no 'message' then zero B<br> |
||||
* Example 'message' could be 256-bit shared secret |
||||
*/ |
||||
PUT_MESSAGE_FROM_TX_IN_A_INTO_B(0x0309, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.putMessageFromTransactionInAIntoB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x030a</tt><br> |
||||
* Put sender/creator address from transaction in A into B |
||||
*/ |
||||
PUT_ADDRESS_FROM_TX_IN_A_INTO_B(0x030a, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.putAddressFromTransactionInAIntoB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x030b</tt><br> |
||||
* Put AT's creator's address into B |
||||
*/ |
||||
PUT_CREATOR_INTO_B(0x030b, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.putCreatorAddressIntoB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0400</tt><br> |
||||
* Returns AT's current balance |
||||
*/ |
||||
GET_CURRENT_BALANCE(0x0400, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.getCurrentBalance(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0401</tt><br> |
||||
* Returns AT's previous balance at end of last execution round<br> |
||||
* Does not include any amounts sent to AT since |
||||
*/ |
||||
GET_PREVIOUS_BALANCE(0x0401, 0, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.getPreviousBalance(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0402</tt><br> |
||||
* Pay fee-inclusive amount to account address in B<br> |
||||
* Reduces amount to current balance rather than failing due to insufficient funds |
||||
*/ |
||||
PAY_TO_ADDRESS_IN_B(0x0402, 1, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.payAmountToB(functionData.value1, state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0403</tt><br> |
||||
* Pay all remaining funds to account address in B |
||||
*/ |
||||
PAY_ALL_TO_ADDRESS_IN_B(0x0403, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.payCurrentBalanceToB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0404</tt><br> |
||||
* Pay previous balance to account address in B<br> |
||||
* Reduces amount to current balance rather than failing due to insufficient funds |
||||
*/ |
||||
PAY_PREVIOUS_TO_ADDRESS_IN_B(0x0404, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.payPreviousBalanceToB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0405</tt><br> |
||||
* Send A as a message to address in B |
||||
*/ |
||||
MESSAGE_A_TO_ADDRESS_IN_B(0x0405, 0, false) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.messageAToB(state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0406</tt><br> |
||||
* Return 'timestamp' based on passed 'timestamp' plus minutes |
||||
*/ |
||||
ADD_MINUTES_TO_TIMESTAMP(0x0406, 2, true) { |
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
functionData.returnValue = state.api.addMinutesToTimestamp(new Timestamp(functionData.value1), functionData.value2, state); |
||||
} |
||||
}, |
||||
/** |
||||
* <tt>0x0500 - 0x06ff</tt><br> |
||||
* Platform-specific functions.<br> |
||||
* These are passed through to the API |
||||
*/ |
||||
API_PASSTHROUGH(0x0500, 0, false) { |
||||
@Override |
||||
public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected); |
||||
} |
||||
|
||||
@Override |
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
state.api.platformSpecificPostCheckExecute(rawFunctionCode, functionData, state); |
||||
} |
||||
}; |
||||
|
||||
public final short value; |
||||
public final int paramCount; |
||||
public final boolean returnsValue; |
||||
|
||||
private final static Map<Short, FunctionCode> map = Arrays.stream(FunctionCode.values()) |
||||
.collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode)); |
||||
|
||||
private FunctionCode(int value, int paramCount, boolean returnsValue) { |
||||
this.value = (short) value; |
||||
this.paramCount = paramCount; |
||||
this.returnsValue = returnsValue; |
||||
} |
||||
|
||||
public static FunctionCode valueOf(int value) { |
||||
// Platform-specific?
|
||||
if (value >= 0x0500 && value <= 0x06ff) |
||||
return API_PASSTHROUGH; |
||||
|
||||
return map.get((short) value); |
||||
} |
||||
|
||||
public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
if (paramCount != this.paramCount) |
||||
throw new IllegalFunctionCodeException( |
||||
"Passed paramCount (" + paramCount + ") does not match function's required paramCount (" + this.paramCount + ")"); |
||||
|
||||
if (returnValueExpected != this.returnsValue) |
||||
throw new IllegalFunctionCodeException( |
||||
"Passed returnValueExpected (" + returnValueExpected + ") does not match function's return signature (" + this.returnsValue + ")"); |
||||
} |
||||
|
||||
/** |
||||
* Execute Function |
||||
* <p> |
||||
* Can modify various fields of <tt>state</tt>, including <tt>programCounter</tt>. |
||||
* <p> |
||||
* Throws a subclass of <tt>ExecutionException</tt> on error, e.g. <tt>InvalidAddressException</tt>. |
||||
* |
||||
* @param functionData |
||||
* @param state |
||||
* @throws ExecutionException |
||||
*/ |
||||
public void execute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { |
||||
// Check passed functionData against requirements of this function
|
||||
preExecuteCheck(functionData.paramCount, functionData.returnValueExpected, state, rawFunctionCode); |
||||
|
||||
if (functionData.paramCount >= 1 && functionData.value1 == null) |
||||
throw new IllegalFunctionCodeException("Passed value1 is null but function has paramCount of (" + this.paramCount + ")"); |
||||
|
||||
if (functionData.paramCount == 2 && functionData.value2 == null) |
||||
throw new IllegalFunctionCodeException("Passed value2 is null but function has paramCount of (" + this.paramCount + ")"); |
||||
|
||||
state.logger.debug("Function \"" + this.name() + "\""); |
||||
|
||||
postCheckExecute(functionData, state, rawFunctionCode); |
||||
} |
||||
|
||||
/** Actually execute function */ |
||||
abstract protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; |
||||
|
||||
// TODO: public abstract String disassemble();
|
||||
|
||||
} |
@ -0,0 +1,29 @@
|
||||
package org.ciyam.at; |
||||
|
||||
public class FunctionData { |
||||
public final int paramCount; |
||||
public final Long value1; |
||||
public final Long value2; |
||||
public final boolean returnValueExpected; |
||||
public Long returnValue; |
||||
|
||||
private FunctionData(int paramCount, Long value1, Long value2, boolean returnValueExpected) { |
||||
this.paramCount = paramCount; |
||||
this.value1 = value1; |
||||
this.value2 = value2; |
||||
this.returnValueExpected = returnValueExpected; |
||||
this.returnValue = null; |
||||
} |
||||
|
||||
public FunctionData(boolean returnValueExpected) { |
||||
this(0, null, null, returnValueExpected); |
||||
} |
||||
|
||||
public FunctionData(Long value, boolean returnValueExpected) { |
||||
this(1, value, null, returnValueExpected); |
||||
} |
||||
|
||||
public FunctionData(Long value1, Long value2, boolean returnValueExpected) { |
||||
this(2, value1, value2, returnValueExpected); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class IllegalFunctionCodeException extends ExecutionException { |
||||
|
||||
public IllegalFunctionCodeException() { |
||||
} |
||||
|
||||
public IllegalFunctionCodeException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public IllegalFunctionCodeException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public IllegalFunctionCodeException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class IllegalOperationException extends ExecutionException { |
||||
|
||||
public IllegalOperationException() { |
||||
} |
||||
|
||||
public IllegalOperationException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public IllegalOperationException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public IllegalOperationException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class InvalidAddressException extends ExecutionException { |
||||
|
||||
public InvalidAddressException() { |
||||
} |
||||
|
||||
public InvalidAddressException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public InvalidAddressException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public InvalidAddressException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,11 @@
|
||||
package org.ciyam.at; |
||||
|
||||
public interface LoggerInterface { |
||||
|
||||
public void error(String message); |
||||
|
||||
public void debug(String message); |
||||
|
||||
public void echo(String message); |
||||
|
||||
} |
@ -0,0 +1,445 @@
|
||||
package org.ciyam.at; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
|
||||
public class MachineState { |
||||
|
||||
/** Header bytes length */ |
||||
public static final int HEADER_LENGTH = 2 + 2 + 2 + 2 + 2 + 2; // version reserved code data call-stack user-stack
|
||||
|
||||
/** Size of value stored in data segment - typically 8 bytes (long) */ |
||||
public static final int VALUE_SIZE = 8; |
||||
|
||||
/** Size of code-address - typically 4 bytes (int) */ |
||||
public static final int ADDRESS_SIZE = 4; |
||||
|
||||
/** Maximum value for an address in the code segment */ |
||||
public static final int MAX_CODE_ADDRESS = 0x1fffffff; |
||||
|
||||
/** Bytes per code page */ |
||||
public static final int CODE_PAGE_SIZE = 1; |
||||
|
||||
/** Bytes per data page */ |
||||
public static final int DATA_PAGE_SIZE = VALUE_SIZE; |
||||
|
||||
/** Bytes per call stack page */ |
||||
public static final int CALL_STACK_PAGE_SIZE = ADDRESS_SIZE; |
||||
|
||||
/** Bytes per user stack page */ |
||||
public static final int USER_STACK_PAGE_SIZE = VALUE_SIZE; |
||||
|
||||
/** Program Counter: offset into code to point of current execution */ |
||||
public int programCounter; |
||||
|
||||
/** Initial program counter value to use on next block after current block's execution has stopped. 0 by default */ |
||||
public int onStopAddress; |
||||
|
||||
/** Program counter value to use if an error occurs during execution. If null upon error, refund all funds to creator and finish */ |
||||
public Integer onErrorAddress; |
||||
|
||||
/** Execution for current block has stopped. Continue at current program counter on next/specific block */ |
||||
public boolean isSleeping; |
||||
|
||||
/** Block height required to wake from sleeping, or null if not in use */ |
||||
public Integer sleepUntilHeight; |
||||
|
||||
/** Execution for current block has stopped. Restart at onStopAddress on next block */ |
||||
public boolean isStopped; |
||||
|
||||
/** Execution stopped due to lack of funds for processing. Restart at onStopAddress if frozenBalance increases */ |
||||
public boolean isFrozen; |
||||
|
||||
/** Balance at which there were not enough funds, or null if not in use */ |
||||
public Long frozenBalance; |
||||
|
||||
/** Execution permanently stopped */ |
||||
public boolean isFinished; |
||||
|
||||
/** Execution permanently stopped due to fatal error */ |
||||
public boolean hadFatalError; |
||||
|
||||
// 256-bit pseudo-registers
|
||||
public long a1; |
||||
public long a2; |
||||
public long a3; |
||||
public long a4; |
||||
|
||||
public long b1; |
||||
public long b2; |
||||
public long b3; |
||||
public long b4; |
||||
|
||||
public int currentBlockHeight; |
||||
|
||||
/** Number of opcodes processed this execution */ |
||||
public int steps; |
||||
|
||||
public API api; |
||||
LoggerInterface logger; |
||||
|
||||
public short version; |
||||
public short reserved; |
||||
public short numCodePages; |
||||
public short numDataPages; |
||||
public short numCallStackPages; |
||||
public short numUserStackPages; |
||||
|
||||
public byte[] headerBytes; |
||||
|
||||
public ByteBuffer codeByteBuffer; |
||||
public ByteBuffer dataByteBuffer; |
||||
public ByteBuffer callStackByteBuffer; |
||||
public ByteBuffer userStackByteBuffer; |
||||
|
||||
private class Flags { |
||||
private int flags; |
||||
|
||||
public Flags() { |
||||
flags = 0; |
||||
} |
||||
|
||||
public Flags(int value) { |
||||
this.flags = value; |
||||
} |
||||
|
||||
public void push(boolean flag) { |
||||
flags <<= 1; |
||||
flags |= flag ? 1 : 0; |
||||
} |
||||
|
||||
public boolean pop() { |
||||
boolean result = (flags & 1) != 0; |
||||
flags >>>= 1; |
||||
return result; |
||||
} |
||||
|
||||
public int intValue() { |
||||
return flags; |
||||
} |
||||
} |
||||
|
||||
/** For internal use when recreating a machine state */ |
||||
private MachineState(API api, LoggerInterface logger, byte[] headerBytes) { |
||||
if (headerBytes.length != HEADER_LENGTH) |
||||
throw new IllegalArgumentException("headerBytes length " + headerBytes.length + " incorrect, expected " + HEADER_LENGTH); |
||||
|
||||
this.headerBytes = headerBytes; |
||||
parseHeader(); |
||||
|
||||
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); |
||||
this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
|
||||
|
||||
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * USER_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); |
||||
this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
|
||||
|
||||
this.api = api; |
||||
this.currentBlockHeight = api.getCurrentBlockHeight(); |
||||
this.steps = 0; |
||||
this.logger = logger; |
||||
} |
||||
|
||||
/** For creating a new machine state */ |
||||
public MachineState(API api, LoggerInterface logger, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) { |
||||
this(api, logger, headerBytes); |
||||
|
||||
// XXX: Why don't we simply ByteBuffer.wrap(codeBytes) as they're read-only?
|
||||
// This would do away with the need to specify numCodePages, save space and provide automatic end-of-code detection during execution thanks to
|
||||
// ByteBuffer's BufferUnderflowException
|
||||
|
||||
if (codeBytes.length > this.numCodePages * CODE_PAGE_SIZE) |
||||
throw new IllegalArgumentException("Number of code pages too small to hold code bytes"); |
||||
|
||||
if (dataBytes.length > this.numDataPages * DATA_PAGE_SIZE) |
||||
throw new IllegalArgumentException("Number of data pages too small to hold data bytes"); |
||||
|
||||
System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length); |
||||
|
||||
System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length); |
||||
|
||||
this.programCounter = 0; |
||||
this.onStopAddress = 0; |
||||
this.onErrorAddress = null; |
||||
this.isSleeping = false; |
||||
this.sleepUntilHeight = null; |
||||
this.isStopped = false; |
||||
this.isFinished = false; |
||||
this.hadFatalError = false; |
||||
this.isFrozen = false; |
||||
this.frozenBalance = null; |
||||
} |
||||
|
||||
/** For serializing a machine state */ |
||||
public byte[] toBytes() { |
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Header first
|
||||
bytes.write(this.headerBytes); |
||||
|
||||
// Code
|
||||
bytes.write(this.codeByteBuffer.array()); |
||||
|
||||
// Data
|
||||
bytes.write(this.dataByteBuffer.array()); |
||||
|
||||
// Call stack length (32bit unsigned int)
|
||||
int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position(); |
||||
bytes.write(toByteArray(callStackLength)); |
||||
// Call stack
|
||||
bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength); |
||||
|
||||
// User stack length (32bit unsigned int)
|
||||
int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position(); |
||||
bytes.write(toByteArray(userStackLength)); |
||||
// User stack
|
||||
bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength); |
||||
|
||||
// Actual state
|
||||
bytes.write(toByteArray(this.programCounter)); |
||||
bytes.write(toByteArray(this.onStopAddress)); |
||||
|
||||
// Various flags
|
||||
Flags flags = new Flags(); |
||||
flags.push(this.isSleeping); |
||||
flags.push(this.isStopped); |
||||
flags.push(this.isFinished); |
||||
flags.push(this.hadFatalError); |
||||
flags.push(this.isFrozen); |
||||
|
||||
flags.push(this.onErrorAddress != null); // has onErrorAddress?
|
||||
flags.push(this.sleepUntilHeight != null); // has sleepUntilHeight?
|
||||
flags.push(this.frozenBalance != null); // has frozenBalance?
|
||||
|
||||
boolean hasNonZeroA = this.a1 != 0 || this.a2 != 0 || this.a3 != 0 || this.a4 != 0; |
||||
flags.push(hasNonZeroA); |
||||
|
||||
boolean hasNonZeroB = this.b1 != 0 || this.b2 != 0 || this.b3 != 0 || this.b4 != 0; |
||||
flags.push(hasNonZeroB); |
||||
|
||||
bytes.write(toByteArray(flags.intValue())); |
||||
|
||||
// Optional flag-indicated extra info in same order as above
|
||||
if (this.onErrorAddress != null) |
||||
bytes.write(toByteArray(this.onErrorAddress)); |
||||
|
||||
if (this.sleepUntilHeight != null) |
||||
bytes.write(toByteArray(this.sleepUntilHeight)); |
||||
|
||||
if (this.frozenBalance != null) |
||||
bytes.write(toByteArray(this.frozenBalance)); |
||||
|
||||
if (hasNonZeroA) { |
||||
bytes.write(toByteArray(this.a1)); |
||||
bytes.write(toByteArray(this.a2)); |
||||
bytes.write(toByteArray(this.a3)); |
||||
bytes.write(toByteArray(this.a4)); |
||||
} |
||||
|
||||
if (hasNonZeroB) { |
||||
bytes.write(toByteArray(this.b1)); |
||||
bytes.write(toByteArray(this.b2)); |
||||
bytes.write(toByteArray(this.b3)); |
||||
bytes.write(toByteArray(this.b4)); |
||||
} |
||||
} catch (IOException e) { |
||||
return null; |
||||
} |
||||
|
||||
return bytes.toByteArray(); |
||||
} |
||||
|
||||
/** For restoring a previously serialized machine state */ |
||||
public static MachineState fromBytes(API api, LoggerInterface logger, byte[] bytes) { |
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
byte[] headerBytes = new byte[HEADER_LENGTH]; |
||||
byteBuffer.get(headerBytes); |
||||
|
||||
MachineState state = new MachineState(api, logger, headerBytes); |
||||
|
||||
byte[] codeBytes = new byte[state.codeByteBuffer.capacity()]; |
||||
byteBuffer.get(codeBytes); |
||||
System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length); |
||||
|
||||
byte[] dataBytes = new byte[state.dataByteBuffer.capacity()]; |
||||
byteBuffer.get(dataBytes); |
||||
System.arraycopy(dataBytes, 0, state.dataByteBuffer.array(), 0, dataBytes.length); |
||||
|
||||
int callStackLength = byteBuffer.getInt(); |
||||
byte[] callStackBytes = new byte[callStackLength]; |
||||
byteBuffer.get(callStackBytes); |
||||
// Restore call stack pointer, and useful for copy below
|
||||
state.callStackByteBuffer.position(state.callStackByteBuffer.limit() - callStackLength); |
||||
// Call stack grows downwards so copy to end
|
||||
System.arraycopy(callStackBytes, 0, state.callStackByteBuffer.array(), state.callStackByteBuffer.position(), callStackLength); |
||||
|
||||
int userStackLength = byteBuffer.getInt(); |
||||
byte[] userStackBytes = new byte[userStackLength]; |
||||
byteBuffer.get(userStackBytes); |
||||
// Restore user stack pointer, and useful for copy below
|
||||
state.userStackByteBuffer.position(state.userStackByteBuffer.limit() - userStackLength); |
||||
// User stack grows downwards so copy to end
|
||||
System.arraycopy(userStackBytes, 0, state.userStackByteBuffer.array(), state.userStackByteBuffer.position(), userStackLength); |
||||
|
||||
// Actual state
|
||||
state.programCounter = byteBuffer.getInt(); |
||||
state.onStopAddress = byteBuffer.getInt(); |
||||
|
||||
// Various flags (reverse order to toBytes)
|
||||
Flags flags = state.new Flags(byteBuffer.getInt()); |
||||
boolean hasNonZeroB = flags.pop(); |
||||
boolean hasNonZeroA = flags.pop(); |
||||
boolean hasFrozenBalance = flags.pop(); |
||||
boolean hasSleepUntilHeight = flags.pop(); |
||||
boolean hasOnErrorAddress = flags.pop(); |
||||
|
||||
state.isFrozen = flags.pop(); |
||||
state.hadFatalError = flags.pop(); |
||||
state.isFinished = flags.pop(); |
||||
state.isStopped = flags.pop(); |
||||
state.isSleeping = flags.pop(); |
||||
|
||||
// Optional extras (same order as toBytes)
|
||||
if (hasOnErrorAddress) |
||||
state.onErrorAddress = byteBuffer.getInt(); |
||||
|
||||
if (hasSleepUntilHeight) |
||||
state.sleepUntilHeight = byteBuffer.getInt(); |
||||
|
||||
if (hasFrozenBalance) |
||||
state.frozenBalance = byteBuffer.getLong(); |
||||
|
||||
if (hasNonZeroA) { |
||||
state.a1 = byteBuffer.getLong(); |
||||
state.a2 = byteBuffer.getLong(); |
||||
state.a3 = byteBuffer.getLong(); |
||||
state.a4 = byteBuffer.getLong(); |
||||
} |
||||
|
||||
if (hasNonZeroB) { |
||||
state.b1 = byteBuffer.getLong(); |
||||
state.b2 = byteBuffer.getLong(); |
||||
state.b3 = byteBuffer.getLong(); |
||||
state.b4 = byteBuffer.getLong(); |
||||
} |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/** Convert int to little-endian byte array */ |
||||
private byte[] toByteArray(int value) { |
||||
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) }; |
||||
} |
||||
|
||||
/** Convert long to little-endian byte array */ |
||||
private byte[] toByteArray(long value) { |
||||
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40), |
||||
(byte) (value >> 48), (byte) (value >> 56) }; |
||||
} |
||||
|
||||
private void parseHeader() { |
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes); |
||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
||||
this.version = byteBuffer.getShort(); |
||||
if (this.version < 1) |
||||
throw new IllegalArgumentException("Version must be >= 0"); |
||||
|
||||
this.reserved = byteBuffer.getShort(); |
||||
|
||||
this.numCodePages = byteBuffer.getShort(); |
||||
if (this.numCodePages < 1) |
||||
throw new IllegalArgumentException("Number of code pages must be > 0"); |
||||
|
||||
this.numDataPages = byteBuffer.getShort(); |
||||
if (this.numDataPages < 1) |
||||
throw new IllegalArgumentException("Number of data pages must be > 0"); |
||||
|
||||
this.numCallStackPages = byteBuffer.getShort(); |
||||
if (this.numCallStackPages < 0) |
||||
throw new IllegalArgumentException("Number of call stack pages must be >= 0"); |
||||
|
||||
this.numUserStackPages = byteBuffer.getShort(); |
||||
if (this.numUserStackPages < 0) |
||||
throw new IllegalArgumentException("Number of user stack pages must be >= 0"); |
||||
} |
||||
|
||||
public void execute() { |
||||
// Set byte buffer position using program counter
|
||||
codeByteBuffer.position(this.programCounter); |
||||
|
||||
// Reset for this round of execution
|
||||
this.isSleeping = false; |
||||
this.sleepUntilHeight = null; |
||||
this.isStopped = false; |
||||
this.isFrozen = false; |
||||
this.frozenBalance = null; |
||||
this.steps = 0; |
||||
|
||||
while (!this.isSleeping && !this.isStopped && !this.isFinished && !this.isFrozen) { |
||||
byte rawOpCode = codeByteBuffer.get(); |
||||
OpCode nextOpCode = OpCode.valueOf(rawOpCode); |
||||
|
||||
try { |
||||
if (nextOpCode == null) |
||||
throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised"); |
||||
|
||||
this.logger.debug("[PC: " + String.format("%04x", this.programCounter) + "] " + nextOpCode.name()); |
||||
|
||||
nextOpCode.execute(codeByteBuffer, dataByteBuffer, userStackByteBuffer, callStackByteBuffer, this); |
||||
this.programCounter = codeByteBuffer.position(); |
||||
} catch (ExecutionException e) { |
||||
this.logger.debug("Error at PC " + String.format("%04x", this.programCounter) + ": " + e.getMessage()); |
||||
|
||||
if (this.onErrorAddress == null) { |
||||
this.isFinished = true; |
||||
this.hadFatalError = true; |
||||
this.api.onFatalError(this, e); |
||||
break; |
||||
} |
||||
|
||||
this.programCounter = this.onErrorAddress; |
||||
codeByteBuffer.position(this.programCounter); |
||||
} |
||||
|
||||
++this.steps; |
||||
} |
||||
|
||||
if (this.isStopped) { |
||||
this.logger.debug("Setting program counter to stop address: " + String.format("%04x", this.onStopAddress)); |
||||
this.programCounter = this.onStopAddress; |
||||
} |
||||
} |
||||
|
||||
// public String disassemble(List<String> dataLabels, Map<Integer, String> codeLabels) {
|
||||
public String disassemble() throws ExecutionException { |
||||
String output = ""; |
||||
|
||||
codeByteBuffer.position(0); |
||||
|
||||
while (codeByteBuffer.hasRemaining()) { |
||||
byte rawOpCode = codeByteBuffer.get(); |
||||
if (rawOpCode == 0) |
||||
continue; |
||||
|
||||
OpCode nextOpCode = OpCode.valueOf(rawOpCode); |
||||
if (nextOpCode == null) |
||||
throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised"); |
||||
|
||||
if (!output.isEmpty()) |
||||
output += "\n"; |
||||
|
||||
output += "[PC: " + String.format("%04x", codeByteBuffer.position() - 1) + "] " + nextOpCode.disassemble(codeByteBuffer, dataByteBuffer); |
||||
} |
||||
|
||||
return output; |
||||
} |
||||
|
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,160 @@
|
||||
package org.ciyam.at; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
public enum OpCodeParam { |
||||
|
||||
VALUE { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Long(Utils.getCodeValue(codeByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("#%016x", (Long) value); |
||||
} |
||||
}, |
||||
DEST_ADDR { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("@%08x", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
INDIRECT_DEST_ADDR { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("@($%08x)", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
INDIRECT_DEST_ADDR_WITH_INDEX { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("@($%08x", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
SRC_ADDR { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("$%08x", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
INDIRECT_SRC_ADDR { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("$($%08x)", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
INDIRECT_SRC_ADDR_WITH_INDEX { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("$($%08x", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
INDEX { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("+ $%08x)", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}, |
||||
CODE_ADDR { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(Utils.getCodeAddress(codeByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("[%04x]", (Integer) value); |
||||
} |
||||
}, |
||||
OFFSET { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Byte(Utils.getCodeOffset(codeByteBuffer)); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("PC+%02x=[%04x]", (int) ((Byte) value), postOpcodeProgramCounter - 1 + (Byte) value); |
||||
} |
||||
}, |
||||
FUNC { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Short(codeByteBuffer.getShort()); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
FunctionCode functionCode = FunctionCode.valueOf((Short) value); |
||||
|
||||
// generic/unknown form
|
||||
if (functionCode == null) |
||||
return String.format("FN(%04x)", (Short) value); |
||||
|
||||
// API pass-through
|
||||
if (functionCode == FunctionCode.API_PASSTHROUGH) |
||||
return String.format("API-FN(%04x)", (Short) value); |
||||
|
||||
return "\"" + functionCode.name() + "\"" + String.format("{%04x}", (Short) value); |
||||
} |
||||
}, |
||||
BLOCK_HEIGHT { |
||||
@Override |
||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { |
||||
return new Integer(codeByteBuffer.getInt()); |
||||
} |
||||
|
||||
@Override |
||||
protected String toString(Object value, int postOpcodeProgramCounter) { |
||||
return String.format("height $%08x", ((Integer) value) / MachineState.VALUE_SIZE); |
||||
} |
||||
}; |
||||
|
||||
public abstract Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException; |
||||
|
||||
public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, int postOpcodeProgramCounter) throws ExecutionException { |
||||
Object value = fetch(codeByteBuffer, dataByteBuffer); |
||||
|
||||
return this.toString(value, postOpcodeProgramCounter); |
||||
} |
||||
|
||||
protected abstract String toString(Object value, int postOpcodeProgramCounter); |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.ciyam.at; |
||||
|
||||
@SuppressWarnings("serial") |
||||
public class StackBoundsException extends ExecutionException { |
||||
|
||||
public StackBoundsException() { |
||||
} |
||||
|
||||
public StackBoundsException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public StackBoundsException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public StackBoundsException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,66 @@
|
||||
package org.ciyam.at; |
||||
|
||||
/** |
||||
* CIYAM-AT "Timestamp" |
||||
* <p> |
||||
* With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes two parts. The first part is a block height |
||||
* (32 bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable). Timestamps can thus be |
||||
* represented as a 64 bit long. |
||||
* <p> |
||||
* |
||||
* @see Timestamp#Timestamp(int, int) |
||||
* @see Timestamp#Timestamp(long) |
||||
* @see Timestamp#longValue() |
||||
* @see Timestamp#toLong(int, int) |
||||
* |
||||
*/ |
||||
public class Timestamp { |
||||
|
||||
public int blockHeight; |
||||
public int transactionSequence; |
||||
|
||||
/** |
||||
* Constructs new CIYAM-AT "timestamp" using block height and transaction sequence. |
||||
* |
||||
* @param blockHeight |
||||
* @param transactionSequence |
||||
*/ |
||||
public Timestamp(int blockHeight, int transactionSequence) { |
||||
this.blockHeight = blockHeight; |
||||
this.transactionSequence = transactionSequence; |
||||
} |
||||
|
||||
/** |
||||
* Constructs new CIYAM-AT "timestamp" using long packed with block height and transaction sequence. |
||||
* |
||||
* @param timestamp |
||||
*/ |
||||
public Timestamp(long timestamp) { |
||||
this.blockHeight = (int) (timestamp >> 32); |
||||
this.transactionSequence = (int) (timestamp & 0xffffff); |
||||
} |
||||
|
||||
/** |
||||
* Returns CIYAM-AT "timestamp" long representing block height and transaction sequence. |
||||
* |
||||
* @return CIYAM-AT "timestamp" as long |
||||
*/ |
||||
public long longValue() { |
||||
return Timestamp.toLong(this.blockHeight, this.transactionSequence); |
||||
} |
||||
|
||||
/** |
||||
* Returns CIYAM-AT "timestamp" long representing block height and transaction sequence. |
||||
* |
||||
* @param blockHeight |
||||
* @param transactionSequence |
||||
* @return CIYAM-AT "timestamp" as long |
||||
*/ |
||||
public static long toLong(int blockHeight, int transactionSequence) { |
||||
long longValue = blockHeight; |
||||
longValue <<= 32; |
||||
longValue |= transactionSequence; |
||||
return longValue; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@
|
||||
package org.ciyam.at; |
||||
|
||||
public interface TwoValueComparator { |
||||
|
||||
public boolean compare(long a, long b); |
||||
|
||||
} |
@ -0,0 +1,7 @@
|
||||
package org.ciyam.at; |
||||
|
||||
public interface TwoValueOperator { |
||||
|
||||
public long apply(long a, long b); |
||||
|
||||
} |
@ -0,0 +1,126 @@
|
||||
package org.ciyam.at; |
||||
|
||||
import java.nio.BufferUnderflowException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class Utils { |
||||
|
||||
/** |
||||
* Returns immediate function code enum from code bytes at current position. |
||||
* <p> |
||||
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 2. |
||||
* |
||||
* @param codeByteBuffer |
||||
* @return FunctionCode enum |
||||
* @throws CodeSegmentException |
||||
* @throws InvalidAddressException |
||||
*/ |
||||
public static FunctionCode getFunctionCode(ByteBuffer codeByteBuffer) throws CodeSegmentException, IllegalFunctionCodeException { |
||||
try { |
||||
int rawFunctionCode = codeByteBuffer.getShort(); |
||||
|
||||
FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode); |
||||
|
||||
if (functionCode == null) |
||||
throw new IllegalFunctionCodeException("Unknown function code"); |
||||
|
||||
return functionCode; |
||||
} catch (BufferUnderflowException e) { |
||||
throw new CodeSegmentException("No code bytes left to get function code", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns code address from code bytes at current position. |
||||
* <p> |
||||
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 4. |
||||
* <p> |
||||
* <b>Note:</b> address is not scaled by <tt>Constants.VALUE_SIZE</tt> unlike other methods in this class. |
||||
* |
||||
* @param codeByteBuffer |
||||
* @return int address into code bytes |
||||
* @throws CodeSegmentException |
||||
* @throws InvalidAddressException |
||||
*/ |
||||
public static int getCodeAddress(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException { |
||||
try { |
||||
int address = codeByteBuffer.getInt(); |
||||
|
||||
if (address < 0 || address > MachineState.MAX_CODE_ADDRESS || address >= codeByteBuffer.limit()) |
||||
throw new InvalidAddressException("Code address out of bounds"); |
||||
|
||||
return address; |
||||
} catch (BufferUnderflowException e) { |
||||
throw new CodeSegmentException("No code bytes left to get code address", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns data address from code bytes at current position. |
||||
* <p> |
||||
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 4. |
||||
* <p> |
||||
* <b>Note:</b> address is returned scaled by <tt>Constants.VALUE_SIZE</tt>. |
||||
* |
||||
* @param codeByteBuffer |
||||
* @return int address into data bytes |
||||
* @throws CodeSegmentException |
||||
* @throws InvalidAddressException |
||||
*/ |
||||
public static int getDataAddress(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws CodeSegmentException, InvalidAddressException { |
||||
try { |
||||
int address = codeByteBuffer.getInt() * MachineState.VALUE_SIZE; |
||||
|
||||
if (address < 0 || address + MachineState.VALUE_SIZE >= dataByteBuffer.limit()) |
||||
throw new InvalidAddressException("Data address out of bounds"); |
||||
|
||||
return address; |
||||
} catch (BufferUnderflowException e) { |
||||
throw new CodeSegmentException("No code bytes left to get data address", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns byte offset from code bytes at current position. |
||||
* <p> |
||||
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 1. |
||||
* <p> |
||||
* <b>Note:</b> offset is not scaled by <tt>Constants.VALUE_SIZE</tt> unlike other methods in this class. |
||||
* |
||||
* @param codeByteBuffer |
||||
* @return byte offset |
||||
* @throws CodeSegmentException |
||||
* @throws InvalidAddressException |
||||
*/ |
||||
public static byte getCodeOffset(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException { |
||||
try { |
||||
byte offset = codeByteBuffer.get(); |
||||
|
||||
if (codeByteBuffer.position() + offset < 0 || codeByteBuffer.position() + offset >= codeByteBuffer.limit()) |
||||
throw new InvalidAddressException("Code offset out of bounds"); |
||||
|
||||
return offset; |
||||
} catch (BufferUnderflowException e) { |
||||
throw new CodeSegmentException("No code bytes left to get code offset", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns long immediate value from code bytes at current position. |
||||
* <p> |
||||
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 8. |
||||
* |
||||
* @param codeByteBuffer |
||||
* @return long value |
||||
* @throws CodeSegmentException |
||||
* @throws InvalidAddressException |
||||
*/ |
||||
public static long getCodeValue(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException { |
||||
try { |
||||
return codeByteBuffer.getLong(); |
||||
} catch (BufferUnderflowException e) { |
||||
throw new CodeSegmentException("No code bytes left to get immediate value", e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,496 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class BranchingOpCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testBZR_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x21; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(0).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBZR_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x21; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(0).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBNZ_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x21; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(0).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBNZ_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x21; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(0).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBGT_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BGT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBGT_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BGT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBLT_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBLT_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBGE_DATtrue1() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBGE_DATtrue2() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBGE_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBLE_DATtrue1() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBLE_DATtrue2() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBLE_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBEQ_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BEQ_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBEQ_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BEQ_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBNE_DATtrue() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBNE_DATfalse() throws ExecutionException { |
||||
int targetAddr = 0x32; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L); |
||||
int tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC)); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,255 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class CallStackOpCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testJMP_SUB() throws ExecutionException { |
||||
int subAddr = 0x06; |
||||
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr); |
||||
int returnAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// subAddr:
|
||||
assertEquals(subAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedCallStackPosition = (state.numCallStackPages - 1) * MachineState.CALL_STACK_PAGE_SIZE; |
||||
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position()); |
||||
|
||||
assertEquals("Return address does not match", returnAddress, state.callStackByteBuffer.getInt(expectedCallStackPosition)); |
||||
|
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testJMP_SUB2() throws ExecutionException { |
||||
int subAddr1 = 0x06; |
||||
int subAddr2 = 0x19; |
||||
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1); |
||||
int returnAddress1 = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// subAddr1:
|
||||
assertEquals(subAddr1, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2); |
||||
int returnAddress2 = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// subAddr2:
|
||||
assertEquals(subAddr2, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * MachineState.CALL_STACK_PAGE_SIZE; |
||||
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position()); |
||||
|
||||
assertEquals("Return address does not match", returnAddress2, state.callStackByteBuffer.getInt(expectedCallStackPosition)); |
||||
assertEquals("Return address does not match", returnAddress1, state.callStackByteBuffer.getInt(expectedCallStackPosition + MachineState.ADDRESS_SIZE)); |
||||
|
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testJMP_SUBoverflow() throws ExecutionException { |
||||
// Call stack is 0x0010 entries in size, so exceed this to test overflow
|
||||
for (int i = 0; i < 20; ++i) { |
||||
// sub address is next opcode!
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(i * (1 + 4)); |
||||
} |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testRET_SUB() throws ExecutionException { |
||||
int subAddr = 0x13; |
||||
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// subAddr:
|
||||
assertEquals(subAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.RET_SUB.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
|
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE; |
||||
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position()); |
||||
|
||||
assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE)); |
||||
|
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testRET_SUB2() throws ExecutionException { |
||||
int subAddr1 = 0x13; |
||||
int subAddr2 = 0x34; |
||||
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// subAddr1:
|
||||
assertEquals(subAddr1, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.RET_SUB.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
|
||||
|
||||
// subAddr2:
|
||||
assertEquals(subAddr2, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.RET_SUB.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
|
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE; |
||||
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position()); |
||||
|
||||
assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE)); |
||||
|
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testRET_SUBoverflow() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.RET_SUB.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testRET_SUBoverflow2() throws ExecutionException { |
||||
// sub address is next opcode!
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(1 + 4); |
||||
// this is return address too
|
||||
codeByteBuffer.put(OpCode.RET_SUB.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,816 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class DataOpCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testSET_VAL() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_VALunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(9999).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_DATunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testCLR_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
// Check data all zero
|
||||
state.dataByteBuffer.position(0); |
||||
while (state.dataByteBuffer.hasRemaining()) |
||||
assertEquals((byte) 0, state.dataByteBuffer.get()); |
||||
} |
||||
|
||||
@Test |
||||
public void testCLR_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testINC_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L + 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testINC_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testINC_DAToverflow() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0xffffffffffffffffL); |
||||
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testDEC_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L - 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testDEC_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testDEC_DATunderflow() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0L); |
||||
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 0xffffffffffffffffL, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testADD_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L + 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testADD_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(9999).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testADD_DATunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testADD_DAToverflow() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0x7fffffffffffffffL); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(0x8000000000000099L); |
||||
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 0x0000000000000098L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSUB_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SUB_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 3333L - 2222L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testMUL_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.MUL_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", (3333L * 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testDIV_DAT() throws ExecutionException { |
||||
// Note: fatal error because error handler not set
|
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", (3333L / 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testDIV_DATzeroWithOnError() throws ExecutionException { |
||||
// Note: non-fatal error because error handler IS set
|
||||
|
||||
int errorAddr = 0x29; |
||||
|
||||
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// errorAddr:
|
||||
assertEquals(errorAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testBOR_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.BOR_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", (3333L | 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testAND_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.AND_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", (3333L & 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testXOR_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.XOR_DAT.value).putInt(3).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", (3333L ^ 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testNOT_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.NOT_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", ~2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IND() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @(6) = $($0) aka $(3) aka 3333
|
||||
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(6 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_INDunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @(6) = $($9999) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_INDunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @(6) = $($0) aka $(9999) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IDX() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @(0) = $($6 + $7) aka $(1 + 3) aka $(4) aka 4444
|
||||
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IDXunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @(0) = $($9999 + $7) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(9999).putInt(7); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IDXunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(9999L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @(0) = $($6 + $7) aka $(9999 + 1) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IDXunbounded3() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(9999L); |
||||
// @(0) = $($6 + $7) aka $(1 + 9999) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSET_IDXunbounded4() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @(0) = $($6 + $9999) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(9999); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIND_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @($0) aka @(3) = $(5) = 5555
|
||||
codeByteBuffer.put(OpCode.IND_DAT.value).putInt(0).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testIND_DATDunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @($9999) = $(5) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IND.value).putInt(9999).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIND_DATDunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
// @($0) aka @(9999) = $(5) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.SET_IND.value).putInt(0).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIDX_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @($6 + $7) aka @(1 + 3) aka @(4) = $(5) aka 5555
|
||||
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(4 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testIDX_DATunbounded() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @($9999 + $7) = $(5) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(9999).putInt(7).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIDX_DATunbounded2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(9999L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @($6 + $7) aka @(9999 + 3) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIDX_DATunbounded3() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(9999L); |
||||
// @($6 + $7) aka @(1 + 9999) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testIDX_DATunbounded4() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L); |
||||
// @($6 + $9999) = $(5) but data address 9999 is out of bounds
|
||||
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(9999).putInt(5); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testMOD_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L % 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testMOD_DATzeroWithOnError() throws ExecutionException { |
||||
// Note: non-fatal error because error handler IS set
|
||||
|
||||
int errorAddr = 0x29; |
||||
|
||||
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(3).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// errorAddr:
|
||||
assertEquals(errorAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSHL_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L << 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSHL_DATexcess() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSHR_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3L); |
||||
codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2222L >> 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSHR_DATexcess() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,116 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionCode; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class DisassemblyTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
@Test |
||||
public void testMD160disassembly() throws ExecutionException { |
||||
// MD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
System.out.println(state.disassemble()); |
||||
} |
||||
|
||||
@Test |
||||
public void testACCTdisassembly() throws ExecutionException { |
||||
codeByteBuffer.put(hexToBytes("3501030900000006040000000900000029302009000000040000000f1ab4000000330403090000003525010a000000260a00")); |
||||
codeByteBuffer.put(hexToBytes("0000320903350703090000003526010a0000001b0a000000cd32280133160100000000331701010000003318010200000033")); |
||||
codeByteBuffer.put(hexToBytes("1901030000003505020a0000001b0a000000a1320b033205041e050000001833000509000000320a033203041ab400000033")); |
||||
codeByteBuffer.put(hexToBytes("160105000000331701060000003318010700000033190108000000320304320b033203041ab7000000000000000000000000")); |
||||
codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); |
||||
codeByteBuffer.put(hexToBytes("000000000000")); |
||||
|
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
System.out.println(state.disassemble()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,311 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionCode; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.ciyam.at.Timestamp; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class FunctionCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testMD5() throws ExecutionException { |
||||
// MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
// A3 unused
|
||||
// A4 unused
|
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MD5_A_TO_B.value); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testCHECK_MD5() throws ExecutionException { |
||||
// MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
// A3 unused
|
||||
// A4 unused
|
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_MD5_A_WITH_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testHASH160() throws ExecutionException { |
||||
// RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
// A4 unused
|
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.HASH160_A_TO_B.value); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testCHECK_HASH160() throws ExecutionException { |
||||
// RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
// A4 unused
|
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSHA256() throws ExecutionException { |
||||
// SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SHA256_A_TO_B.value); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testCHECK_SHA256() throws ExecutionException { |
||||
// SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051")); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(0); |
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(1); |
||||
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testRandom() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0)); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GENERATE_RANDOM_USING_TX_IN_A.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertNotEquals("Random wasn't generated", 0L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testInvalidFunctionCode() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort((short) 0xaaaa); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testPlatformSpecific0501() { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0)); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort((short) 0x0501).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testPlatformSpecific0501Error() { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0)); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort((short) 0x0501).putInt(0).putInt(0); // Wrong OPCODE for function
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,111 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionCode; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class MiscTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testSimpleCode() throws ExecutionException { |
||||
long testValue = 8888L; |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(testValue); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", testValue, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testInvalidOpCode() throws ExecutionException { |
||||
codeByteBuffer.put((byte) 0xdd); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,292 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class OpCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished && !state.isFrozen && !state.isSleeping && !state.isStopped); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testNOP() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.NOP.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
// Check data unchanged
|
||||
state.dataByteBuffer.position(0); |
||||
while (state.dataByteBuffer.hasRemaining()) |
||||
assertEquals((byte) 0, state.dataByteBuffer.get()); |
||||
} |
||||
|
||||
@Test |
||||
public void testJMP_ADR() throws ExecutionException { |
||||
int targetAddr = 0x12; |
||||
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(targetAddr); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1L); |
||||
|
||||
// targetAddr:
|
||||
assertEquals(targetAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testSLP_DAT() throws ExecutionException { |
||||
int blockHeight = 12345; |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(blockHeight); |
||||
codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isSleeping); |
||||
assertFalse(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Sleep-until block height incorrect", blockHeight, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testFIZ_DATtrue() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SLP_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.isSleeping); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testFIZ_DATfalse() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SLP_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertFalse(state.isFinished); |
||||
assertTrue(state.isSleeping); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSTZ_DATtrue() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
int stopAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isStopped); |
||||
assertFalse(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Program counter incorrect", stopAddress, state.programCounter); |
||||
} |
||||
|
||||
@Test |
||||
public void testSTZ_DATfalse() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertFalse(state.isStopped); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testFIN_IMD() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
codeByteBuffer.put(OpCode.STP_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.isStopped); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testSTP_IMD() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
int stopAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.NOP.value); |
||||
codeByteBuffer.put(OpCode.STP_IMD.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isStopped); |
||||
assertFalse(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Program counter incorrect", stopAddress, state.programCounter); |
||||
} |
||||
|
||||
@Test |
||||
public void testSLP_IMD() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SLP_IMD.value); |
||||
int nextAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isSleeping); |
||||
assertFalse(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Program counter incorrect", nextAddress, state.programCounter); |
||||
} |
||||
|
||||
@Test |
||||
public void testERR_ADR() throws ExecutionException { |
||||
// Note: non-fatal error because error handler IS set
|
||||
|
||||
int errorAddr = 0x29; |
||||
|
||||
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr); |
||||
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(12345L); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(0L); |
||||
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(0).putInt(1); // divide by zero
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// errorAddr:
|
||||
assertEquals(errorAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPCS() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111")); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222")); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
int expectedStopAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertEquals(expectedStopAddress, state.onStopAddress); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testPCS2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111")); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
int expectedStopAddress = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222")); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertEquals(expectedStopAddress, state.onStopAddress); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,109 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.util.Arrays; |
||||
|
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionCode; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class SerializationTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(256).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private byte[] simulate() { |
||||
// version 0003, reserved 0000, code 0100 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0001" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
return executeAndCheck(state); |
||||
} |
||||
|
||||
private byte[] continueSimulation(byte[] savedState) { |
||||
state = MachineState.fromBytes(api, logger, savedState); |
||||
|
||||
// Pretend we're on next block
|
||||
state.currentBlockHeight++; |
||||
|
||||
return executeAndCheck(state); |
||||
} |
||||
|
||||
private byte[] executeAndCheck(MachineState state) { |
||||
state.execute(); |
||||
|
||||
byte[] stateBytes = state.toBytes(); |
||||
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes); |
||||
byte[] restoredStateBytes = restoredState.toBytes(); |
||||
|
||||
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes)); |
||||
|
||||
return stateBytes; |
||||
} |
||||
|
||||
@Test |
||||
public void testPCS2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111")); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222")); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertEquals(0x0e, (int) state.onStopAddress); |
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testStopWithStacks() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(100); // 0000
|
||||
codeByteBuffer.put(OpCode.SET_PCS.value); // 000d
|
||||
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(0x002a); // 000e
|
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(10); // 0013
|
||||
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(0).putInt(1); // 0020
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value); // 0029
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); // 002a
|
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); // 0031
|
||||
codeByteBuffer.put(OpCode.RET_SUB.value); // 0036
|
||||
|
||||
byte[] savedState = simulate(); |
||||
|
||||
assertEquals(0x0e, (int) state.onStopAddress); |
||||
assertTrue(state.isStopped); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
savedState = continueSimulation(savedState); |
||||
savedState = continueSimulation(savedState); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,218 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.SecureRandom; |
||||
import java.security.Security; |
||||
import java.util.Arrays; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionCode; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.ACCTAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class TestACCT { |
||||
|
||||
public TestLogger logger; |
||||
public ACCTAPI api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
public ByteBuffer dataByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new ACCTAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(0x0200 * 1).order(ByteOrder.LITTLE_ENDIAN); |
||||
dataByteBuffer = ByteBuffer.allocate(0x0020 * 8).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
dataByteBuffer = null; |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private byte[] simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = dataByteBuffer.array(); |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
return executeAndCheck(state); |
||||
} |
||||
|
||||
private byte[] continueSimulation(byte[] savedState) { |
||||
state = MachineState.fromBytes(api, logger, savedState); |
||||
|
||||
// Pretend we're on next block
|
||||
state.currentBlockHeight++; |
||||
|
||||
return executeAndCheck(state); |
||||
} |
||||
|
||||
private byte[] executeAndCheck(MachineState state) { |
||||
state.execute(); |
||||
|
||||
byte[] stateBytes = state.toBytes(); |
||||
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes); |
||||
byte[] restoredStateBytes = restoredState.toBytes(); |
||||
|
||||
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes)); |
||||
|
||||
return stateBytes; |
||||
} |
||||
|
||||
@Test |
||||
public void testACCT() throws ExecutionException { |
||||
// DATA
|
||||
final int addrHashPart1 = 0x0; |
||||
final int addrHashPart2 = 0x1; |
||||
final int addrHashPart3 = 0x2; |
||||
final int addrHashPart4 = 0x3; |
||||
final int addrAddressPart1 = 0x4; |
||||
final int addrAddressPart2 = 0x5; |
||||
final int addrAddressPart3 = 0x6; |
||||
final int addrAddressPart4 = 0x7; |
||||
final int addrRefundMinutes = 0x8; |
||||
final int addrRefundTimestamp = 0x9; |
||||
final int addrLastTimestamp = 0xa; |
||||
final int addrBlockTimestamp = 0xb; |
||||
final int addrTxType = 0xc; |
||||
final int addrComparator = 0xd; |
||||
final int addrAddressTemp1 = 0xe; |
||||
final int addrAddressTemp2 = 0xf; |
||||
final int addrAddressTemp3 = 0x10; |
||||
final int addrAddressTemp4 = 0x11; |
||||
|
||||
byte[] secret = new byte[32]; |
||||
new SecureRandom().nextBytes(secret); |
||||
|
||||
try { |
||||
MessageDigest digester = MessageDigest.getInstance("SHA-256"); |
||||
byte[] digest = digester.digest(secret); |
||||
|
||||
dataByteBuffer.put(digest); |
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new ExecutionException("No SHA-256 message digest service available", e); |
||||
} |
||||
|
||||
// Destination address (based on "R" for "Responder", where "R" is 0x52)
|
||||
dataByteBuffer.put(hexToBytes("5200000000000000520000000000000052000000000000005200000000000000")); |
||||
|
||||
// Expiry in minutes (but actually blocks in this test case)
|
||||
dataByteBuffer.putLong(8L); |
||||
|
||||
// Code labels
|
||||
final int addrTxLoop = 0x36; |
||||
final int addrCheckTx = 0x4b; |
||||
final int addrCheckSender = 0x64; |
||||
final int addrCheckMessage = 0xab; |
||||
final int addrPayout = 0xdf; |
||||
final int addrRefund = 0x102; |
||||
|
||||
int tempPC; |
||||
|
||||
// init:
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp); |
||||
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) |
||||
.putInt(addrRefundTimestamp).putInt(addrRefundMinutes); |
||||
codeByteBuffer.put(OpCode.SET_PCS.value); |
||||
|
||||
// loop:
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC)); |
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund); |
||||
|
||||
// txloop:
|
||||
assertEquals(addrTxLoop, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); |
||||
codeByteBuffer.put(OpCode.STP_IMD.value); |
||||
|
||||
// checkTx:
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC)); |
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); |
||||
|
||||
// checkSender
|
||||
assertEquals(addrCheckSender, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC)); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC)); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC)); |
||||
|
||||
// checkMessage:
|
||||
assertEquals(addrCheckMessage, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator); |
||||
tempPC = codeByteBuffer.position(); |
||||
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC)); |
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); |
||||
|
||||
// payout:
|
||||
assertEquals(addrPayout, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3); |
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// refund:
|
||||
assertEquals(addrRefund, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value); |
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
byte[] savedState = simulate(); |
||||
|
||||
while (!state.isFinished) { |
||||
((ACCTAPI) state.api).generateNextBlock(secret); |
||||
|
||||
savedState = continueSimulation(savedState); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class ToolchainTests { |
||||
|
||||
@Test |
||||
public void testHexToBytes() { |
||||
assertTrue(Arrays.equals(new byte[] { 0x12 }, hexToBytes("12"))); |
||||
assertTrue(Arrays.equals(new byte[] { 0x00, 0x00, 0x12 }, hexToBytes("000012"))); |
||||
assertTrue(Arrays.equals(new byte[] { (byte) 0xff }, hexToBytes("ff"))); |
||||
assertTrue(Arrays.equals(new byte[] { 0x00, 0x00, (byte) 0xee }, hexToBytes("0000ee"))); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,224 @@
|
||||
import static common.TestUtils.hexToBytes; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.security.Security; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.OpCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
import common.TestAPI; |
||||
import common.TestLogger; |
||||
|
||||
public class UserStackOpCodeTests { |
||||
|
||||
public TestLogger logger; |
||||
public API api; |
||||
public MachineState state; |
||||
public ByteBuffer codeByteBuffer; |
||||
|
||||
@BeforeClass |
||||
public static void beforeClass() { |
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
} |
||||
|
||||
@Before |
||||
public void beforeTest() { |
||||
logger = new TestLogger(); |
||||
api = new TestAPI(); |
||||
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() { |
||||
codeByteBuffer = null; |
||||
api = null; |
||||
logger = null; |
||||
} |
||||
|
||||
private void execute() { |
||||
System.out.println("Starting execution:"); |
||||
System.out.println("Current block height: " + state.currentBlockHeight); |
||||
|
||||
state.execute(); |
||||
|
||||
System.out.println("After execution:"); |
||||
System.out.println("Steps: " + state.steps); |
||||
System.out.println("Program Counter: " + String.format("%04x", state.programCounter)); |
||||
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress)); |
||||
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress))); |
||||
if (state.isSleeping) |
||||
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight); |
||||
else |
||||
System.out.println("Sleeping: " + state.isSleeping); |
||||
System.out.println("Stopped: " + state.isStopped); |
||||
System.out.println("Finished: " + state.isFinished); |
||||
if (state.hadFatalError) |
||||
System.out.println("Finished due to fatal error!"); |
||||
System.out.println("Frozen: " + state.isFrozen); |
||||
} |
||||
|
||||
private void simulate() { |
||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); |
||||
byte[] codeBytes = codeByteBuffer.array(); |
||||
byte[] dataBytes = new byte[0]; |
||||
|
||||
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); |
||||
|
||||
do { |
||||
execute(); |
||||
|
||||
// Bump block height
|
||||
state.currentBlockHeight++; |
||||
} while (!state.isFinished); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testPSH_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedUserStackPosition = (state.numUserStackPages - 1) * MachineState.USER_STACK_PAGE_SIZE; |
||||
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position()); |
||||
assertEquals("Data does not match", 4444L, state.userStackByteBuffer.getLong(expectedUserStackPosition)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPSH_DAT2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedUserStackPosition = (state.numUserStackPages - 2) * MachineState.USER_STACK_PAGE_SIZE; |
||||
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position()); |
||||
assertEquals("Data does not match", 3333L, state.userStackByteBuffer.getLong(expectedUserStackPosition)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPSH_DAToverflow() throws ExecutionException { |
||||
// User stack is 0x0010 entries in size, so exceed this to test overflow
|
||||
for (int i = 0; i < 20; ++i) { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(i).putLong(1000L * i); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(i); |
||||
} |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testPSH_DAToverflowWithOnError() throws ExecutionException { |
||||
int errorAddr = 0x16e; |
||||
|
||||
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr); |
||||
|
||||
// User stack is 0x0010 entries in size, so exceed this to test overflow
|
||||
for (int i = 0; i < 20; ++i) { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(i).putLong(1000L * i); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(i); |
||||
} |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
// errorAddr:
|
||||
assertEquals(errorAddr, codeByteBuffer.position()); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPOP_DAT() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * MachineState.USER_STACK_PAGE_SIZE; |
||||
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position()); |
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Stack entry not cleared", 0L, state.userStackByteBuffer.getLong(expectedUserStackPosition - MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPOP_DAT2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(3333L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(3); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertFalse(state.hadFatalError); |
||||
|
||||
int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * MachineState.USER_STACK_PAGE_SIZE; |
||||
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position()); |
||||
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE)); |
||||
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE)); |
||||
} |
||||
|
||||
@Test |
||||
public void testPOP_DAToverflow() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
@Test |
||||
public void testPOP_DAToverflow2() throws ExecutionException { |
||||
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L); |
||||
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1); |
||||
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2); |
||||
codeByteBuffer.put(OpCode.FIN_IMD.value); |
||||
|
||||
simulate(); |
||||
|
||||
assertTrue(state.isFinished); |
||||
assertTrue(state.hadFatalError); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,328 @@
|
||||
package common; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Random; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionData; |
||||
import org.ciyam.at.IllegalFunctionCodeException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.Timestamp; |
||||
|
||||
public class ACCTAPI implements API { |
||||
|
||||
private class Account { |
||||
public String address; |
||||
public long balance; |
||||
|
||||
public Account(String address, long amount) { |
||||
this.address = address; |
||||
this.balance = amount; |
||||
} |
||||
} |
||||
|
||||
private class Transaction { |
||||
public int txType; |
||||
public String creator; |
||||
public String recipient; |
||||
public long amount; |
||||
public long[] message; |
||||
} |
||||
|
||||
private class Block { |
||||
public List<Transaction> transactions; |
||||
|
||||
public Block() { |
||||
this.transactions = new ArrayList<Transaction>(); |
||||
} |
||||
} |
||||
|
||||
//
|
||||
private List<Block> blockchain; |
||||
private Map<String, Account> accounts; |
||||
private long balanceAT; |
||||
private long previousBalanceAT; |
||||
|
||||
//
|
||||
public ACCTAPI() { |
||||
// build blockchain
|
||||
this.blockchain = new ArrayList<Block>(); |
||||
|
||||
Block genesisBlock = new Block(); |
||||
this.blockchain.add(genesisBlock); |
||||
|
||||
// generate accounts
|
||||
this.accounts = new HashMap<String, Account>(); |
||||
|
||||
Account initiator = new Account("Initiator", 0); |
||||
this.accounts.put(initiator.address, initiator); |
||||
|
||||
Account responder = new Account("Responder", 10000); |
||||
this.accounts.put(responder.address, responder); |
||||
|
||||
Account bystander = new Account("Bystander", 999); |
||||
this.accounts.put(bystander.address, bystander); |
||||
|
||||
this.balanceAT = 50000; |
||||
this.previousBalanceAT = this.balanceAT; |
||||
} |
||||
|
||||
public void generateNextBlock(byte[] secret) { |
||||
Random random = new Random(); |
||||
|
||||
Block block = new Block(); |
||||
|
||||
System.out.println("Block " + (this.blockchain.size() + 1)); |
||||
|
||||
int transactionCount = random.nextInt(5); |
||||
|
||||
for (int i = 0; i < transactionCount; ++i) { |
||||
Transaction transaction = new Transaction(); |
||||
|
||||
transaction.txType = random.nextInt(2); |
||||
|
||||
switch (transaction.txType) { |
||||
case 0: // payment
|
||||
transaction.amount = random.nextInt(1000); |
||||
System.out.print("Payment Tx [" + transaction.amount + "]"); |
||||
break; |
||||
|
||||
case 1: // message
|
||||
System.out.print("Message Tx ["); |
||||
transaction.message = new long[4]; |
||||
|
||||
if (random.nextInt(3) == 0) { |
||||
// correct message
|
||||
transaction.message[0] = fromBytes(secret, 0); |
||||
transaction.message[1] = fromBytes(secret, 8); |
||||
transaction.message[2] = fromBytes(secret, 16); |
||||
transaction.message[3] = fromBytes(secret, 24); |
||||
} else { |
||||
// incorrect message
|
||||
transaction.message[0] = 0xdeadbeefdeadbeefL; |
||||
transaction.message[1] = 0xdeadbeefdeadbeefL; |
||||
transaction.message[2] = 0xdeadbeefdeadbeefL; |
||||
transaction.message[3] = 0xdeadbeefdeadbeefL; |
||||
} |
||||
System.out.print(String.format("%016x", transaction.message[0])); |
||||
System.out.print(String.format("%016x", transaction.message[1])); |
||||
System.out.print(String.format("%016x", transaction.message[2])); |
||||
System.out.print(String.format("%016x", transaction.message[3])); |
||||
System.out.print("]"); |
||||
break; |
||||
} |
||||
|
||||
transaction.creator = getRandomAccount(); |
||||
transaction.recipient = getRandomAccount(); |
||||
System.out.println(" from " + transaction.creator + " to " + transaction.recipient); |
||||
|
||||
block.transactions.add(transaction); |
||||
} |
||||
|
||||
this.blockchain.add(block); |
||||
|
||||
this.previousBalanceAT = this.balanceAT; |
||||
} |
||||
|
||||
/** Convert long to little-endian byte array */ |
||||
private byte[] toByteArray(long value) { |
||||
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40), |
||||
(byte) (value >> 48), (byte) (value >> 56) }; |
||||
} |
||||
|
||||
/** Convert part of little-endian byte[] to long */ |
||||
private long fromBytes(byte[] bytes, int start) { |
||||
return (bytes[start] & 0xffL) | (bytes[start + 1] & 0xffL) << 8 | (bytes[start + 2] & 0xffL) << 16 | (bytes[start + 3] & 0xffL) << 24 |
||||
| (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56; |
||||
} |
||||
|
||||
private String getRandomAccount() { |
||||
int numAccounts = this.accounts.size(); |
||||
int accountIndex = new Random().nextInt(numAccounts); |
||||
|
||||
List<Account> accounts = this.accounts.values().stream().collect(Collectors.toList()); |
||||
return accounts.get(accountIndex).address; |
||||
} |
||||
|
||||
@Override |
||||
public int getCurrentBlockHeight() { |
||||
return this.blockchain.size(); |
||||
} |
||||
|
||||
@Override |
||||
public int getATCreationBlockHeight(MachineState state) { |
||||
return 1; |
||||
} |
||||
|
||||
@Override |
||||
public void putPreviousBlockHashInA(MachineState state) { |
||||
state.a1 = this.blockchain.size() - 1; |
||||
state.a2 = state.a1; |
||||
state.a3 = state.a1; |
||||
state.a4 = state.a1; |
||||
} |
||||
|
||||
@Override |
||||
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) { |
||||
int blockHeight = timestamp.blockHeight; |
||||
int transactionSequence = timestamp.transactionSequence + 1; |
||||
|
||||
while (blockHeight <= this.blockchain.size()) { |
||||
Block block = this.blockchain.get(blockHeight - 1); |
||||
|
||||
List<Transaction> transactions = block.transactions; |
||||
|
||||
if (transactionSequence > transactions.size() - 1) { |
||||
// No more transactions at this height
|
||||
++blockHeight; |
||||
transactionSequence = 0; |
||||
continue; |
||||
} |
||||
|
||||
Transaction transaction = transactions.get(transactionSequence); |
||||
|
||||
if (transaction.recipient.equals("Initiator")) { |
||||
// Found a transaction
|
||||
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence); |
||||
|
||||
// Generate pseudo-hash of transaction
|
||||
state.a1 = new Timestamp(blockHeight, transactionSequence).longValue(); |
||||
state.a2 = state.a1; |
||||
state.a3 = state.a1; |
||||
state.a4 = state.a1; |
||||
return; |
||||
} |
||||
|
||||
++transactionSequence; |
||||
} |
||||
|
||||
// Nothing found
|
||||
state.a1 = 0L; |
||||
state.a2 = 0L; |
||||
state.a3 = 0L; |
||||
state.a4 = 0L; |
||||
} |
||||
|
||||
@Override |
||||
public long getTypeFromTransactionInA(MachineState state) { |
||||
Timestamp timestamp = new Timestamp(state.a1); |
||||
Block block = this.blockchain.get(timestamp.blockHeight - 1); |
||||
Transaction transaction = block.transactions.get(timestamp.transactionSequence); |
||||
return transaction.txType; |
||||
} |
||||
|
||||
@Override |
||||
public long getAmountFromTransactionInA(MachineState state) { |
||||
Timestamp timestamp = new Timestamp(state.a1); |
||||
Block block = this.blockchain.get(timestamp.blockHeight - 1); |
||||
Transaction transaction = block.transactions.get(timestamp.transactionSequence); |
||||
return transaction.amount; |
||||
} |
||||
|
||||
@Override |
||||
public long getTimestampFromTransactionInA(MachineState state) { |
||||
// Transaction hash in A is actually just 4 copies of transaction's "timestamp"
|
||||
Timestamp timestamp = new Timestamp(state.a1); |
||||
return timestamp.longValue(); |
||||
} |
||||
|
||||
@Override |
||||
public long generateRandomUsingTransactionInA(MachineState state) { |
||||
// NOT USED
|
||||
return 0L; |
||||
} |
||||
|
||||
@Override |
||||
public void putMessageFromTransactionInAIntoB(MachineState state) { |
||||
Timestamp timestamp = new Timestamp(state.a1); |
||||
Block block = this.blockchain.get(timestamp.blockHeight - 1); |
||||
Transaction transaction = block.transactions.get(timestamp.transactionSequence); |
||||
state.b1 = transaction.message[0]; |
||||
state.b2 = transaction.message[1]; |
||||
state.b3 = transaction.message[2]; |
||||
state.b4 = transaction.message[3]; |
||||
} |
||||
|
||||
@Override |
||||
public void putAddressFromTransactionInAIntoB(MachineState state) { |
||||
Timestamp timestamp = new Timestamp(state.a1); |
||||
Block block = this.blockchain.get(timestamp.blockHeight - 1); |
||||
Transaction transaction = block.transactions.get(timestamp.transactionSequence); |
||||
state.b1 = transaction.creator.charAt(0); |
||||
state.b2 = state.b1; |
||||
state.b3 = state.b1; |
||||
state.b4 = state.b1; |
||||
} |
||||
|
||||
@Override |
||||
public void putCreatorAddressIntoB(MachineState state) { |
||||
// Dummy creator
|
||||
state.b1 = "C".charAt(0); |
||||
state.b2 = state.b1; |
||||
state.b3 = state.b1; |
||||
state.b4 = state.b1; |
||||
} |
||||
|
||||
@Override |
||||
public long getCurrentBalance(MachineState state) { |
||||
return this.balanceAT; |
||||
} |
||||
|
||||
@Override |
||||
public long getPreviousBalance(MachineState state) { |
||||
return this.previousBalanceAT; |
||||
} |
||||
|
||||
@Override |
||||
public void payAmountToB(long value1, MachineState state) { |
||||
char firstChar = String.format("%c", state.b1).charAt(0); |
||||
Account recipient = this.accounts.values().stream().filter((account) -> account.address.charAt(0) == firstChar).findFirst().get(); |
||||
recipient.balance += value1; |
||||
System.out.println("Paid " + value1 + " to " + recipient.address + ", their balance now: " + recipient.balance); |
||||
this.balanceAT -= value1; |
||||
System.out.println("Our balance now: " + this.balanceAT); |
||||
} |
||||
|
||||
@Override |
||||
public void payCurrentBalanceToB(MachineState state) { |
||||
// NOT USED
|
||||
} |
||||
|
||||
@Override |
||||
public void payPreviousBalanceToB(MachineState state) { |
||||
// NOT USED
|
||||
} |
||||
|
||||
@Override |
||||
public void messageAToB(MachineState state) { |
||||
// NOT USED
|
||||
} |
||||
|
||||
@Override |
||||
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) { |
||||
timestamp.blockHeight += (int) minutes; |
||||
return timestamp.longValue(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFatalError(MachineState state, ExecutionException e) { |
||||
System.out.println("Fatal error: " + e.getMessage()); |
||||
System.out.println("No error address set - refunding to creator and finishing"); |
||||
} |
||||
|
||||
@Override |
||||
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException { |
||||
// NOT USED
|
||||
} |
||||
|
||||
@Override |
||||
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException { |
||||
// NOT USED
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,193 @@
|
||||
package common; |
||||
|
||||
import org.ciyam.at.API; |
||||
import org.ciyam.at.ExecutionException; |
||||
import org.ciyam.at.FunctionData; |
||||
import org.ciyam.at.IllegalFunctionCodeException; |
||||
import org.ciyam.at.MachineState; |
||||
import org.ciyam.at.Timestamp; |
||||
|
||||
public class TestAPI implements API { |
||||
|
||||
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
|
||||
|
||||
@Override |
||||
public int getCurrentBlockHeight() { |
||||
return 10; |
||||
} |
||||
|
||||
@Override |
||||
public int getATCreationBlockHeight(MachineState state) { |
||||
return 5; |
||||
} |
||||
|
||||
@Override |
||||
public void putPreviousBlockHashInA(MachineState state) { |
||||
state.a1 = 9L; |
||||
state.a2 = 9L; |
||||
state.a3 = 9L; |
||||
state.a4 = 9L; |
||||
} |
||||
|
||||
@Override |
||||
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) { |
||||
// Cycle through transactions: 1 -> 2 -> 3 -> 0 -> 1 ...
|
||||
state.a1 = (timestamp.transactionSequence + 1) % 4; |
||||
state.a2 = state.a1; |
||||
state.a3 = state.a1; |
||||
state.a4 = state.a1; |
||||
} |
||||
|
||||
@Override |
||||
public long getTypeFromTransactionInA(MachineState state) { |
||||
return 0L; |
||||
} |
||||
|
||||
@Override |
||||
public long getAmountFromTransactionInA(MachineState state) { |
||||
return 123L; |
||||
} |
||||
|
||||
@Override |
||||
public long getTimestampFromTransactionInA(MachineState state) { |
||||
return 1536227162000L; |
||||
} |
||||
|
||||
@Override |
||||
public long generateRandomUsingTransactionInA(MachineState state) { |
||||
if (state.steps != 0) { |
||||
// First call
|
||||
System.out.println("generateRandomUsingTransactionInA: first call - sleeping"); |
||||
|
||||
// Perform init?
|
||||
|
||||
state.isSleeping = true; |
||||
|
||||
return 0L; // not used
|
||||
} else { |
||||
// Second call
|
||||
System.out.println("generateRandomUsingTransactionInA: second call - returning random"); |
||||
|
||||
// HASH(A and new block hash)
|
||||
return (state.a1 ^ 9L) << 3 ^ (state.a2 ^ 9L) << 12 ^ (state.a3 ^ 9L) << 5 ^ (state.a4 ^ 9L); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void putMessageFromTransactionInAIntoB(MachineState state) { |
||||
state.b1 = state.a4; |
||||
state.b2 = state.a3; |
||||
state.b3 = state.a2; |
||||
state.b4 = state.a1; |
||||
} |
||||
|
||||
@Override |
||||
public void putAddressFromTransactionInAIntoB(MachineState state) { |
||||
// Dummy address
|
||||
state.b1 = 0xaaaaaaaaaaaaaaaaL; |
||||
state.b2 = 0xaaaaaaaaaaaaaaaaL; |
||||
state.b3 = 0xaaaaaaaaaaaaaaaaL; |
||||
state.b4 = 0xaaaaaaaaaaaaaaaaL; |
||||
} |
||||
|
||||
@Override |
||||
public void putCreatorAddressIntoB(MachineState state) { |
||||
// Dummy creator
|
||||
state.b1 = 0xccccccccccccccccL; |
||||
state.b2 = 0xccccccccccccccccL; |
||||
state.b3 = 0xccccccccccccccccL; |
||||
state.b4 = 0xccccccccccccccccL; |
||||
} |
||||
|
||||
@Override |
||||
public long getCurrentBalance(MachineState state) { |
||||
return 12345L; |
||||
} |
||||
|
||||
@Override |
||||
public long getPreviousBalance(MachineState state) { |
||||
return 10000L; |
||||
} |
||||
|
||||
@Override |
||||
public void payAmountToB(long value1, MachineState state) { |
||||
} |
||||
|
||||
@Override |
||||
public void payCurrentBalanceToB(MachineState state) { |
||||
} |
||||
|
||||
@Override |
||||
public void payPreviousBalanceToB(MachineState state) { |
||||
} |
||||
|
||||
@Override |
||||
public void messageAToB(MachineState state) { |
||||
} |
||||
|
||||
@Override |
||||
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) { |
||||
timestamp.blockHeight = ((int) minutes * 60) / BLOCK_PERIOD; |
||||
return timestamp.longValue(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFatalError(MachineState state, ExecutionException e) { |
||||
System.out.println("Fatal error: " + e.getMessage()); |
||||
System.out.println("No error address set - refunding to creator and finishing"); |
||||
} |
||||
|
||||
@Override |
||||
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException { |
||||
Integer requiredParamCount; |
||||
Boolean returnsValue; |
||||
|
||||
switch (functionCodeValue) { |
||||
case 0x0501: |
||||
// take one arg, no return value
|
||||
requiredParamCount = 1; |
||||
returnsValue = false; |
||||
break; |
||||
|
||||
case 0x0502: |
||||
// take no arg, return a value
|
||||
requiredParamCount = 0; |
||||
returnsValue = true; |
||||
break; |
||||
|
||||
default: |
||||
// Unrecognised platform-specific function code
|
||||
throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue)); |
||||
} |
||||
|
||||
if (requiredParamCount == null || returnsValue == null) |
||||
throw new IllegalFunctionCodeException("Error during platform-specific function pre-execute check"); |
||||
|
||||
if (paramCount != requiredParamCount) |
||||
throw new IllegalFunctionCodeException("Passed paramCount (" + paramCount + ") does not match platform-specific function code 0x" |
||||
+ String.format("%04x", functionCodeValue) + " required paramCount (" + requiredParamCount + ")"); |
||||
|
||||
if (returnValueExpected != returnsValue) |
||||
throw new IllegalFunctionCodeException("Passed returnValueExpected (" + returnValueExpected + ") does not match platform-specific function code 0x" |
||||
+ String.format("%04x", functionCodeValue) + " return signature (" + returnsValue + ")"); |
||||
} |
||||
|
||||
@Override |
||||
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException { |
||||
switch (functionCodeValue) { |
||||
case 0x0501: |
||||
System.out.println("Platform-specific function 0x0501 called with 0x" + String.format("%016x", functionData.value1)); |
||||
break; |
||||
|
||||
case 0x0502: |
||||
System.out.println("Platform-specific function 0x0502 called!"); |
||||
functionData.returnValue = 0x0502L; |
||||
break; |
||||
|
||||
default: |
||||
// Unrecognised platform-specific function code
|
||||
throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue)); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package common; |
||||
|
||||
import org.ciyam.at.LoggerInterface; |
||||
|
||||
public class TestLogger implements LoggerInterface { |
||||
@Override |
||||
public void error(String message) { |
||||
System.err.println("ERROR: " + message); |
||||
} |
||||
|
||||
@Override |
||||
public void debug(String message) { |
||||
System.err.println("DEBUG: " + message); |
||||
} |
||||
|
||||
@Override |
||||
public void echo(String message) { |
||||
System.err.println("ECHO: " + message); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@
|
||||
package common; |
||||
|
||||
import java.math.BigInteger; |
||||
|
||||
public class TestUtils { |
||||
|
||||
public static byte[] hexToBytes(String hex) { |
||||
byte[] output = new byte[hex.length() / 2]; |
||||
byte[] converted = new BigInteger("00" + hex, 16).toByteArray(); |
||||
|
||||
int convertedLength = Math.min(output.length, converted.length); |
||||
int convertedStart = converted.length - convertedLength; |
||||
|
||||
int outputStart = output.length - convertedLength; |
||||
|
||||
System.arraycopy(converted, convertedStart, output, outputStart, convertedLength); |
||||
|
||||
return output; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue