From 4a5f1d4d078826a4945382af50c4bcd9b1faa7e5 Mon Sep 17 00:00:00 2001 From: ionut Date: Sun, 3 May 2026 23:30:18 +0300 Subject: [PATCH] data card and stuff anything but device info --- TODO.md | 1 - src/main.c | 8 +- src/neonucleus.c | 219 +++++++++++++++++++++++++++++++++++++++++++++-- src/neonucleus.h | 103 ++++++++++++++++++++++ 4 files changed, 323 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index d006ed4..72199fa 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,6 @@ # For MVP functionality - make `computer` component use callbacks -- make `NN_ENV_POWERON`, `NN_ENV_POWEROFF` and `NN_ENV_CRASHED` actually get called - finish tmpfs (rework the whole thing) - device info - userdata support diff --git a/src/main.c b/src/main.c index cab1bde..5b7eb93 100644 --- a/src/main.c +++ b/src/main.c @@ -501,7 +501,11 @@ int main(int argc, char **argv) { ncl_mountKeyboard(scrstate, "mainKB"); nn_Computer *c = nn_createComputer(u, NULL, NULL, ramTotal, 256, 256); - nn_setComputerEnvironment(c, (nn_Environment) {.userdata = NULL, .handler = ne_env}); + nn_Environment cEnv = { + .userdata = NULL, + .handler = ne_env, + }; + nn_setComputerEnvironment(c, cEnv); nn_setCallBudget(c, 0); nn_setTotalEnergy(c, allEnergy); @@ -693,6 +697,8 @@ int main(int argc, char **argv) { nn_removeEnergy(c, ncl_getScreenEnergyUsage(nn_getComponentState(screen))); if(noIdle) nn_resetIdleTime(c); + // OC computers consume 0.5W when running, 0.05W when running but idle + nn_removeEnergy(c, nn_isComputerIdle(c) ? 0.05 : 0.5); nn_Exit e = nn_tick(c); if(e != NN_OK) { printf("error: %s\n", nn_getError(c)); diff --git a/src/neonucleus.c b/src/neonucleus.c index 7fa6f68..ee65ae4 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -113,11 +113,6 @@ void nn_free(nn_Context *ctx, void *memory, size_t size) { } void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize) { - // nn_realloc passed memory (which is NULL here) as first argument - // to nn_alloc instead of ctx. nn_alloc dereferences it as a context - // struct to call ctx->alloc(), so this is a NULL pointer dereference. - // Confirmed by test_realloc crashing on nn_realloc(&ctx, NULL, 0, 64). - // Original: if(memory == NULL) return nn_alloc(memory, newSize); if(memory == ctx->alloc) return nn_alloc(memory, newSize); if(memory == NULL) return nn_alloc(ctx, newSize); if(memory == ctx->alloc) return nn_alloc(ctx, newSize); if(newSize == 0) { @@ -1221,6 +1216,11 @@ nn_Exit nn_startComputer(nn_Computer *computer) { return err; } computer->archState = req.localState; + nn_EnvironmentRequest envreq; + envreq.userdata = computer->env.userdata; + envreq.computer = computer; + envreq.action = NN_ENV_POWERON; + computer->env.handler(&envreq); return NN_OK; } @@ -1234,6 +1234,12 @@ void nn_stopComputer(nn_Computer *computer) { req.action = NN_ARCH_DEINIT; computer->arch.handler(&req); computer->archState = NULL; + + nn_EnvironmentRequest envreq; + envreq.userdata = computer->env.userdata; + envreq.computer = computer; + envreq.action = NN_ENV_POWEROFF; + computer->env.handler(&envreq); } computer->state = NN_BOOTUP; for(size_t i = 0; i < computer->signalCount; i++) { @@ -1609,6 +1615,11 @@ nn_Exit nn_tick(nn_Computer *computer) { if(err) { computer->state = NN_CRASHED; nn_setErrorFromExit(computer, err); + nn_EnvironmentRequest envreq; + envreq.userdata = computer->env.userdata; + envreq.computer = computer; + envreq.action = NN_ENV_CRASHED; + computer->env.handler(&envreq); return err; } return NN_OK; @@ -1628,6 +1639,11 @@ nn_Exit nn_tickSynchronized(nn_Computer *computer) { if(err) { computer->state = NN_CRASHED; nn_setErrorFromExit(computer, err); + nn_EnvironmentRequest envreq; + envreq.userdata = computer->env.userdata; + envreq.computer = computer; + envreq.action = NN_ENV_CRASHED; + computer->env.handler(&envreq); return err; } return NN_OK; @@ -5842,6 +5858,197 @@ nn_Component *nn_createGPU( return c; } +typedef enum nn_DataNum { + NN_DATANUM_GETLIMIT, + + NN_DATANUM_DECODE64, + + NN_DATANUM_ENCODE64, + + NN_DATANUM_CRC32, + + NN_DATANUM_MD5, + + NN_DATANUM_SHA256, + + NN_DATANUM_DEFLATE, + + NN_DATANUM_INFLATE, + + NN_DATANUM_ENCRYPT, + + NN_DATANUM_DECRYPT, + + NN_DATANUM_RANDOM, + + NN_DATANUM_MAXRANDOM, + + NN_DATANUM_GENKEYPAIR, + + NN_DATANUM_ECDSA, + + NN_DATANUM_ECDH, + + NN_DATANUM_DESERIALIZEKEY, + + NN_DATANUM_COUNT, +} nn_DataNum; + +typedef struct nn_DataState { + nn_Context *ctx; + nn_DataCard dataCard; + nn_DataCardHandler *handler; +} nn_DataState; + +nn_DataCard nn_defaultDataCards[3] = { + NN_INIT(nn_DataCard) { + .limit = NN_MiB, + .maxRandom = NN_KiB, + .canHash = true, + .canEncrypt = false, + .canECDH = false, + .canCompress = true, + .trivialCost = 0.2, + .trivialCostByte = 0.005, + .simpleCost = 1.0, + .simpleCostByte = 0.01, + .complexCost = 6.0, + .complexCostByte = 0.1, + .assymetricCost = 10.0, + }, + NN_INIT(nn_DataCard) { + .limit = NN_MiB, + .maxRandom = NN_KiB, + .canHash = true, + .canEncrypt = true, + .canECDH = false, + .canCompress = true, + .trivialCost = 0.2, + .trivialCostByte = 0.005, + .simpleCost = 1.0, + .simpleCostByte = 0.01, + .complexCost = 6.0, + .complexCostByte = 0.1, + .assymetricCost = 10.0, + }, + NN_INIT(nn_DataCard) { + .limit = NN_MiB, + .maxRandom = NN_KiB, + .canHash = true, + .canEncrypt = true, + .canECDH = true, + .canCompress = true, + .trivialCost = 0.2, + .trivialCostByte = 0.005, + .simpleCost = 1.0, + .simpleCostByte = 0.01, + .complexCost = 6.0, + .complexCostByte = 0.1, + .assymetricCost = 10.0, + }, +}; +static nn_Exit nn_dataHandler(nn_ComponentRequest *req) { + if(req->action == NN_COMP_SIGNAL) return NN_OK; + nn_Context *ctx = req->ctx; + nn_DataState *state = req->classState; + nn_Computer *C = req->computer; + + nn_DataCard dataCard = state->dataCard; + nn_DataNum method = req->methodIdx; + + if(req->action == NN_COMP_CHECKMETHOD) { + if(method == NN_DATANUM_SHA256 || method == NN_DATANUM_MD5) { + req->methodEnabled = dataCard.canHash; + } + if(method == NN_DATANUM_DEFLATE || method == NN_DATANUM_INFLATE || method == NN_DATANUM_RANDOM || method == NN_DATANUM_MAXRANDOM) { + req->methodEnabled = dataCard.canCompress; + } + if(method == NN_DATANUM_ENCRYPT || method == NN_DATANUM_DECRYPT) { + req->methodEnabled = dataCard.canEncrypt; + } + if(method == NN_DATANUM_GENKEYPAIR || method == NN_DATANUM_ECDH || method == NN_DATANUM_ECDSA || method == NN_DATANUM_DESERIALIZEKEY) { + req->methodEnabled = dataCard.canECDH; + } + return NN_OK; + } + + nn_DataCardRequest dreq; + dreq.ctx = ctx; + dreq.computer = C; + dreq.state = req->state; + dreq.dataCard = &state->dataCard; + nn_Exit e; + + if(req->action == NN_COMP_DROP) { + dreq.action = NN_DATA_DROP; + state->handler(&dreq); + nn_free(ctx, state, sizeof(*state)); + return NN_OK; + } + + if(method == NN_DATANUM_GETLIMIT) { + req->returnCount = 1; + return nn_pushinteger(C, dataCard.limit); + } + if(method == NN_DATANUM_MAXRANDOM) { + req->returnCount = 1; + return nn_pushinteger(C, dataCard.maxRandom); + } + + // TODO: the cool methods + + if(C) nn_setError(C, "data: not implemented yet"); + return NN_EBADCALL; +} + +nn_Component *nn_createDataCard(nn_Universe *universe, const char *address, const nn_DataCard *dataCard, void *state, nn_DataCardHandler *handler) { + nn_Component *c = nn_createComponent( + universe, address, "data"); + if(c == NULL) return NULL; + + nn_Method methods[NN_DATANUM_COUNT] = { + [NN_DATANUM_GETLIMIT] = {"getLimit", "function(): integer - Get the buffer capacity of the card", NN_DIRECT}, + [NN_DATANUM_DECODE64] = {"decode64", "function(data: string): string - Decodes the string as base64 data", NN_DIRECT}, + [NN_DATANUM_ENCODE64] = {"encode64", "function(data: string): string - Encodes the string into base64 data", NN_DIRECT}, + [NN_DATANUM_CRC32] = {"crc32", "function(data: string): string - Computes the CRC-32 hash of the data", NN_DIRECT}, + [NN_DATANUM_MD5] = {"md5", "function(data: string): string - Computes the MD5 hash of the data", NN_DIRECT}, + [NN_DATANUM_SHA256] = {"sha256", "function(data: string): string - Computes the SHA256 hash of the data", NN_DIRECT}, + [NN_DATANUM_DEFLATE] = {"deflate", "function(data: string): string - Compresses the data", NN_DIRECT}, + [NN_DATANUM_INFLATE] = {"deflate", "function(data: string): string - Decompresses the compressed data", NN_DIRECT}, + [NN_DATANUM_ENCRYPT] = {"encrypt", "function(data: string, key: string, iv: string): string - Encrypts the data", NN_DIRECT}, + [NN_DATANUM_DECRYPT] = {"decrypt", "function(data: string, key: string, iv: string): string - Decrypts the data", NN_DIRECT}, + [NN_DATANUM_RANDOM] = {"random", "function(size: integer): string - Generates an amount of secure random bytes", NN_DIRECT}, + [NN_DATANUM_MAXRANDOM] = {"getRandomLimit", "function(): integer - Maximum amount of secure random bytes the data card will generate", NN_DIRECT}, + [NN_DATANUM_GENKEYPAIR] = {"generateKeyPair", "function(bitLen?: integer): userdata, userdata - Generates a pair of 2 ECDH keys; ec-public and ec-private respectively. Supports 256-bit and 384-bit keys.", NN_DIRECT}, + [NN_DATANUM_ECDSA] = {"ecdsa", "function(data: string, key: userdata, sig: string?): string or boolean - Either generates a signature using a private key, or if signature is specified, verifies it with public key", NN_DIRECT}, + [NN_DATANUM_ECDH] = {"ecdh", "function(privateKey: userdata, publicKey: userdata): string - Computes a shared secret using a private and public key from different pairs. The rule is, ecdh(a.private, b.public) == ecdh(b.private, a.public)", NN_DIRECT}, + [NN_DATANUM_DESERIALIZEKEY] = {"deserializeKey", "function(data: string, type: string): userdata - Deserializes key data assuming a specific type. Public keys are of type ec-public, and private ones of type ec-private.", NN_DIRECT}, + }; + + if(dataCard->canEncrypt && dataCard->canHash) { + methods[NN_DATANUM_SHA256].doc = "function(data: string, hmacKey: string?): string - Computes the SHA256 hash / HMAC"; + methods[NN_DATANUM_MD5].doc = "function(data: string, hmacKey: string?): string - Computes the MD5 hash / HMAC"; + } + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_DATANUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_DataState *cls = nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->dataCard = *dataCard; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_dataHandler); + return c; +} + typedef enum nn_ModemNum { NN_MODEMNUM_ISWIRED, NN_MODEMNUM_ISWIRELESS, @@ -5934,7 +6141,7 @@ static nn_Exit nn_modemHandler(nn_ComponentRequest *req) { nn_Component *nn_createModem(nn_Universe *universe, const char *address, const nn_Modem *modem, void *state, nn_ModemHandler *handler) { nn_Component *c = nn_createComponent( - universe, address, "gpu"); + universe, address, "modem"); if(c == NULL) return NULL; const nn_Method methods[NN_MODEMNUM_COUNT] = { diff --git a/src/neonucleus.h b/src/neonucleus.h index b43c6a2..e6497f1 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -1594,6 +1594,109 @@ nn_Component *nn_createScreen( nn_ScreenHandler *handler ); +typedef struct nn_DataCard { + // The buffer size of the data card, limit in both input and output. + // This buffer is allocated on the heap before the call, thus setting it to be very large will lead to huge *spikes* in memory usage. + // In OC, this value is 1MiB regardless of tier. + size_t limit; + + // The maximum amount of secure random bytes that can be generated. + // In OC, this was hardcoded to 1024. Here, its configurable. + // Unlike the normal limit, this does not preallocate the maximum capacity, + // as the amount of bytes needed is known perfectly. + size_t maxRandom; + + // Capabilities + bool canHash; + bool canEncrypt; + bool canECDH; + bool canCompress; + + // Trivial operation cost (CRC32 and base64 encoding/decoding) + double trivialCost; + double trivialCostByte; + + // Simple operation cost (MD5 and encryption/decryption) + double simpleCost; + double simpleCostByte; + + double complexCost; + double complexCostByte; + + // Assymetric operation cost (ECDH, ECDSA). ECDH has no per-byte cost, and ECDSA uses complexCostByte as it relies on SHA256. + double assymetricCost; +} nn_DataCard; + +typedef enum nn_DataCardAction { + // Data card destroyed + NN_DATA_DROP, + + // If you want to match the behavior of OC, which you should if you want + // data compressed or encrypted in OC to work in your emulators, you should + // aim to match what the JVM, com.google.common.hash.Hashing and javax.crypto do. + // For more details, see https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/scala/li/cil/oc/server/component/DataCard.scala + + // encoding base64 + NN_DATA_ENCODE64, + + NN_DATA_DECODE64, + // hashing + + // CRC32, little endian + NN_DATA_CRC32, + + // SHA2-256 hash, optional HMAC key (javax.crypto HmacSHA256) + NN_DATA_SHA256, + + // MD5 hash, optional HMAC key (javax.crypto HmacMD5) + NN_DATA_MD5, + + // Deflate. To match OC, make it follow the ZLIB format, which has a 2 byte header. + NN_DATA_DEFLATE, + + // Inflate. Should support the ZLIB format, as thats what OC uses, and GZIP support is optional. + NN_DATA_INFALTE, + + // Encrypt data with AES-128. The full algorithm is AES/CBC/PKCS5, as is use PKCS5 for padding, CBC for block sequences, and AES-128 for encrypting blocks. + // It does also receive a 128-bit AES Initialization Vector, for better security. + NN_DATA_ENCRYPT, + + // Decrypt data, also using AES/CBC/PKCS5. + NN_DATA_DECRYPT, + + // Meant to be *secure RNG*, and can generate anywhere between 1 and the data card's maxRandom. + NN_DATA_RANDOM, + + // Generate an ECDH public/private pair of either 256 or 384 bits each. + NN_DATA_GENKEYS, + + // Does an ECDH pass, matching javax.crypto.KeyAgreement. + // This generates a shared secret as binary data. + // This is not an AES key as output, the AES key is often computed by either MD5 hashing + // the shared secret, or SHA256 hashing it and chopping the hash in half. + NN_DATA_ECDH, + + // ECDSA algorithm, sign data using an ECDH (private) key. + NN_DATA_ECDSA_SIGN, + // ECDSA algorithm, verify data using an ECDH (public) key and signature. + NN_DATA_ECDSA_VERIFY, +} nn_DataCardAction; + +typedef struct nn_DataCardRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_DataCard *dataCard; + nn_DataCardAction action; + // TODO: the fields +} nn_DataCardRequest; + +typedef nn_Exit (nn_DataCardHandler)(nn_DataCardRequest *req); + +extern nn_DataCard nn_defaultDataCards[3]; + +nn_Component *nn_createDataCard(nn_Universe *universe, const char *address, const nn_DataCard *dataCard, void *state, nn_DataCardHandler *handler); + typedef struct nn_Modem { // maximum range. Set to 0 for non-wireless modems size_t maxRange;