diff --git a/microscript/ILibDuktape_fs.c b/microscript/ILibDuktape_fs.c index 0f96663..c62e9b7 100644 --- a/microscript/ILibDuktape_fs.c +++ b/microscript/ILibDuktape_fs.c @@ -341,18 +341,22 @@ duk_ret_t ILibDuktape_fs_read_readsetSink(duk_context *ctx) duk_array_pop(ctx, -1); // [DescriptorEvents][pending][options] duk_size_t bufferLen; - char *buffer = Duktape_GetBufferProperty(ctx, -1, "buffer"); + char *buffer = Duktape_GetBufferPropertyEx(ctx, -1, "buffer", &bufferLen); duk_size_t offset = (duk_size_t)Duktape_GetIntPropertyValue(ctx, -1, "offset", 0); duk_size_t length = (duk_size_t)Duktape_GetIntPropertyValue(ctx, -1, "length", (int)bufferLen); +#ifdef WIN32 + int bytesRead = _read(fd, buffer + offset, (unsigned int)length); +#else int bytesRead = read(fd, buffer + offset, length); +#endif int status = bytesRead < 0 ? errno : 0; duk_get_prop_string(ctx, -1, "callback"); // [DescriptorEvents][pending][options][callback] - duk_get_prop_string(ctx, -1, "call"); duk_swap_top(ctx, -2);// ..............[pending][options][call][this] - duk_eval_string(ctx, "require('fs');"); // ..............[pending][options][call][fn/this][this] - duk_push_int(ctx, status); // ..............[pending][options][call][fn/this][this][err/status] - duk_push_int(ctx, bytesRead); // ..............[pending][options][call][fn/this][this][err/status][bytesRead] - duk_get_prop_string(ctx, -6, "buffer"); // ..............[pending][options][call][fn/this][this][err/status][bytesRead][buffer] + duk_eval_string(ctx, "require('fs');"); // ..............[pending][options][callback][this] + duk_push_int(ctx, status); // ..............[pending][options][callback][this][err/status] + duk_push_int(ctx, bytesRead); // ..............[pending][options][callback][this][err/status][bytesRead] + duk_get_prop_string(ctx, -5, "buffer"); // ..............[pending][options][callback][this][err/status][bytesRead][buffer] + duk_dup(ctx, -6); // ..............[pending][options][callback][this][err/status][bytesRead][buffer][options] if (duk_pcall_method(ctx, 4) != 0) // [DescriptorEvents][pending][options][val] { ILibDuktape_Process_UncaughtExceptionEx(ctx, "fs.read() Callback Error: %s ", duk_safe_to_string(ctx, -1)); @@ -373,6 +377,172 @@ duk_ret_t ILibDuktape_fs_read_readsetSink(duk_context *ctx) } return(0); } +duk_ret_t ILibDuktape_fs_write_writeset_sink(duk_context *ctx) +{ + int fd = (int)duk_require_int(ctx, 0); + duk_push_this(ctx); // [events] + duk_get_prop_string(ctx, -1, FS_EVENT_DESCRIPTORS_IO); // [events][pending] + duk_size_t bufferLen; + char *buffer = Duktape_GetBufferPropertyEx(ctx, -1, "buffer", &bufferLen); + int performCleanup = 0; +#ifdef WIN32 + int bytesWritten = _write(fd, buffer, (unsigned int)bufferLen); +#else + int bytesWritten = write(fd, buffer, bufferLen); +#endif + if (bytesWritten == bufferLen) + { + // Complete + duk_del_prop_string(ctx, -2, FS_EVENT_DESCRIPTORS_IO); + duk_get_prop_string(ctx, -1, "callback"); // [events][pending][callback] + duk_eval_string(ctx, "require('fs');"); // [events][pending][callback][this] + duk_push_int(ctx, 0); // [events][pending][callback][this][status] + duk_get_prop_string(ctx, -5, "original"); // [events][pending][callback][this][status][buffer] + duk_get_length(ctx, -1); // [events][pending][callback][this][status][buffer][length] + duk_swap_top(ctx, -2); // [events][pending][callback][this][status][bytesWritten][buffer] + duk_get_prop_string(ctx, -6, "opt"); // [events][pending][callback][this][status][bytesWritten][buffer][options] + if (duk_pcall_method(ctx, 4) != 0) // [events][pending][ret] + { + ILibDuktape_Process_UncaughtExceptionEx(ctx, "fs.write() Callback Error: %s ", duk_safe_to_string(ctx, -1)); + } + duk_pop_2(ctx); // [events] + if (!duk_has_prop_string(ctx, -1, FS_EVENT_DESCRIPTORS_IO)) + { + performCleanup = 1; + } + } + else + { + if (bytesWritten > 0) // [events][pending] + { + duk_get_prop_string(ctx, -1, "buffer"); // [events][pending][buffer] + duk_buffer_slice(ctx, -1, bytesWritten, (int)bufferLen - bytesWritten); // ....][buffer][sliced] + duk_put_prop_string(ctx, -3, "buffer"); // [events][pending][oldBuffer] + } + else + { + int e = errno; + if (e != EAGAIN && e != EWOULDBLOCK && e != EINTR) // [events][pending] + { + // Error occured + duk_del_prop_string(ctx, -2, FS_EVENT_DESCRIPTORS_IO); + performCleanup = 1; + + duk_get_prop_string(ctx, -1, "callback"); // [events][pending][callback] + duk_eval_string(ctx, "require('fs');"); // [events][pending][callback][this] + duk_push_int(ctx, e); // [events][pending][callback][this][status] + duk_get_prop_string(ctx, -5, "original"); // [events][pending][callback][this][status][buffer] + duk_push_int(ctx, 0); // [events][pending][callback][this][status][buffer][written] + duk_swap_top(ctx, -2); // [events][pending][callback][this][status][bytesWritten][buffer] + duk_get_prop_string(ctx, -6, "opt"); // [events][pending][callback][this][status][bytesWritten][buffer][options] + if (duk_pcall_method(ctx, 4) != 0) // [events][pending][ret] + { + ILibDuktape_Process_UncaughtExceptionEx(ctx, "fs.write() Callback Error: %s ", duk_safe_to_string(ctx, -1)); + } + } + } + } + if (performCleanup != 0) + { + // No more pending writes, so we can do some cleanup + duk_eval_string(ctx, "require('fs');"); // ... [fs] + duk_get_prop_string(ctx, -1, FS_EVENT_W_DESCRIPTORS); // ... [fs][table] + duk_del_prop_index(ctx, -1, fd); // ... [fs][table] + + // And we can also unhook ourselves + duk_eval_string(ctx, "require('EventDescriptors');"); // ... [eventdescriptors] + duk_get_prop_string(ctx, -1, "removeDescriptor"); // ... [eventdescriptors][remove] + duk_swap_top(ctx, -2); // ... [remove][this] + duk_push_int(ctx, fd); // ... [remove][this][fd] + duk_call_method(ctx, 1); // ... [ret] + } + return(0); +} +duk_ret_t ILibDuktape_fs_write(duk_context *ctx) +{ + int top = duk_get_top(ctx); + int fd = (int)duk_require_int(ctx, 0); + duk_size_t bufferLen; + char *buffer = Duktape_GetBuffer(ctx, 1, &bufferLen); + int cbx = 2; + int offset = 0, length = (int)bufferLen, e; + int position = -1; + if (duk_is_number(ctx, 2)) { offset = (int)duk_require_int(ctx, 2); cbx++; } + if (duk_is_number(ctx, 3)) { length = (int)duk_require_int(ctx, 3); cbx++; } + if (duk_is_number(ctx, 4)) { position = (int)duk_require_int(ctx, 4); cbx++; } + if (!duk_is_function(ctx, cbx)) { return(ILibDuktape_Error(ctx, "Invalid Parameters")); } + +#ifdef WIN32 + int bytesWritten = _write(fd, buffer + offset, length); +#else + int bytesWritten = write(fd, buffer + offset, length); +#endif + if (bytesWritten == length) + { + // Completed + duk_require_function(ctx, cbx); // [func] + duk_push_this(ctx); // [func][this] + duk_push_int(ctx, 0); // [func][this][ERR] + duk_push_int(ctx, bytesWritten); // [func][this][ERR][bytesWritten] + duk_dup(ctx, 1); // [func][this][ERR][bytesWritten][buffer] + duk_dup(ctx, cbx + 1); // [func][this][ERR][bytesWritten][buffer][options] + duk_call_method(ctx, 4); + return(0); + } + if (bytesWritten > 0 || (e = errno) == EAGAIN || e == EWOULDBLOCK || e == EINTR) + { + // Partial Write + duk_push_this(ctx); // [fs] + duk_get_prop_string(ctx, -1, FS_EVENT_W_DESCRIPTORS); // [fs][table] + if (!duk_has_prop_index(ctx, -1, fd)) + { + duk_eval_string(ctx, "require('DescriptorEvents');"); // [fs][table][DescriptorEvents] + duk_get_prop_string(ctx, -1, "addDescriptor"); // [fs][table][DescriptorEvents][add] + duk_swap_top(ctx, -2); // [fs][table][add][this] + duk_push_int(ctx, fd); // [fs][table][add][this][fd] + duk_push_object(ctx); // [fs][table][add][this][fd][options] + duk_push_true(ctx); duk_put_prop_string(ctx, -2, "writeset"); // ...[add][this][fd][options] + if (duk_is_object(ctx, cbx + 1) && duk_has_prop_string(ctx, cbx + 1, "metadata")) + { + duk_push_string(ctx, "fs.write(), "); // [fs][table][add][this][fd][options][str1] + duk_get_prop_string(ctx, cbx + 1, "metadata"); // [fs][table][add][this][fd][options][str1][str2] + duk_string_concat(ctx, -2); duk_remove(ctx, -2); // [fs][table][add][this][fd][options][metadata] + } + else + { + duk_push_string(ctx, "fs.write()"); // [fs][table][add][this][fd][options][metadata] + } + duk_put_prop_string(ctx, -2, "metadata"); // [fs][table][add][this][fd][options] + duk_call_method(ctx, 2); // [fs][table][events] + ILibDuktape_EventEmitter_SetupOn(ctx, duk_get_heapptr(ctx, -1), "writeset"); // [on][this][writeset] + duk_push_c_function(ctx, ILibDuktape_fs_write_writeset_sink, DUK_VARARGS); // [on][this][writeset][func] + duk_call_method(ctx, 2); duk_pop(ctx); // [fs][table][events] ............................. + duk_put_prop_index(ctx, -2, fd); // [fs][table] + } + duk_get_prop_index(ctx, -1, fd); // [fs][table][events] + duk_push_object(ctx); // [fs][table][events][pending] + if (duk_is_object(ctx, cbx + 1)) { duk_dup(ctx, cbx + 1); duk_put_prop_string(ctx, -2, "opt"); } + duk_dup(ctx, 1); // [fs][table][events][pending][buffer] + duk_buffer_slice(ctx, -1, offset + length, length - offset);// [fs][table][events][pending][buffer][sliced] + duk_dup(ctx, -1); // [fs][table][events][pending][buffer][sliced][dup] + duk_put_prop_string(ctx, -4, "buffer"); // [fs][table][events][pending][buffer][sliced] + duk_put_prop_string(ctx, -3, "original"); // [fs][table][events][pending][buffer] + duk_pop(ctx); // [fs][table][events][pending] + duk_dup(ctx, cbx); duk_put_prop_string(ctx, -2, "callback");// [fs][table][events][pending] + duk_put_prop_string(ctx, -2, FS_EVENT_DESCRIPTORS_IO); // [fs][table][events] + return(0); + } + + // ERROR + duk_require_function(ctx, cbx); // [func] + duk_push_this(ctx); // [func][this] + duk_push_int(ctx, e); // [func][this][ERR] + duk_push_int(ctx, bytesWritten); // [func][this][ERR][bytesWritten] + duk_dup(ctx, 1); // [func][this][ERR][bytesWritten][buffer] + duk_dup(ctx, cbx + 1); // [func][this][ERR][bytesWritten][buffer][options] + duk_call_method(ctx, 4); + return(0); +} duk_ret_t ILibDuktape_fs_read(duk_context *ctx) { int top = duk_get_top(ctx); @@ -416,21 +586,24 @@ duk_ret_t ILibDuktape_fs_read(duk_context *ctx) // First, we'll attempt to read, and see if it completes or is pending duk_size_t bufferLen; - char *buffer = Duktape_GetBufferProperty(ctx, 1, "buffer"); + char *buffer = Duktape_GetBufferPropertyEx(ctx, 1, "buffer", &bufferLen); duk_size_t offset = (duk_size_t)Duktape_GetIntPropertyValue(ctx, 1, "offset", 0); duk_size_t length = (duk_size_t)Duktape_GetIntPropertyValue(ctx, 1, "length", (int)bufferLen); +#ifdef WIN32 + int bytesRead = _read(fd, buffer + offset, (unsigned int)length); +#else int bytesRead = read(fd, buffer + offset, length); - if (bytesRead >= 0 || (errno != E_AGAIN && errno != E_WOULDBLOCK)) +#endif + if (bytesRead >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { // Completed int errStatus = bytesRead >= 0 ? 0 : errno; duk_dup(ctx, 2); // [func] - duk_get_prop_string(ctx, -1, "call"); // [func][call] - duk_swap_top(ctx, -2); // [call][this] - duk_push_this(ctx); // [call][this][this/fs] - duk_push_int(ctx, errStatus); // [call][this][this/fs][err/status] - duk_push_int(ctx, bytesRead); // [call][this][this/fs][err/status][bytesRead] - duk_get_prop_string(ctx, 1, "buffer"); // [call][this][this/fs][err/status][bytesRead][buffer] + duk_push_this(ctx); // [func][this] + duk_push_int(ctx, errStatus); // [func][this][err/status] + duk_push_int(ctx, bytesRead); // [func][this][err/status][bytesRead] + duk_get_prop_string(ctx, 1, "buffer"); // [func][this][err/status][bytesRead][buffer] + duk_dup(ctx, 3); // [func][this][err/status][bytesRead][buffer][options] duk_call_method(ctx, 4); return(0); } @@ -1830,6 +2003,7 @@ void ILibDuktape_fs_PUSH(duk_context *ctx, void *chain) ILibDuktape_CreateInstanceMethod(ctx, "readSync", ILibDuktape_fs_readSync, 5); ILibDuktape_CreateInstanceMethod(ctx, "writeSync", ILibDuktape_fs_writeSync, DUK_VARARGS); ILibDuktape_CreateInstanceMethod(ctx, "read", ILibDuktape_fs_read, DUK_VARARGS); + ILibDuktape_CreateInstanceMethod(ctx, "write", ILibDuktape_fs_write, DUK_VARARGS); #ifdef WIN32 ILibDuktape_CreateInstanceMethod(ctx, "_readdirSync", ILibDuktape_fs_readdirSync, DUK_VARARGS); ILibDuktape_CreateInstanceMethod(ctx, "_statSync", ILibDuktape_fs_statSync, 1); diff --git a/modules/amt-mei.js b/modules/amt-mei.js index e06eee1..304ac35 100644 --- a/modules/amt-mei.js +++ b/modules/amt-mei.js @@ -398,20 +398,25 @@ function amt_heci() { fn.apply(this, opt); }, callback, optional); } - this.getProtocolVersion = function getProtocolVersion(callback) { + this.getProtocolVersion = function getProtocolVersion(callback) + { var optional = []; for (var i = 1; i < arguments.length; ++i) { opt.push(arguments[i]); } - heci.doIoctl(heci.IOCTL.HECI_VERSION, Buffer.alloc(5), Buffer.alloc(5), function (status, buffer, self, fn, opt) { + if (!this._tmpSession) { this._tmpSession = heci.create(); this._tmpSession.parent = this;} + this._tmpSession.doIoctl(heci.IOCTL.HECI_VERSION, Buffer.alloc(5), Buffer.alloc(5), function (status, buffer, self, fn, opt) + { if (status == 0) { var result = buffer.readUInt8(0).toString() + '.' + buffer.readUInt8(1).toString() + '.' + buffer.readUInt8(2).toString() + '.' + buffer.readUInt16BE(3).toString(); opt.unshift(result); fn.apply(self, opt); } - else { + else + { opt.unshift(null); fn.apply(self, opt); } + }, this, callback, optional); } } diff --git a/modules/heci.js b/modules/heci.js index bcdb39a..5c64283 100644 --- a/modules/heci.js +++ b/modules/heci.js @@ -60,16 +60,24 @@ function heci_create() 'write': function (chunk, flush) { console.log('write:' + chunk.length + ' on ' + this._hashCode()); - if (this._writeoverlapped == null) { throw ('Not Connected'); } if (chunk.length > this.MaxBufferSize) { throw ('Buffer too large'); } + + if (process.platform == 'win32') + { + if (this._writeoverlapped == null) { throw ('Not Connected'); } + } this._pendingWrites.unshift({ buffer: chunk, flush: flush }); + if (this._pendingWrites.length == 1) { // Kickstart the write this._processWrite(); } - return (false); + if (process.platform == 'win32') + { + return (false); + } }, 'final': function (flush) { @@ -78,7 +86,16 @@ function heci_create() 'read': function(size) { console.log('read: (' + size + ') on ' + this._hashCode()); - if (!this._readbuffer) { this._readbuffer = GM.CreateVariable(this.MaxBufferSize); } + if (!this._readbuffer) + { + this._readbuffer = process.platform == 'win32' ? GM.CreateVariable(this.MaxBufferSize) : Buffer.alloc(this.MaxBufferSize); + } + + if (process.platform == 'linux') + { + this._processRead(); + return; + } var result = kernel32.ReadFile(this._descriptor, this._readbuffer, this._readbuffer._size, 0, this._readoverlapped); if(result.Val != 0 || result._LastError == ERROR_IO_PENDING) @@ -194,7 +211,13 @@ function heci_create() }) .addMethod('descriptorPath', function _descriptorPath() { - console.log(' heci.createDescriptor()'); + if (process.platform == 'linux') + { + if (require('fs').existsSync('/dev/mei')) { return ('/dev/mei'); } + if (require('fs').existsSync('/dev/mei0')) { return ('/dev/mei0'); } + throw ('HECI not supported'); + } + if (process.platform != 'win32') { throw ('HECI not supported'); } var result; var ii; @@ -248,6 +271,12 @@ function heci_create() }) .addMethod('createDescriptor', function _createDescriptor(path) { + if (process.platform == 'linux') + { + return (require('fs').openSync(path, require('fs').constants.O_RDWR | require('fs').constants.O_NONBLOCK)); + } + if (process.platform != 'win32') { throw ('HECI not supported'); } + var devPath = GM.CreateVariable(path); var ret = kernel32.CreateFileA(devPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (ret.Val == -1) @@ -327,7 +356,8 @@ function heci_create() ret.doIoctl = function doIoctl(code, inputBuffer, outputBuffer, callback) { console.log('doIoctl()'); - if(typeof(callback)!='function') { throw('Callback not specified');} + if (typeof (callback) != 'function') { throw ('Callback not specified'); } + var i; var parms = []; for (i = 4; i < arguments.length; ++i) @@ -335,6 +365,20 @@ function heci_create() parms.push(arguments[i]); } + + if (process.platform == 'linux') + { + if (outputBuffer.length < inputBuffer.length) { throw ('output buffer is too small'); } + outputBuffer.fill(0); + inputBuffer.copy(outputBuffer); + if (this._descriptor == null) { this._descriptor = this.createDescriptor(this.descriptorPath()); } + var ret = require('ioctl')(this._descriptor, code, outputBuffer); + parms.unshift(outputBuffer); + parms.unshift(ret); + callback.apply(this, parms); + return; + } + this._ioctls.unshift({ code: code, input: inputBuffer, output: outputBuffer, callback: callback, parms: parms }); if(this._ioctls.length == 1) { @@ -342,6 +386,7 @@ function heci_create() this._send(this._ioctls.peek()); } }; + ret._send = function _send(options) { if(this._descriptor == null) @@ -406,24 +451,46 @@ function heci_create() { var chunk = this._pendingWrites.peek(); console.log('_WRITING: ' + chunk.buffer.length + ' bytes' + ' on ' + this._hashCode()); - if (chunk.buffer.length == 23) + + if (process.platform == 'win32') { - console.log(chunk.buffer.toString('hex')); - GM.CreateVariable(chunk.buffer)._debug(); - } - var result = kernel32.WriteFile(this._descriptor, GM.CreateVariable(chunk.buffer), chunk.buffer.length, 0, this._writeoverlapped); - if(result.Val != 0 || result._LastError == ERROR_IO_PENDING) - { - if(!this._wDescriptorEvent) + var result = kernel32.WriteFile(this._descriptor, GM.CreateVariable(chunk.buffer), chunk.buffer.length, 0, this._writeoverlapped); + if (result.Val != 0 || result._LastError == ERROR_IO_PENDING) { - this._wDescriptorEvent = require('DescriptorEvents').addDescriptor(this._writeoverlapped.hEvent, { metadata: 'heci.session [write]' }); - this._wDescriptorEvent.session = this; - this._wDescriptorEvent.on('signaled', this._processWrite_signaled); + if (!this._wDescriptorEvent) + { + this._wDescriptorEvent = require('DescriptorEvents').addDescriptor(this._writeoverlapped.hEvent, { metadata: 'heci.session [write]' }); + this._wDescriptorEvent.session = this; + this._wDescriptorEvent.on('signaled', this._processWrite_signaled); + } + } + else + { + console.log('Write Error: ' + result._LastError); } } - else + + require('fs').write(this._descriptor, chunk.buffer, ret._processWrite_linux_signaled, { metadata: 'heci.session', session: this }); + }; + ret._processWrite_linux_signaled = function _processWrite_linux_signaled(status, bytesWritten, buffer, options) + { + if(status == 0) { - console.log('Write Error: ' + result._LastError); + console.log(bytesWritten + ' bytes written'); + console.log('noPipeline = ' + options.session._options.noPipeline, options.session._pendingWrites.length); + if (options.session._options.noPipeline == null || options.session._options.noPipeline == false) + { + var item = options.session._pendingWrites.pop(); + if (options.session._pendingWrites.length > 0) + { + options.session._processWrite(); + } + else + { + console.log('Write/Flush'); + item.flush(); + } + } } }; ret._processWrite_signaled = function _processWrite_signaled(status) @@ -455,12 +522,67 @@ function heci_create() } } }; + ret._processRead_readSet_sink = function _processRead_readSet_sink(status, bytesRead, buffer, options) + { + if (status != 0) { options.session.push(null); return; } + console.log(bytesRead + ' bytes read'); + + buffer = buffer.slice(0, bytesRead); + var pushResult = options.session.push(buffer); + if (options.session._options.noPipeline != 0 && options.session._pendingWrites.length > 0) + { + // Unlock a write + console.log('pendingWriteCount: ' + options.session._pendingWrites.length); + var item = options.session._pendingWrites.pop(); + + if (options.session._pendingWrites.length > 0) + { + options.session._processWrite(); + } + else + { + console.log('Write/Flush'); + item.flush(); + } + } + + if (pushResult) + { + // We can read more, because data is still flowing + console.log('READING MORE on ' + options.session._hashCode()); + var result = kernel32.ReadFile(this.session._descriptor, this.session._readbuffer, this.session._readbuffer._size, 0, this.session._readoverlapped); + if (result.Val != 0 || result._LastError == ERROR_IO_PENDING) + { + options.session._processRead(); + return (true); + } + else + { + console.log('Sometype of error: ' + result._LastError); + this.session.push(null); + } + } + + }; + ret._processRead = function _processRead() + { + require('fs').read(this._descriptor, { metadata: 'heci.session', buffer: this._readbuffer, session: this }, _processRead_readSet_sink); + }; return (ret); } var ioctl = {}; -Object.defineProperty(ioctl, 'HECI_VERSION', { value: 0x8000E000 }); -Object.defineProperty(ioctl, 'CLIENT_CONNECT', { value: 0x8000E004 }); +if(process.platform == 'win32') +{ + Object.defineProperty(ioctl, 'HECI_VERSION', { value: 0x8000E000 }); + Object.defineProperty(ioctl, 'CLIENT_CONNECT', { value: 0x8000E004 }); +} +if(process.platform == 'linux') +{ + Object.defineProperty(ioctl, 'HECI_VERSION', { value: 0x00 }); + Object.defineProperty(ioctl, 'CLIENT_CONNECT', { value: 0x01 }); +} + var guids = {}; Object.defineProperty(guids, 'AMT', { value: Buffer.from('2800F812B7B42D4BACA846E0FF65814C', 'hex') }); Object.defineProperty(guids, 'LME', { value: Buffer.from('DBA4336776047B4EB3AFBCFC29BEE7A7', 'hex') });