Initial commit

This commit is contained in:
SKBotNL committed 2025-11-17 20:29:49 +01:00
commit 9641d5e729
39 files changed
+10132

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.cache
build
tests/build
+3
View File
@@ -0,0 +1,3 @@
{
"cmake.generator": "Unix Makefiles"
}
+24
View File
@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.16)
project(substagent VERSION 0.1 LANGUAGES C)
set(BUILD_INFO_IN "${CMAKE_CURRENT_SOURCE_DIR}/src/build_info.h.in")
set(BUILD_INFO_OUT "${CMAKE_CURRENT_BINARY_DIR}/build_info.h")
add_custom_target(build_info ALL
COMMAND "${CMAKE_COMMAND}"
-DSRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
-P "${CMAKE_SOURCE_DIR}/cmake/gen_build_info.cmake"
BYPRODUCTS "${BUILD_INFO_OUT}"
COMMENT "Generating build_info.h with current git hash"
)
find_package(JNI REQUIRED)
add_library(substagent SHARED src/substagent.c)
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/external/stb)
target_include_directories(substagent PRIVATE ${JNI_INCLUDE_DIRS})
target_include_directories(substagent PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(substagent PRIVATE stb)
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
+10
View File
@@ -0,0 +1,10 @@
# SubstAgent-JVMTI
A Java Agent written in C that listens for strings containing environment variables and substitutes them accordingly. It is intended for use with Spigot plugins to enable environment variable support in their configuration files.
# How to use
`java -agentpath:/full/path/to/libsubstagent.so [rest of your command as normal]`\
For example:\
`java -agentpath:/full/path/to/libsubstagent.so -jar server.jar nogui`
# Tests
The test script assumes that the generator is set to make (`-G "Unix Makefiles"`)
+17
View File
@@ -0,0 +1,17 @@
execute_process(
COMMAND git rev-parse --short=10 HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_RESULT
)
if(NOT GIT_RESULT EQUAL 0)
set(GIT_HASH "unknown")
endif()
configure_file(
${SRC_DIR}/src/build_info.h.in
${CMAKE_CURRENT_BINARY_DIR}/build_info.h
@ONLY
)
+1895
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define GIT_HASH "@GIT_HASH@"
+353
View File
@@ -0,0 +1,353 @@
#include <errno.h>
#include <jvmti.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"
#include "build_info.h"
static jvmtiEnv* jvmti = NULL;
typedef jint (JNICALL *open0_t)(JNIEnv*, jobject, jstring);
typedef jint (JNICALL *readBytes_t)(JNIEnv*, jobject, jbyteArray, jint, jint);
static volatile readBytes_t real_readBytes = NULL;
static volatile open0_t real_open0 = NULL;
struct file_data{
signed char* data;
size_t data_len;
size_t offset;
};
struct file_entry{
jint key;
struct file_data value;
} *files_map = NULL;
// Check return value for .data == NULL
struct file_data make_file_data(const signed char* data, size_t len, size_t offset) {
struct file_data fd;
fd.data = malloc(len);
if (fd.data == NULL) {
fd.data_len = 0;
return fd;
}
memcpy(fd.data, data, len);
fd.data_len = len;
fd.offset = offset;
return fd;
}
static const jlong TAG = 1;
static void tag(jobject obj) {
if (obj && jvmti) {
(*jvmti)->SetTag(jvmti, obj, TAG);
}
}
static int has_tag(jobject obj) {
jlong tag = 0;
if (obj && jvmti) {
(*jvmti)->GetTag(jvmti, obj, &tag);
}
return tag == 1;
}
static int is_config_path(const char* path) {
char* extString = strrchr(path, '.');
if (extString) {
return strcmp(extString, ".yml") == 0 || strcmp(extString, ".yaml") == 0 || strcmp(extString, ".json") == 0 || strcmp(extString, ".txt") == 0;
}
return 0;
}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) < (b) ? (b) : (a))
static void substitute(const char* value, size_t value_len, int dollar_sign_matched_index, size_t i, ssize_t len_diff, jsize ba_length, signed char* arr, jsize* used_bytes, signed char* exceeded, size_t* exceeded_len) {
signed char before[dollar_sign_matched_index];
size_t after_len = MAX(ba_length - (ssize_t)i - len_diff, 0);
signed char after[after_len];
memcpy(before, arr, dollar_sign_matched_index);
memcpy(after, arr+i, after_len);
signed char result[ba_length];
memcpy(result, before, dollar_sign_matched_index);
memcpy(result + dollar_sign_matched_index, value, value_len);
memcpy(result + dollar_sign_matched_index + value_len, after, after_len);
if (len_diff < 0) {
// len_diff is negative so +- which equates to -
*used_bytes += len_diff;
} else if (len_diff > 0) {
ssize_t clamped_free_bytes = MAX(ba_length - *used_bytes, 0);
size_t already_copied_bytes = 0;
if (clamped_free_bytes > 0) {
already_copied_bytes = MIN(len_diff, clamped_free_bytes);
*used_bytes += already_copied_bytes;
}
size_t new_exceeded_len = *exceeded_len + len_diff - already_copied_bytes;
exceeded = realloc(exceeded, new_exceeded_len);
memcpy(exceeded, exceeded, *exceeded_len);
memcpy(exceeded+*exceeded_len, arr + (ba_length - len_diff) + already_copied_bytes, len_diff - already_copied_bytes);
*exceeded_len = new_exceeded_len;
}
memcpy(arr, result, ba_length);
}
static jint JNICALL readBytes_hook(JNIEnv* env, jobject thiz, jbyteArray buf, jint off, jint len) {
jclass class = (*env)->GetObjectClass(env, thiz);
jmethodID hash_code_method = (*env)->GetMethodID(env, class, "hashCode", "()I");
jint hash = (*env)->CallIntMethod(env, thiz, hash_code_method);
struct file_entry *fe = hmgetp(files_map, hash);
jint r = real_readBytes(env, thiz, buf, off, len);
if (!has_tag(thiz)) {
return r;
}
jmethodID length_method_id = (*env)->GetMethodID(env, class, "length", "()J");
jlong length = (*env)->CallLongMethod(env, thiz, length_method_id);
jmethodID position_method_id = (*env)->GetMethodID(env, class, "position", "()J");
jlong position = (*env)->CallLongMethod(env, thiz, position_method_id);
int last_data = length - position == 0;
jsize ba_length = (*env)->GetArrayLength(env, buf);
jbyte *ba = (*env)->GetByteArrayElements(env, buf, JNI_FALSE);
if (r == -1) {
if (fe && fe->key != hash) {
(*env)->ReleaseByteArrayElements(env, buf, ba, 0);
return -1;
} else {
struct file_data fd = fe->value;
size_t writeDataLen = MIN(fd.data_len, ba_length);
memcpy(ba, fd.data, writeDataLen);
if (ba_length < fd.data_len) {
signed char data[fd.data_len - ba_length];
memcpy(data, fd.data+ba_length, fd.data_len - ba_length);
struct file_data new_fd = make_file_data(data, fd.data_len-ba_length, 0);
if (new_fd.data == NULL) {
fprintf(stderr, "\e[0;31m[SubstAgent] Malloc failed\e[0m\n");
exit(EXIT_FAILURE);
}
hmput(files_map, hash, fd);
}
(*env)->ReleaseByteArrayElements(env, buf, ba, 0);
return writeDataLen;
}
}
jsize used_bytes = r;
if (ba) {
signed char* arr = malloc(ba_length);
signed char* exceeded = malloc(0);
size_t exceeded_len = 0;
if (fe->key == hash) {
struct file_data fd = fe->value;
ssize_t orig_fitting_data_len = ba_length - fd.data_len;
if (orig_fitting_data_len > 0) {
memcpy(arr+fd.data_len, ba, orig_fitting_data_len);
}
size_t dataLen = MIN(fd.data_len, ba_length);
ssize_t offset = 0;
if (off != 0) {
offset = fd.offset - off;
}
for (ssize_t i = offset; i < dataLen; i++) {
if (i < 0) continue;
arr[i] = fd.data[i];
}
ssize_t free_bytes = ba_length - used_bytes;
size_t already_copied_bytes = 0;
if (free_bytes > 0) {
already_copied_bytes = MIN(fd.data_len, free_bytes);
used_bytes += already_copied_bytes;
}
size_t left_over_exceeded = MAX((ssize_t)fd.data_len - (ssize_t)ba_length, 0);
exceeded = realloc(exceeded, left_over_exceeded + fd.data_len - left_over_exceeded);
memcpy(exceeded, fd.data+fd.data_len-left_over_exceeded, left_over_exceeded);
memcpy(exceeded+left_over_exceeded, ba+(ba_length-fd.data_len)+left_over_exceeded, fd.data_len-left_over_exceeded);
exceeded_len = left_over_exceeded + fd.data_len - left_over_exceeded;
} else {
memcpy(arr, ba, ba_length);
}
int dollar_sign_matched_index = -1;
char env_var[4096];
env_var[0] = '\0';
for (size_t i = 0; i < len; i++) {
unsigned char c = arr[i];
if (dollar_sign_matched_index != -1) {
if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 95) {
size_t len = strlen(env_var);
env_var[len] = c;
env_var[len + 1] = '\0';
} else {
if (strlen(env_var) == 0) continue;
char* value = getenv(env_var);
if (!value) {
dollar_sign_matched_index = -1;
env_var[0] = '\0';
continue;
}
size_t env_len = i - dollar_sign_matched_index;
size_t value_len = strlen(value);
ssize_t len_diff = value_len - env_len;
substitute(value, value_len, dollar_sign_matched_index, i, len_diff, ba_length, arr, &used_bytes, exceeded, &exceeded_len);
dollar_sign_matched_index = -1;
env_var[0] = '\0';
continue;
}
} else {
if (c == '$') {
dollar_sign_matched_index = i;
}
}
}
if (last_data && dollar_sign_matched_index != -1) {
if (strlen(env_var) != 0) {
char* value = getenv(env_var);
if (value) {
size_t env_len = len - dollar_sign_matched_index;
size_t value_len = strlen(value);
ssize_t len_diff = value_len - env_len;
substitute(value, value_len, dollar_sign_matched_index, len, len_diff, ba_length, arr, &used_bytes, exceeded, &exceeded_len);
}
}
}
if (exceeded_len > 0) {
struct file_data fd = make_file_data(exceeded, exceeded_len, ba_length);
hmput(files_map, hash, fd);
}
memcpy(ba, arr, ba_length);
}
(*env)->ReleaseByteArrayElements(env, buf, ba, 0);
return used_bytes;
}
static void open0_hook(JNIEnv* env, jobject thiz, jstring jpath) {
if (!jpath) {
return;
}
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
if (path) {
if (is_config_path(path)) {
tag(thiz);
}
(*env)->ReleaseStringUTFChars(env, jpath, path);
}
real_open0(env, thiz, jpath);
}
static void dealloc(jvmtiEnv* jvmti, char* p) {
if (p != NULL) {
unsigned char* up = (unsigned char*)p;
(*jvmti)->Deallocate(jvmti, up);
}
}
static void JNICALL onNativeMethodBind(jvmtiEnv* jvmti_env, JNIEnv* env, jthread thread, jmethodID method, void* address, void** new_address_ptr) {
char* mname = NULL;
char* msig = NULL;
char* csig = NULL;
char* gsig = NULL;
jclass decl;
(*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &decl);
(*jvmti_env)->GetClassSignature(jvmti_env, decl, &csig, &gsig);
(*jvmti_env)->GetMethodName(jvmti_env, method, &mname, &msig, NULL);
if (csig && strcmp(csig, "Ljava/io/FileInputStream;") == 0 && mname && msig) {
if (strcmp(mname, "open0") == 0 && strcmp(msig, "(Ljava/lang/String;)V") == 0) {
real_open0 = (open0_t)address;
*new_address_ptr = (void*)&open0_hook;
} else if (strcmp(mname, "readBytes") == 0 && strcmp(msig, "([BII)I") == 0) {
real_readBytes = (readBytes_t)address;
*new_address_ptr = (void*)&readBytes_hook;
}
}
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)mname);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)msig);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)csig);
(*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)gsig);
}
static jint init_jvmti(JavaVM* vm) {
jint rc = (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION_1_2);
if (rc != JNI_OK || jvmti == NULL) return JNI_ERR;
jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_generate_native_method_bind_events = 1;
caps.can_tag_objects = 1;
(*jvmti)->AddCapabilities(jvmti, &caps);
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.NativeMethodBind = &onNativeMethodBind;
(*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, (jthread)NULL);
FILE *env_file = fopen(".env", "r");
if (env_file) {
char* line = NULL;
size_t len = 0;
ssize_t n;
while ((n = getline(&line, &len, env_file)) != -1) {
if (n > 0 && line[n - 1] == '\n') {
line[n - 1] = '\0';
}
char *name = line;
char *value = NULL;
char *equal = strchr(line, '=');
if (equal == NULL) {
fprintf(stderr, "\e[0;31m[SubstAgent] Invalid line in .env: \"%s\"\e[0m\n", line);
continue;
}
*equal = '\0';
value = equal + 1;
if (setenv(name, value, 0) != 0) {
perror("setenv failed");
}
}
} else {
if (errno != ENOENT) {
perror("[SubstAgent] fopen failed");
exit(EXIT_FAILURE);
}
}
fprintf(stderr, "[SubstAgent] Loaded version %s\n", GIT_HASH);
return JNI_OK;
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
return init_jvmti(vm);
}
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
return init_jvmti(vm);
}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm) {}
+3
View File
@@ -0,0 +1,3 @@
SAME_LENGTH_ENV=abcdefghijklmno
SHORTER_ENV=abcdefghijklmnopqrstuvwxyz
LONGER_ENV=abcdef
+40
View File
@@ -0,0 +1,40 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SAME_LENGTH_ENV where everything fits in one byte array
class Test1 {
static String correctString = "Hello abcdefghijklmno";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test1.txt");
byte[] buf = new byte[256];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SAME_LENGTH_ENV at the end of the file
class Test10 {
static String correctString = "According to all known laws of aviation, there is no way a bee should be able to fly abcdefghijklmno";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test10.txt");
byte[] buf = new byte[101];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SHORTER_ENV at the end of the file
class Test11 {
static String correctString = "According to all known laws of aviation, there is no way a bee should be able to fly abcdefghijklmnopqrstuvwxyz";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test11.txt");
byte[] buf = new byte[111];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test overflowing $SHORTER_ENV
class Test12 {
static String correctFirstString = "abcdefghijkl";
static String correctSecondString = "mnopqrstuvwxyz";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test12.txt");
byte[] firstBuf = new byte[12];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[256];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(secondContentString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test file that is not a config file
class Test13 {
static String correctString = "This is not a config file so $SAME_LENGTH_ENV shouldn't be substituted";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test13.nacf");
byte[] buf = new byte[256];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import java.io.FileInputStream;
import java.io.IOException;
// Test reading really big file
class Test14 {
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test14.txt");
byte[] buf = new byte[2097152];
fis.read(buf);
} catch (IOException e) {
throw e;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SHORTER_ENV where everything fits in one byte array
class Test2 {
static String correctString = "Hello abcdefghijklmnopqrstuvwxyz";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test2.txt");
byte[] buf = new byte[256];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $LONGER_ENV where everything fits in one byte array
class Test3 {
static String correctString = "Hello abcdef";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test3.txt");
byte[] buf = new byte[256];
int n = fis.read(buf);
byte[] content = Arrays.copyOfRange(buf, 0, n);
String contentString = new String(content);
if (contentString.equals(correctString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("Content string was \"%s\", should be \"%s\"\n", contentString, correctString);
System.out.printf("Content string hex: \"%s\"\n", toHexDump(contentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SAME_LENGTH_ENV split in 2 byte arrays, only the read content overflows
class Test4 {
static String correctFirstString = "Hi abcdefghijklmno ";
static String correctSecondString = "According to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test4.txt");
byte[] firstBuf = new byte[20];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[256];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.printf("Incorrect output. Was \"%s\" should be \"%s\"\n", firstContentString + secondContentString, correctFirstString + correctSecondString);
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SHORTER_ENV split in 2 byte arrays, only the read content overflows
class Test5 {
static String correctFirstString = "Hi abcdefghijklmnopqrstuvwxyz ";
static String correctSecondString = "According to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test5.txt");
byte[] firstBuf = new byte[30];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[256];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $LONGER_ENV split in 2 byte arrays, only the read content overflows
class Test6 {
static String correctFirstString = "Hi abcdef ";
static String correctSecondString = "According to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test6.txt");
byte[] firstBuf = new byte[15];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[256];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SAME_LENGTH_ENV split in 3 byte arrays, the entirety of the read content overflows
class Test7 {
static String correctFirstString = "Hi abcdefghijklmno ";
static String correctSecondString = "According ";
static String correctThirdString = "to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test7.txt");
byte[] firstBuf = new byte[20];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[10];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
byte[] thirdBuf = new byte[256];
int thirdN = fis.read(thirdBuf);
byte[] thirdContent = Arrays.copyOfRange(thirdBuf, 0, thirdN);
String thirdContentString = new String(thirdContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString) && thirdContentString.equals(correctThirdString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.printf("Third content string was \"%s\", should be \"%s\"\n", thirdContentString, correctThirdString);
System.out.printf("Third content string hex: \"%s\"\n", toHexDump(thirdContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $SHORTER_ENV split in 3 byte arrays, the entirety of the read content overflows
class Test8 {
static String correctFirstString = "Hi abcdefghijklmnopqrstuvwxyz ";
static String correctSecondString = "According ";
static String correctThirdString = "to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test5.txt");
byte[] firstBuf = new byte[30];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[10];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
byte[] thirdBuf = new byte[256];
int thirdN = fis.read(thirdBuf);
byte[] thirdContent = Arrays.copyOfRange(thirdBuf, 0, thirdN);
String thirdContentString = new String(thirdContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString) && thirdContentString.equals(correctThirdString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.printf("Third content string was \"%s\", should be \"%s\"\n", thirdContentString, correctThirdString);
System.out.printf("Third content string hex: \"%s\"\n", toHexDump(thirdContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
// Test $LONGER_ENV split in 3 byte arrays, the entirety of the read content overflows
class Test9 {
static String correctFirstString = "Hi abcdef ";
static String correctSecondString = "According ";
static String correctThirdString = "to all known laws of aviation, there is no way a bee should be able to fly";
public static String toHexDump(String s) {
byte[] bytes = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X ", bytes[i] & 0xFF));
}
return sb.toString().trim();
}
@SuppressWarnings("CallToPrintStackTrace")
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream("../data/test9.txt");
byte[] firstBuf = new byte[15];
int firstN = fis.read(firstBuf);
byte[] firstContent = Arrays.copyOfRange(firstBuf, 0, firstN);
String firstContentString = new String(firstContent);
byte[] secondBuf = new byte[10];
int secondN = fis.read(secondBuf);
byte[] secondContent = Arrays.copyOfRange(secondBuf, 0, secondN);
String secondContentString = new String(secondContent);
byte[] thirdBuf = new byte[256];
int thirdN = fis.read(thirdBuf);
byte[] thirdContent = Arrays.copyOfRange(thirdBuf, 0, thirdN);
String thirdContentString = new String(thirdContent);
if (firstContentString.equals(correctFirstString) && secondContentString.equals(correctSecondString) && thirdContentString.equals(correctThirdString)) {
System.err.println("Correct output");
System.exit(0);
} else {
System.out.println("=== Incorrect output ===");
System.out.printf("First content string was \"%s\", should be \"%s\"\n", firstContentString, correctFirstString);
System.out.printf("First content string hex: \"%s\"\n", toHexDump(firstContentString));
System.out.printf("Second content string was \"%s\", should be \"%s\"\n", secondContentString, correctSecondString);
System.out.printf("Second content string hex: \"%s\"\n", toHexDump(secondContentString));
System.out.printf("Third content string was \"%s\", should be \"%s\"\n", thirdContentString, correctThirdString);
System.out.printf("Third content string hex: \"%s\"\n", toHexDump(thirdContentString));
System.out.println("========================");
System.exit(1);
}
} catch (IOException e) {
throw e;
}
}
}
+1
View File
@@ -0,0 +1 @@
Hello $SAME_LENGTH_ENV
+1
View File
@@ -0,0 +1 @@
According to all known laws of aviation, there is no way a bee should be able to fly $SAME_LENGTH_ENV
+1
View File
@@ -0,0 +1 @@
According to all known laws of aviation, there is no way a bee should be able to fly $SHORTER_ENV
+1
View File
@@ -0,0 +1 @@
$SHORTER_ENV
+1
View File
@@ -0,0 +1 @@
This is not a config file so $SAME_LENGTH_ENV shouldn't be substituted
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
Hello $SHORTER_ENV
+1
View File
@@ -0,0 +1 @@
Hello $LONGER_ENV
+1
View File
@@ -0,0 +1 @@
Hi $SAME_LENGTH_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+1
View File
@@ -0,0 +1 @@
Hi $SHORTER_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+1
View File
@@ -0,0 +1 @@
Hi $LONGER_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+1
View File
@@ -0,0 +1 @@
Hi $SAME_LENGTH_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+1
View File
@@ -0,0 +1 @@
Hi $SHORTER_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+1
View File
@@ -0,0 +1 @@
Hi $LONGER_ENV According to all known laws of aviation, there is no way a bee should be able to fly
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
set -u
TESTS_COUNT=14
TEST_DESCRIPTIONS=(
"\$SAME_LENGTH_ENV where everything fits in one byte array"
"\$SHORTER_ENV where everything fits in one byte array"
"\$LONGER_ENV where everything fits in one byte array"
"\$SAME_LENGTH_ENV split in 2 byte arrays, only the read content overflows"
"\$SHORTER_ENV split in 2 byte arrays, only the read content overflows"
"\$LONGER_ENV split in 2 byte arrays, only the read content overflows"
"\$SAME_LENGTH_ENV split in 3 byte arrays, the entirety of the read content overflows"
"\$SHORTER_ENV split in 3 byte arrays, the entirety of the read content overflows"
"\$LONGER_ENV split in 3 byte arrays, the entirety of the read content overflows"
"\$SAME_LENGTH_ENV at the end of the file"
"\$SHORTER_ENV at the end of the file"
"Overflowing \$SHORTER_ENV"
"File that is not a config file"
"Reading really big file"
)
# Build SubstAgent
echo -n "Compiling SubstAgent..."
cd ../build
output=$(make 2>&1)
if [ $? -ne 0 ]; then
echo -e "\e[0;31mBuild failed\e[0m"
echo "$output"
exit 1
fi
echo -ne "\e[2K\r"
echo "Done compiling SubstAgent"
cd ../tests
substagent_path=$(realpath "../build/libsubstagent.so")
# Cleanup
rm -rf ./build
mkdir ./build
# Compile tests
echo -n "Compiling test "
tests_count_digits=${#TESTS_COUNT}
for (( TEST=1; TEST<=TESTS_COUNT; TEST++ )); do
echo -n "$TEST/$TESTS_COUNT"
javac Test$TEST.java -d ./build
if [ $? -ne 0 ]; then
echo -e "\e[0;31mCould not compile test $TEST\e[0m"
exit 1
fi
test_digits=${#TEST}
shift_left=$(( tests_count_digits + test_digits + 1 ))
echo -ne "\e[${shift_left}D"
done
echo -ne "\e[2K\r"
echo "Done compiling tests"
cp .env ./build
# Run tests
cd ./build
a_test_has_failed=false
for (( TEST=1; TEST<=TESTS_COUNT; TEST++ )); do
output=$(java -agentpath:$substagent_path Test$TEST 2>&1)
if [ $? -eq 0 ]; then
echo -e "\e[0;32m✓ Test $TEST passed - ${TEST_DESCRIPTIONS[$TEST-1]}\e[0m"
else
echo -e "\e[0;31m✗ Test $TEST failed - ${TEST_DESCRIPTIONS[$TEST-1]}\e[0m"
a_test_has_failed=true
echo "$output"
fi
done
echo "=================="
if [ "$a_test_has_failed" == true ] ; then
echo -e "\e[0;31m✗ One or more tests failed\e[0m"
else
echo -e "\e[0;32m✓ All tests passed\e[0m"
fi