diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/embind/embind.js | 619 | ||||
-rw-r--r-- | src/embind/emval.js | 111 | ||||
-rw-r--r-- | src/library.js | 263 | ||||
-rw-r--r-- | src/library_browser.js | 24 | ||||
-rw-r--r-- | src/library_sdl.js | 1 | ||||
-rw-r--r-- | src/settings.js | 1 |
6 files changed, 1006 insertions, 13 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js new file mode 100644 index 00000000..fff19d86 --- /dev/null +++ b/src/embind/embind.js @@ -0,0 +1,619 @@ +/*global Module*/ +/*global _malloc, _free, _memcpy*/ +/*global FUNCTION_TABLE, HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global __emval_register, _emval_handle_array, __emval_decref*/ + +function createNamedFunction(name, body) { + /*jshint evil:true*/ + return new Function( + "body", + "return function " + name + "() {\n" + + " return body.apply(this, arguments);\n" + + "};\n" + )(body); +} + +function _embind_repr(v) { + var t = typeof v; + if (t === 'object' || t === 'array' || t === 'function') { + return v.toString(); + } else { + return '' + v; + } +} + +var typeRegistry = {}; + +function validateType(type, name) { + if (!type) { + throw new BindingError('type "' + name + '" must have a positive integer typeid pointer'); + } + if (undefined !== typeRegistry[type]) { + throw new BindingError('cannot register type "' + name + '" twice'); + } +} + +function __embind_register_void(voidType, name) { + name = Pointer_stringify(name); + validateType(voidType, name); + typeRegistry[voidType] = { + name: name, + fromWireType: function() { + return undefined; + } + }; +} + +function __embind_register_bool(boolType, name, trueValue, falseValue) { + name = Pointer_stringify(name); + validateType(boolType, name); + typeRegistry[boolType] = { + name: name, + toWireType: function(destructors, o) { + return o ? trueValue : falseValue; + }, + fromWireType: function(wt) { + return wt === trueValue; + }, + }; +} + +function __embind_register_integer(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value | 0; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_float(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_cstring(stringType, name) { + name = Pointer_stringify(name); + validateType(stringType, name); + typeRegistry[stringType] = { + name: name, + toWireType: function(destructors, value) { + var ptr = _malloc(value.length + 1); + writeStringToMemory(value, ptr); + destructors.push(_free); + destructors.push(ptr); + return ptr; + }, + fromWireType: function(value) { + var rv = Pointer_stringify(value); + _free(value); + return rv; + } + }; +} + +function __embind_register_emval(emvalType, name) { + name = Pointer_stringify(name); + validateType(emvalType, name); + typeRegistry[emvalType] = { + name: name, + toWireType: function(destructors, value) { + return __emval_register(value); + }, + fromWireType: function(handle) { + var rv = _emval_handle_array[handle].value; + __emval_decref(handle); + return rv; + } + }; +} + +var BindingError = Error; +/** @expose */ +Module.BindingError = BindingError; + +function typeName(typeID) { + // could use our carnal knowledge of RTTI but for now just return the pointer... + return typeID; +} + +function requireRegisteredType(type, humanName) { + var impl = typeRegistry[type]; + if (undefined === impl) { + throw new BindingError(humanName + " has unknown type: " + typeName(type)); + } + return impl; +} + +function requireArgumentTypes(argCount, argTypes, name) { + var argTypeImpls = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = HEAP32[(argTypes >> 2) + i]; + argTypeImpls[i] = requireRegisteredType(argType, name + " parameter " + i); + } + return argTypeImpls; +} + +function runDestructors(destructors) { + while (destructors.length) { + var ptr = destructors.pop(); + var del = destructors.pop(); + del(ptr); + } +} + +function __embind_register_function(name, returnType, argCount, argTypes, invoker, fn) { + name = Pointer_stringify(name); + returnType = requireRegisteredType(returnType, "Function " + name + " return value"); + invoker = FUNCTION_TABLE[invoker]; + argTypes = requireArgumentTypes(argCount, argTypes, name); + + Module[name] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding function ' + name + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount + 1); + args[0] = fn; + for (var i = 0; i < argCount; ++i) { + args[i + 1] = argTypes[i].toWireType(destructors, arguments[i]); + } + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_tuple(tupleType, name, constructor, destructor) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + var elements = []; + + typeRegistry[tupleType] = { + name: name, + elements: elements, + fromWireType: function(ptr) { + var len = elements.length; + var rv = new Array(len); + for (var i = 0; i < len; ++i) { + rv[i] = elements[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var len = elements.length; + if (len !== o.length) { + throw new TypeError("Incorrect number of tuple elements"); + } + var ptr = constructor(); + for (var i = 0; i < len; ++i) { + elements[i].write(ptr, o[i]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function copyMemberPointer(memberPointer, memberPointerSize) { + var copy = _malloc(memberPointerSize); + if (!copy) { + throw new Error('Failed to allocate member pointer copy'); + } + _memcpy(copy, memberPointer, memberPointerSize); + return copy; +} + +function __embind_register_tuple_element( + tupleType, + elementType, + getter, + setter, + memberPointerSize, + memberPointer +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_tuple_element_accessor( + tupleType, + elementType, + staticGetter, + getterSize, + getter, + staticSetter, + setterSize, + setter +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + staticGetter = FUNCTION_TABLE[staticGetter]; + getter = copyMemberPointer(getter, getterSize); + staticSetter = FUNCTION_TABLE[staticSetter]; + setter = copyMemberPointer(setter, setterSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(staticGetter(ptr, HEAP32[getter >> 2])); + }, + write: function(ptr, o) { + var destructors = []; + staticSetter( + ptr, + HEAP32[setter >> 2], + elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_struct( + structType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[structType] = { + fields: {}, + fromWireType: function(ptr) { + var fields = this.fields; + var rv = {}; + for (var i in fields) { + rv[i] = fields[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var fields = this.fields; + for (var fieldName in fields) { + if (!(fieldName in o)) { + throw new TypeError('Missing field'); + } + } + var ptr = constructor(); + for (var fieldName in fields) { + fields[fieldName].write(ptr, o[fieldName]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function __embind_register_struct_field( + structType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + structType = requireRegisteredType(structType, 'struct'); + fieldName = Pointer_stringify(fieldName); + fieldType = requireRegisteredType(fieldType, 'field "' + structType.name + '.' + fieldName + '"'); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + structType.fields[fieldName] = { + read: function(ptr) { + return fieldType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, fieldType.toWireType(destructors, o)); + runDestructors(destructors); + } + }; +} + +function __embind_register_class( + classType, + name, + destructor +) { + name = Pointer_stringify(name); + destructor = FUNCTION_TABLE[destructor]; + + var Handle = createNamedFunction(name, function(ptr) { + this.count = {value: 1}; + this.ptr = ptr; + }); + + Handle.prototype.clone = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + var clone = Object.create(Handle.prototype); + clone.count = this.count; + clone.ptr = this.ptr; + + clone.count.value += 1; + return clone; + }; + + Handle.prototype.move = function() { + var rv = this.clone(); + this.delete(); + return rv; + }; + + Handle.prototype['delete'] = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + this.count.value -= 1; + if (0 === this.count.value) { + destructor(this.ptr); + } + this.ptr = undefined; + }; + + var constructor = createNamedFunction(name, function() { + var body = constructor.body; + body.apply(this, arguments); + }); + constructor.prototype = Object.create(Handle.prototype); + + typeRegistry[classType] = { + name: name, + constructor: constructor, + Handle: Handle, + fromWireType: function(ptr) { + return new Handle(ptr); + }, + toWireType: function(destructors, o) { + return o.ptr; + } + }; + + Module[name] = constructor; +} + +function __embind_register_class_constructor( + classType, + argCount, + argTypes, + constructor +) { + classType = requireRegisteredType(classType, 'class'); + var humanName = 'constructor ' + classType.name; + argTypes = requireArgumentTypes(argCount, argTypes, humanName); + constructor = FUNCTION_TABLE[constructor]; + + classType.constructor.body = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var ptr = constructor.apply(null, args); + runDestructors(destructors); + classType.Handle.call(this, ptr); + }; +} + +function __embind_register_class_method( + classType, + methodName, + returnType, + argCount, + argTypes, + invoker, + memberFunctionSize, + memberFunction +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'method ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'method ' + humanName); + invoker = FUNCTION_TABLE[invoker]; + memberFunction = copyMemberPointer(memberFunction, memberFunctionSize); + + classType.Handle.prototype[methodName] = function() { + if (!this.ptr) { + throw new BindingError('cannot call emscripten binding method ' + humanName + ' on deleted object'); + } + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount + 2); + args[0] = this.ptr; + args[1] = memberFunction; + for (var i = 0; i < argCount; ++i) { + args[i + 2] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_classmethod( + classType, + methodName, + returnType, + argCount, + argTypes, + method +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'classmethod ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'classmethod ' + humanName); + method = FUNCTION_TABLE[method]; + + classType.constructor[methodName] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(method.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_field( + classType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + classType = requireRegisteredType(classType, 'class'); + fieldName = Pointer_stringify(fieldName); + var humanName = classType.name + '.' + fieldName; + fieldType = requireRegisteredType(fieldType, 'field ' + humanName); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + Object.defineProperty(classType.Handle.prototype, fieldName, { + get: function() { + if (!this.ptr) { + throw new BindingError('cannot access emscripten binding field ' + humanName + ' on deleted object'); + } + return fieldType.fromWireType(getter(this.ptr, memberPointer)); + }, + set: function(v) { + if (!this.ptr) { + throw new BindingError('cannot modify emscripten binding field ' + humanName + ' on deleted object'); + } + var destructors = []; + setter(this.ptr, memberPointer, fieldType.toWireType(destructors, v)); + runDestructors(destructors); + }, + enumerable: true + }); +} + +function __embind_register_enum( + enumType, + name +) { + name = Pointer_stringify(name); + + function Enum() { + } + Enum.values = {}; + + typeRegistry[enumType] = { + name: name, + constructor: Enum, + toWireType: function(destructors, c) { + return c.value; + }, + fromWireType: function(c) { + return Enum.values[c]; + }, + }; + + Module[name] = Enum; +} + +function __embind_register_enum_value( + enumType, + name, + enumValue +) { + enumType = requireRegisteredType(enumType, 'enum'); + name = Pointer_stringify(name); + + var Enum = enumType.constructor; + + var Value = Object.create(enumType.constructor.prototype, { + value: {value: enumValue}, + constructor: {value: createNamedFunction(enumType.name + '_' + name, function() {})}, + }); + Enum.values[enumValue] = Value; + Enum[name] = Value; +} + +function __embind_register_interface( + interfaceType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[interfaceType] = { + name: name, + toWireType: function(destructors, o) { + var handle = __emval_register(o); + var ptr = constructor(handle); + destructors.push(destructor); + destructors.push(ptr); + return ptr; + }, + }; +} diff --git a/src/embind/emval.js b/src/embind/emval.js new file mode 100644 index 00000000..9574ab37 --- /dev/null +++ b/src/embind/emval.js @@ -0,0 +1,111 @@ +/*global Module*/ +/*global HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global requireRegisteredType*/ + +var _emval_handle_array = []; +var _emval_free_list = []; + +// Public JS API + +/** @expose */ +Module.count_emval_handles = function() { + return _emval_handle_array.length; +}; + +// Private C++ API + +function __emval_register(value) { + var handle = _emval_free_list.length ? + _emval_free_list.pop() : + _emval_handle_array.length; + _emval_handle_array[handle] = {refcount: 1, value: value}; + return handle; +} + +function __emval_incref(handle) { + _emval_handle_array[handle].refcount += 1; +} + +function __emval_decref(handle) { + if (0 === --_emval_handle_array[handle].refcount) { + delete _emval_handle_array[handle]; + _emval_free_list.push(handle); + + var actual_length = _emval_handle_array.length; + while (actual_length > 0 && _emval_handle_array[actual_length - 1] === undefined) { + --actual_length; + } + _emval_handle_array.length = actual_length; + } +} + +function __emval_new_object() { + return __emval_register({}); +} + +function __emval_new_long(value) { + return __emval_register(value); +} + +function __emval_new_cstring(str) { + return __emval_register(Pointer_stringify(str)); +} + +function __emval_get_property(handle, k) { + k = Pointer_stringify(k); + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_unsigned_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_set_property(handle, k, value) { + k = Pointer_stringify(k); + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_set_property_by_int(handle, k, value) { + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_as(handle, returnType) { + returnType = requireRegisteredType(returnType, 'emval::as'); + var destructors = []; + // caller owns destructing + return returnType.toWireType(destructors, _emval_handle_array[handle].value); +} + +function __emval_call(handle, argCount, argTypes) { + var args = Array.prototype.slice.call(arguments, 3); + var fn = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = fn.apply(undefined, a); + return __emval_register(rv); +} + +function __emval_call_method(handle, name, argCount, argTypes) { + name = Pointer_stringify(name); + var args = Array.prototype.slice.call(arguments, 4); + var obj = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = obj[name].apply(obj, a); + return __emval_register(rv); +} diff --git a/src/library.js b/src/library.js index 938a3c92..1bb58833 100644 --- a/src/library.js +++ b/src/library.js @@ -304,12 +304,14 @@ LibraryManager.library = { // You can also call this with a typed array instead of a url. It will then // do preloading for the Image/Audio part, as if the typed array were the // result of an XHR that you did manually. - createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror) { + createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) { Browser.ensureObjects(); var fullname = FS.joinPath([parent, name], true); function processData(byteArray) { function finish(byteArray) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite); + if (!dontCreateFile) { + FS.createDataFile(parent, name, byteArray, canRead, canWrite); + } if (onload) onload(); removeRunDependency('cp ' + fullname); } @@ -1209,7 +1211,7 @@ LibraryManager.library = { return 0; case {{{ cDefine('F_SETOWN') }}}: case {{{ cDefine('F_GETOWN') }}}: - // These are for sockets. We don't have them implemented (yet?). + // These are for sockets. We don't have them fully implemented yet. ___setErrNo(ERRNO_CODES.EINVAL); return -1; default: @@ -6315,14 +6317,18 @@ LibraryManager.library = { ntohl: 'htonl', ntohs: 'htons', - inet_pton__deps: ['__setErrNo', '$ERRNO_CODES'], + inet_addr: function(ptr) { + var b = Pointer_stringify(ptr).split("."); + if (b.length !== 4) return -1; // we return -1 for error, and otherwise a uint32. this helps inet_pton differentiate + return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; + }, + + inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_addr'], inet_pton: function(af, src, dst) { // int af, const char *src, void *dst if ((af ^ {{{ cDefine("AF_INET") }}}) !== 0) { ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); return -1; } - var b = Pointer_stringify(src).split("."); - if (b.length !== 4) return 0; - var ret = Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24); - if (isNaN(ret)) return 0; + var ret = _inet_addr(src); + if (ret == -1 || isNaN(ret)) return 0; setValue(dst, ret, 'i32'); return 1; }, @@ -6340,7 +6346,62 @@ LibraryManager.library = { }, // ========================================================================== - // sockets + // netdb.h + // ========================================================================== + + // All we can do is alias names to ips. you give this a name, it returns an + // "ip" that we later know to use as a name. There is no way to do actual + // name resolving clientside in a browser. + // we do the aliasing in 172.29.*.*, giving us 65536 possibilities + // note: lots of leaking here! + __hostent_struct_layout: Runtime.generateStructInfo([ + ['i8*', 'h_name'], + ['i8**', 'h_aliases'], + ['i32', 'h_addrtype'], + ['i32', 'h_length'], + ['i8**', 'h_addr_list'], + ]), + gethostbyname__deps: ['__hostent_struct_layout'], + gethostbyname: function(name) { + name = Pointer_stringify(name); + if (!_gethostbyname.id) { + _gethostbyname.id = 1; + _gethostbyname.table = {}; + } + var id = _gethostbyname.id++; + assert(id < 65535); + var fakeAddr = 172 | (29 << 8) | ((id & 0xff) << 16) | ((id & 0xff00) << 24); + _gethostbyname.table[id] = name; + // generate hostent + var ret = _malloc(___hostent_struct_layout.__size__); + var nameBuf = _malloc(name.length+1); + writeStringToMemory(name, nameBuf); + setValue(ret+___hostent_struct_layout.h_name, nameBuf, 'i8*'); + var aliasesBuf = _malloc(4); + setValue(aliasesBuf, 0, 'i8*'); + setValue(ret+___hostent_struct_layout.h_aliases, aliasesBuf, 'i8**'); + setValue(ret+___hostent_struct_layout.h_addrtype, {{{ cDefine("AF_INET") }}}, 'i32'); + setValue(ret+___hostent_struct_layout.h_length, 4, 'i32'); + var addrListBuf = _malloc(12); + setValue(addrListBuf, addrListBuf+8, 'i32*'); + setValue(addrListBuf+4, 0, 'i32*'); + setValue(addrListBuf+8, fakeAddr, 'i32'); + setValue(ret+___hostent_struct_layout.h_addr_list, addrListBuf, 'i8**'); + return ret; + }, + + gethostbyname_r__deps: ['gethostbyname'], + gethostbyname_r: function(name, hostData, buffer, bufferSize, hostEntry, errnum) { + var data = _gethostbyname(name); + _memcpy(hostData, data, ___hostent_struct_layout.__size__); + _free(data); + setValue(errnum, 0, 'i32'); + return 0; + }, + + // ========================================================================== + // sockets. Note that the implementation assumes all sockets are always + // nonblocking // ========================================================================== $Sockets__deps: ['__setErrNo', '$ERRNO_CODES'], @@ -6354,6 +6415,15 @@ LibraryManager.library = { ['i32', 'sin_addr'], ['i64', 'sin_zero'], ]), + msghdr_layout: Runtime.generateStructInfo([ + ['*', 'msg_name'], + ['i32', 'msg_namelen'], + ['*', 'msg_iov'], + ['i32', 'msg_iovlen'], + ['*', 'msg_control'], + ['i32', 'msg_controllen'], + ['i32', 'msg_flags'], + ]), }, socket__deps: ['$Sockets'], @@ -6365,7 +6435,7 @@ LibraryManager.library = { return fd; }, - connect__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs'], + connect__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs', 'gethostbyname'], connect: function(fd, addr, addrlen) { var info = Sockets.fds[fd]; if (!info) return -1; @@ -6373,24 +6443,68 @@ LibraryManager.library = { info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); info.port = _ntohs(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); info.host = __inet_ntop_raw(info.addr); + // Support 'fake' ips from gethostbyname + var parts = info.host.split('.'); + if (parts[0] == '172' && parts[1] == '29') { + var low = Number(parts[2]); + var high = Number(parts[3]); + info.host = _gethostbyname.table[low + 0xff*high]; + assert(info.host, 'problem translating fake ip ' + parts); + } + console.log('opening ws://' + info.host + ':' + info.port); info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['arraybuffer']); info.socket.binaryType = 'arraybuffer'; info.buffer = new Uint8Array(Sockets.BUFFER_SIZE); info.bufferWrite = info.bufferRead = 0; info.socket.onmessage = function (event) { var data = event.data; +#if SOCKET_DEBUG + Module.print(['onmessage', window.location, event.data, window.atob(data)]); +#endif if (typeof data == 'string') { var binaryString = window.atob(data); var len = binaryString.length; +#if SOCKET_DEBUG + var out = []; +#endif for (var i = 0; i < len; i++) { - info.buffer[info.bufferWrite++] = binaryString.charCodeAt(i); + var curr = binaryString.charCodeAt(i); + info.buffer[info.bufferWrite++] = curr; +#if SOCKET_DEBUG + out.push(curr); +#endif if (info.bufferWrite == Sockets.BUFFER_SIZE) info.bufferWrite = 0; if (info.bufferWrite == info.bufferRead) throw 'socket buffer overflow'; } +#if SOCKET_DEBUG + Module.print(['onmessage data:', len, ':', out]); +#endif } else { console.log('binary!'); } } + info.sendQueue = []; + info.senderWaiting = false; + info.sender = function(data) { + if (data) { + info.sendQueue.push(data); + } else { + info.senderWaiting = false; // we are a setTimeout callback + if (info.sendQueue.length == 0) return; + } + if (info.socket.readyState != info.socket.OPEN) { + if (!info.senderWaiting) { + console.log('waiting for socket in order to send'); + setTimeout(info.sender, 100); + info.senderWaiting = true; + } + return; + } + for (var i = 0; i < info.sendQueue.length; i++) { + info.socket.send(window.btoa(info.sendQueue[i])); + } + info.sendQueue = []; + } return 0; }, @@ -6403,6 +6517,9 @@ LibraryManager.library = { return 0; // should this be -1 like the spec says? } var ret = 0; +#if SOCKET_DEBUG + Module.print('pre-recv: ' + [len, info.bufferWrite, info.bufferRead]); +#endif while (info.bufferWrite != info.bufferRead && len > 0) { // write out a byte {{{ makeSetValue('buf++', '0', 'info.buffer[info.bufferRead++]', 'i8') }}}; @@ -6410,9 +6527,105 @@ LibraryManager.library = { len--; ret++; } +#if SOCKET_DEBUG + Module.print('recv: ' + ret + ' : ' + Array.prototype.slice.call(HEAPU8.subarray(buf-len, buf))); +#endif + return ret; + }, + + send__deps: ['$Sockets'], + send: function(fd, buf, len, flags) { + var info = Sockets.fds[fd]; + if (!info) return -1; +#if SOCKET_DEBUG + Module.print('send: ' + Array.prototype.slice.call(HEAPU8.subarray(buf, buf+len))); +#endif + info.sender(Pointer_stringify(buf, len)); + return len; + }, + + sendmsg__deps: ['$Sockets', 'connect'], + sendmsg: function(fd, msg, flags) { + var info = Sockets.fds[fd]; + if (!info) return -1; + // if we are not connected, use the address info in the message + if (!info.connected) { + var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; + assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); + _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + } + var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + var data = ''; + for (var i = 0; i < num; i++) { + var currNum = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i' + '+4', 'i32') }}}; + if (!currNum) continue; + var currBuf = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i', 'i8*') }}}; +#if SOCKET_DEBUG + Module.print('sendmsg part ' + i + ' : ' + currNum + ' : ' + Array.prototype.slice.call(HEAPU8.subarray(currBuf, currBuf+currNum))); +#endif + data += Pointer_stringify(currBuf, currNum); + } + info.sender(data); + return data.length; + }, + + recvmsg__deps: ['$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES'], + recvmsg: function(fd, msg, flags) { +#if SOCKET_DEBUG + Module.print('recvmsg!'); +#endif + var info = Sockets.fds[fd]; + if (!info) return -1; + // if we are not connected, use the address info in the message + if (!info.connected) { +#if SOCKET_DEBUG + Module.print('recvmsg connecting'); +#endif + var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; + assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); + _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + } + var bytes = info.bufferWrite - info.bufferRead; + if (bytes < 0) bytes += Sockets.BUFFER_SIZE; +#if SOCKET_DEBUG + Module.print('recvmsg bytes: ' + bytes); +#endif + if (bytes == 0) { + ___setErrNo(ERRNO_CODES.EWOULDBLOCK); + return -1; + } + var ret = bytes; + var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + var data = ''; + for (var i = 0; i < num && bytes > 0; i++) |