static bool caerOutputUnixSocketServerInit(caerModuleData moduleData) { // First, always create all needed setting nodes, set their default values // and add their listeners. sshsNodePutStringIfAbsent(moduleData->moduleNode, "socketPath", "/tmp/caer.sock"); sshsNodePutShortIfAbsent(moduleData->moduleNode, "backlogSize", 5); sshsNodePutShortIfAbsent(moduleData->moduleNode, "concurrentConnections", 10); // Open a Unix local socket on a known path, to be accessed by other processes (server-like mode). int serverSockFd = socket(AF_UNIX, SOCK_STREAM, 0); if (serverSockFd < 0) { caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not create local Unix socket. Error: %d.", errno); return (false); } struct sockaddr_un unixSocketAddr; memset(&unixSocketAddr, 0, sizeof(struct sockaddr_un)); unixSocketAddr.sun_family = AF_UNIX; char *socketPath = sshsNodeGetString(moduleData->moduleNode, "socketPath"); strncpy(unixSocketAddr.sun_path, socketPath, sizeof(unixSocketAddr.sun_path) - 1); unixSocketAddr.sun_path[sizeof(unixSocketAddr.sun_path) - 1] = '\0'; // Ensure NUL terminated string. free(socketPath); // Bind socket to above address. if (bind(serverSockFd, (struct sockaddr *) &unixSocketAddr, sizeof(struct sockaddr_un)) < 0) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not bind local Unix socket. Error: %d.", errno); return (false); } // Listen to new connections on the socket. if (listen(serverSockFd, sshsNodeGetShort(moduleData->moduleNode, "backlogSize")) < 0) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Could not listen on local Unix socket. Error: %d.", errno); return (false); } outputCommonFDs fileDescriptors = caerOutputCommonAllocateFdArray( (size_t) sshsNodeGetShort(moduleData->moduleNode, "concurrentConnections")); if (fileDescriptors == NULL) { close(serverSockFd); caerLog(CAER_LOG_CRITICAL, moduleData->moduleSubSystemString, "Unable to allocate memory for file descriptors."); return (false); } fileDescriptors->serverFd = serverSockFd; if (!caerOutputCommonInit(moduleData, fileDescriptors, true, false)) { close(serverSockFd); free(fileDescriptors); return (false); } caerLog(CAER_LOG_INFO, moduleData->moduleSubSystemString, "Local Unix socket ready at '%s'.", unixSocketAddr.sun_path); return (true); }
static int caerConfigServerRunner(void *inPtr) { UNUSED_ARGUMENT(inPtr); // Get the right configuration node first. sshsNode serverNode = sshsGetNode(sshsGetGlobal(), "/server/"); // Ensure default values are present. sshsNodePutStringIfAbsent(serverNode, "ipAddress", "127.0.0.1"); sshsNodePutIntIfAbsent(serverNode, "portNumber", 4040); sshsNodePutShortIfAbsent(serverNode, "backlogSize", 5); sshsNodePutShortIfAbsent(serverNode, "concurrentConnections", 5); // Open a TCP server socket for configuration handling. // TCP chosen for reliability, which is more important here than speed. int configServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (configServerSocket < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not create server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Make socket address reusable right away. socketReuseAddr(configServerSocket, true); struct sockaddr_in configServerAddress; memset(&configServerAddress, 0, sizeof(struct sockaddr_in)); configServerAddress.sin_family = AF_INET; configServerAddress.sin_port = htons(sshsNodeGetInt(serverNode, "portNumber")); char *ipAddress = sshsNodeGetString(serverNode, "ipAddress"); inet_aton(ipAddress, &configServerAddress.sin_addr); // htonl() is implicit here. free(ipAddress); // Bind socket to above address. if (bind(configServerSocket, (struct sockaddr *) &configServerAddress, sizeof(struct sockaddr_in)) < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not bind server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Listen to new connections on the socket. if (listen(configServerSocket, sshsNodeGetShort(serverNode, "backlogSize")) < 0) { caerLog(CAER_LOG_CRITICAL, "Config Server", "Could not listen on server socket. Error: %d.", errno); return (EXIT_FAILURE); } // Control message format: 1 byte ACTION, 1 byte TYPE, 2 bytes EXTRA_LEN, // 2 bytes NODE_LEN, 2 bytes KEY_LEN, 2 bytes VALUE_LEN, then up to 4086 // bytes split between EXTRA, NODE, KEY, VALUE (with 4 bytes for NUL). // Basically: (EXTRA_LEN + NODE_LEN + KEY_LEN + VALUE_LEN) <= 4086. // EXTRA, NODE, KEY, VALUE have to be NUL terminated, and their length // must include the NUL termination byte. // This results in a maximum message size of 4096 bytes (4KB). uint8_t configServerBuffer[4096]; caerLog(CAER_LOG_NOTICE, "Config Server", "Ready and listening on %s:%" PRIu16 ".", inet_ntoa(configServerAddress.sin_addr), ntohs(configServerAddress.sin_port)); size_t connections = (size_t) sshsNodeGetShort(serverNode, "concurrentConnections") + 1;// +1 for listening socket. struct pollfd pollSockets[connections]; // Initially ignore all pollfds, and react only to POLLIN events. for (size_t i = 0; i < connections; i++) { pollSockets[i].fd = -1; pollSockets[i].events = POLLIN; } // First pollfd is the listening socket, always. pollSockets[0].fd = configServerSocket; while (atomic_load_explicit(&configServerThread.running, memory_order_relaxed)) { int pollResult = poll(pollSockets, (nfds_t) connections, 1000); if (pollResult > 0) { // First let's check if there's a new connection waiting to be accepted. if ((pollSockets[0].revents & POLLIN) != 0) { int newClientSocket = accept(pollSockets[0].fd, NULL, NULL); if (newClientSocket >= 0) { // Put it in the list of watched fds if possible, or close. bool putInFDList = false; for (size_t i = 1; i < connections; i++) { if (pollSockets[i].fd == -1) { // Empty place in watch list, add this one. pollSockets[i].fd = newClientSocket; putInFDList = true; break; } } // No space for new connection, just close it (client will exit). if (!putInFDList) { close(newClientSocket); caerLog(CAER_LOG_DEBUG, "Config Server", "Rejected client (fd %d), queue full.", newClientSocket); } else { caerLog(CAER_LOG_DEBUG, "Config Server", "Accepted new connection from client (fd %d).", newClientSocket); } } } for (size_t i = 1; i < connections; i++) { // Work on existing connections, valid and with read events. if (pollSockets[i].fd != -1 && (pollSockets[i].revents & POLLIN) != 0) { // Accepted client connection, let's get all the data we need: first // the 10 byte header, and then whatever remains based on that. if (!recvUntilDone(pollSockets[i].fd, configServerBuffer, 10)) { // Closed on other side or error. Let's just close here too. close(pollSockets[i].fd); caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on recv (fd %d).", pollSockets[i].fd); pollSockets[i].fd = -1; continue; } // Decode header fields (all in little-endian). uint8_t action = configServerBuffer[0]; uint8_t type = configServerBuffer[1]; uint16_t extraLength = le16toh(*(uint16_t * )(configServerBuffer + 2)); uint16_t nodeLength = le16toh(*(uint16_t * )(configServerBuffer + 4)); uint16_t keyLength = le16toh(*(uint16_t * )(configServerBuffer + 6)); uint16_t valueLength = le16toh(*(uint16_t * )(configServerBuffer + 8)); // Total length to get for command. size_t readLength = (size_t) (extraLength + nodeLength + keyLength + valueLength); if (!recvUntilDone(pollSockets[i].fd, configServerBuffer + 10, readLength)) { // Closed on other side or error. Let's just close here too. close(pollSockets[i].fd); caerLog(CAER_LOG_DEBUG, "Config Server", "Disconnected client on recv (fd %d).", pollSockets[i].fd); pollSockets[i].fd = -1; continue; } // Now we have everything. The header fields are already // fully decoded: handle request (and send back data eventually). caerConfigServerHandleRequest(pollSockets[i].fd, action, type, configServerBuffer + 10, extraLength, configServerBuffer + 10 + extraLength, nodeLength, configServerBuffer + 10 + extraLength + nodeLength, keyLength, configServerBuffer + 10 + extraLength + nodeLength + keyLength, valueLength); } } } else if (pollResult < 0) { caerLog(CAER_LOG_ALERT, "Config Server", "poll() failed. Error: %d.", errno); } // pollResult == 0 just means nothing to do (timeout reached). } // Shutdown! close(configServerSocket); return (EXIT_SUCCESS); }
static bool caerInputDVS128Init(caerModuleData moduleData) { caerLog(CAER_LOG_DEBUG, moduleData->moduleSubSystemString, "Initializing module ..."); // USB port/bus/SN settings/restrictions. // These can be used to force connection to one specific device at startup. sshsNodePutShortIfAbsent(moduleData->moduleNode, "BusNumber", 0); sshsNodePutShortIfAbsent(moduleData->moduleNode, "DevAddress", 0); sshsNodePutStringIfAbsent(moduleData->moduleNode, "SerialNumber", ""); // Add auto-restart setting. sshsNodePutBoolIfAbsent(moduleData->moduleNode, "Auto-Restart", true); // Start data acquisition, and correctly notify mainloop of new data and module of exceptional // shutdown cases (device pulled, ...). char *serialNumber = sshsNodeGetString(moduleData->moduleNode, "SerialNumber"); moduleData->moduleState = caerDeviceOpen(moduleData->moduleID, CAER_DEVICE_DVS128, U8T(sshsNodeGetShort(moduleData->moduleNode, "BusNumber")), U8T(sshsNodeGetShort(moduleData->moduleNode, "DevAddress")), serialNumber); free(serialNumber); if (moduleData->moduleState == NULL) { // Failed to open device. return (false); } // Put global source information into SSHS. struct caer_dvs128_info devInfo = caerDVS128InfoGet(moduleData->moduleState); sshsNode sourceInfoNode = sshsGetRelativeNode(moduleData->moduleNode, "sourceInfo/"); sshsNodePutShort(sourceInfoNode, "logicVersion", devInfo.logicVersion); sshsNodePutBool(sourceInfoNode, "deviceIsMaster", devInfo.deviceIsMaster); sshsNodePutShort(sourceInfoNode, "dvsSizeX", devInfo.dvsSizeX); sshsNodePutShort(sourceInfoNode, "dvsSizeY", devInfo.dvsSizeY); // Put source information for "virtual" APS frame that can be used to display and debug filter information. sshsNodePutShort(sourceInfoNode, "apsSizeX", devInfo.dvsSizeX); sshsNodePutShort(sourceInfoNode, "apsSizeY", devInfo.dvsSizeY); caerModuleSetSubSystemString(moduleData, devInfo.deviceString); // Ensure good defaults for data acquisition settings. // No blocking behavior due to mainloop notification, and no auto-start of // all producers to ensure cAER settings are respected. caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_BLOCKING, false); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_START_PRODUCERS, false); caerDeviceConfigSet(moduleData->moduleState, CAER_HOST_CONFIG_DATAEXCHANGE, CAER_HOST_CONFIG_DATAEXCHANGE_STOP_PRODUCERS, true); // Create default settings and send them to the device. createDefaultConfiguration(moduleData); sendDefaultConfiguration(moduleData); // Start data acquisition. bool ret = caerDeviceDataStart(moduleData->moduleState, &mainloopDataNotifyIncrease, &mainloopDataNotifyDecrease, caerMainloopGetReference(), &moduleShutdownNotify, moduleData->moduleNode); if (!ret) { // Failed to start data acquisition, close device and exit. caerDeviceClose((caerDeviceHandle *) &moduleData->moduleState); return (false); } return (true); }