blob: b67c0b008021238726e3bef5d761298f2ced62d6 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.vab.protocol.basyx.server;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import org.eclipse.basyx.vab.coder.json.provider.JSONProvider;
import org.eclipse.basyx.vab.exception.provider.ProviderException;
import org.eclipse.basyx.vab.modelprovider.api.IModelProvider;
import org.eclipse.basyx.vab.protocol.basyx.CoderTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provider class that enables access to an IModelProvider via native BaSyx
* protocol
*
* @author kuhn, pschorn
*
*/
public class VABBaSyxTCPInterface<ModelProvider extends IModelProvider> extends Thread {
private static Logger logger = LoggerFactory.getLogger(VABBaSyxTCPInterface.class);
/**
* BaSyx get command
*/
public static final byte BASYX_GET = 0x01;
/**
* BaSyx set command
*/
public static final byte BASYX_SET = 0x02;
/**
* BaSyx create command
*/
public static final byte BASYX_CREATE = 0x03;
/**
* BaSyx delete command
*/
public static final byte BASYX_DELETE = 0x04;
/**
* BaSyx invoke command
*/
public static final byte BASYX_INVOKE = 0x05;
/**
* BaSyx result 'OK' : 0x00
*/
public static final byte BASYX_RESULT_OK = 0x00;
/**
* Reference to IModelProvider backend
*/
protected JSONProvider<ModelProvider> providerBackend = null;
/**
* Socket communication channel
*/
protected SocketChannel commChannel = null;
/**
* Constructor that accepts an already created server socket channel
*/
public VABBaSyxTCPInterface(ModelProvider modelProviderBackend, SocketChannel channel) {
// Store reference to channel and backend
providerBackend = new JSONProvider<ModelProvider>(modelProviderBackend);
commChannel = channel;
setName(VABBaSyxTCPInterface.class.getName() + " " + System.currentTimeMillis());
logger.debug("Socket created");
}
/**
* Process input frame
*/
public void processInputFrame(byte[] rxFrame) throws IOException {
// Create output streams
ByteArrayOutputStream output = new ByteArrayOutputStream();
// Get command
switch (rxFrame[0]) {
case BASYX_GET: {
// Get path string
int pathLen = CoderTools.getInt32(rxFrame, 1);
String path = new String(rxFrame, 1 + 4, pathLen);
// Forward request to provider
try {
providerBackend.processBaSysGet(path, output);
} catch (ProviderException e) {
logger.debug("Exception in BASYX_GET", e);
// Catch Exceptions from JSONProvider
// No further action here, as the current version
// of the TCP-Mapping states, that always Statuscode 0x00
// should be returned with Exceptions encoded in returned String
}
// System.out.println("Processed GET:"+path);
// Send response frame
output.flush();
sendResponseFrame(output);
break;
}
case BASYX_SET: {
// Get path string length and value
int pathLen = CoderTools.getInt32(rxFrame, 1);
String path = new String(rxFrame, 1 + 4, pathLen);
// Get value string length and value
int jsonValueLen = CoderTools.getInt32(rxFrame, 1 + 4 + pathLen);
String jsonValue = new String(rxFrame, 1 + 4 + pathLen + 4, jsonValueLen);
// Invoke get operation
try {
providerBackend.processBaSysSet(path, jsonValue, output);
} catch (ProviderException e) {
logger.debug("Exception in BASYX_SET", e);
// Catch Exceptions from JSONProvider
// No further action here, as the current version
// of the TCP-Mapping states, that always Statuscode 0x00
// should be returned with Exceptions encoded in returned String
}
// Send response frame
output.flush();
sendResponseFrame(output);
break;
}
case BASYX_CREATE: {
// Get path string length and value
int pathLen = CoderTools.getInt32(rxFrame, 1);
String path = new String(rxFrame, 1 + 4, pathLen);
// Get value string length and value
int jsonValueLen = CoderTools.getInt32(rxFrame, 1 + 4 + pathLen);
String jsonValue = new String(rxFrame, 1 + 4 + pathLen + 4, jsonValueLen);
// Invoke get operation
try {
providerBackend.processBaSysCreate(path, jsonValue, output);
} catch (ProviderException e) {
logger.debug("Exception in BASYX_CREATE", e);
// Catch Exceptions from JSONProvider
// No further action here, as the current version
// of the TCP-Mapping states, that always Statuscode 0x00
// should be returned with Exceptions encoded in returned String
}
// Send response frame
output.flush();
sendResponseFrame(output);
break;
}
case BASYX_DELETE: {
// Get path string length and value
int pathLen = CoderTools.getInt32(rxFrame, 1);
String path = new String(rxFrame, 1 + 4, pathLen);
// Get value string length and value if available; default is null value
String jsonValue = "";
try {
int jsonValueLen = CoderTools.getInt32(rxFrame, 1 + 4 + pathLen);
jsonValue = new String(rxFrame, 1 + 4 + pathLen + 4, jsonValueLen);
} catch (ArrayIndexOutOfBoundsException e) {
// pass, provide serialize null argument to processBaSysDelete to indicate that
// an
// entity should be removed
}
// Invoke delete operation
try {
providerBackend.processBaSysDelete(path, jsonValue, output);
} catch (ProviderException e) {
logger.debug("Exception in BASYX_DELETE", e);
// Catch Exceptions from JSONProvider
// No further action here, as the current version
// of the TCP-Mapping states, that always Statuscode 0x00
// should be returned with Exceptions encoded in returned String
}
// Send response frame
output.flush();
sendResponseFrame(output);
break;
}
case BASYX_INVOKE: {
// Get path string length and value
int pathLen = CoderTools.getInt32(rxFrame, 1);
String path = new String(rxFrame, 1 + 4, pathLen);
// Get value string length and value
int jsonValueLen = CoderTools.getInt32(rxFrame, 1 + 4 + pathLen);
String jsonValue = new String(rxFrame, 1 + 4 + pathLen + 4, jsonValueLen);
// Invoke get operation
try {
providerBackend.processBaSysInvoke(path, jsonValue, output);
} catch (ProviderException e) {
logger.debug("Exception in BASYX_INVOKE", e);
// Catch Exceptions from JSONProvider
// No further action here, as the current version
// of the TCP-Mapping states, that always Statuscode 0x00
// should be returned with Exceptions encoded in returned String
}
// Send response frame
output.flush();
sendResponseFrame(output);
break;
}
default:
throw new RuntimeException("Unknown BaSyx TCP command received");
}
}
/**
* Sends a response to the client that carries the JSON response
*
* @param byteArrayOutput
* @throws IOException
*/
private void sendResponseFrame(ByteArrayOutputStream byteArrayOutput) throws IOException {
// Create response frame with positive response
sendResponseFrame(byteArrayOutput, BASYX_RESULT_OK);
}
/**
* Sends a response to the client that carries the JSON response
*
* @param byteArrayOutput
* @throws IOException
*/
private void sendResponseFrame(ByteArrayOutputStream byteArrayOutput, int result) throws IOException {
// Create response frame
byte[] encodedResult = byteArrayOutput.toByteArray();
int resultFrameSize = encodedResult.length + 1;
byte[] frameLength = new byte[4];
byte[] encodedResultLength = new byte[4];
CoderTools.setInt32(frameLength, 0, resultFrameSize + 4);
CoderTools.setInt32(encodedResultLength, 0, encodedResult.length);
// Place response frame in buffer
ByteBuffer buffer = ByteBuffer.allocate(resultFrameSize + 4 + 4);
buffer.put(frameLength);
buffer.put((byte) result);
buffer.put(encodedResultLength);
buffer.put(encodedResult);
buffer.flip();
// Transmit response frame
commChannel.write(buffer);
// Reset output stream
byteArrayOutput.reset();
}
/**
* Read a number of bytes
*/
protected void readBytes(ByteBuffer bytes, int expectedBytes) throws IOException {
// Read bytes until buffer is full
while (bytes.position() < expectedBytes) {
int res = commChannel.read(bytes);
if (res == -1) {
throw new ClosedChannelException();
}
}
}
/**
* Thread main function
*/
@Override
public void run() {
// Run forever (until socket is closed)
while (true) {
// Process inputs
try {
// Read response
// - Wait for leading 4 byte header that contains frame length
ByteBuffer rxBuffer1 = ByteBuffer.allocate(4);
readBytes(rxBuffer1, 4);
int frameSize = CoderTools.getInt32(rxBuffer1.array(), 0);
logger.debug("Read Frame with size: " + frameSize);
// Wait for frame to arrive
ByteBuffer rxBuffer2 = ByteBuffer.allocate(frameSize);
readBytes(rxBuffer2, frameSize);
byte[] rxFrame = rxBuffer2.array();
// Process input frame
processInputFrame(rxFrame);
} catch (IOException e) {
// End when TCP socket is closed
if (e instanceof ClosedChannelException) {
break;
}
// Output error
logger.error("Exception in run", e);
}
}
try {
commChannel.close();
} catch (IOException e) {
logger.debug("Exception while closing the comChannel ", e);
}
logger.debug(getName() + " Socket closed");
}
}