-/*
- * SPDX-License-Identifier: LGPL-2.1-only
- *
- * Copyright (C) 2016 EfficiOS Inc.
- * Copyright (C) 2016 Alexandre Montplaisir <alexmonthy@efficios.com>
- */
-
-package org.lttng.ust.agent.context;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.Collection;
-import java.util.Map;
-
-import org.lttng.ust.agent.utils.LttngUstAgentLogger;
-
-/**
- * This class is used to serialize the list of "context info" objects to pass
- * through JNI.
- *
- * The protocol expects two byte array parameters, which are contained here in
- * the {@link SerializedContexts} inner class.
- *
- * The first byte array is called the "entries array", and contains fixed-size
- * entries, one per context element.
- *
- * The second one is the "strings array", it is of variable length and used to
- * hold the variable-length strings. Each one of these strings is formatted as a
- * UTF-8 C-string, meaning in will end with a "\0" byte to indicate its end.
- * Entries in the first array may refer to offsets in the second array to point
- * to relevant strings.
- *
- * The fixed-size entries in the entries array contain the following elements
- * (size in bytes in parentheses):
- *
- * <ul>
- * <li>The offset in the strings array pointing to the full context name, like
- * "$app.myprovider:mycontext" (4)</li>
- * <li>The context value type (1)</li>
- * <li>The context value itself (8)</li>
- * </ul>
- *
- * The context value type will indicate how many bytes are used for the value.
- * If the it is of String type, then we use 4 bytes to represent the offset in
- * the strings array.
- *
- * So the total size of each entry is 13 bytes. All unused bytes (for context
- * values shorter than 8 bytes for example) will be zero'ed.
- *
- * @author Alexandre Montplaisir
- */
-public class ContextInfoSerializer {
-
- private enum DataType {
- NULL(0),
- INTEGER(1),
- LONG(2),
- DOUBLE(3),
- FLOAT(4),
- BYTE(5),
- SHORT(6),
- BOOLEAN(7),
- STRING(8);
-
- private final byte value;
-
- private DataType(int value) {
- this.value = (byte) value;
- }
-
- public byte getValue() {
- return value;
- }
- }
-
- /**
- * Class used to wrap the two byte arrays returned by
- * {@link #queryAndSerializeRequestedContexts}.
- */
- public static class SerializedContexts {
-
- private final byte[] contextEntries;
- private final byte[] contextStrings;
-
- /**
- * Constructor
- *
- * @param entries
- * Arrays for the fixed-size context entries.
- * @param strings
- * Arrays for variable-length strings
- */
- public SerializedContexts(byte[] entries, byte[] strings) {
- contextEntries = entries;
- contextStrings = strings;
- }
-
- /**
- * @return The entries array
- */
- public byte[] getEntriesArray() {
- return contextEntries;
- }
-
- /**
- * @return The strings array
- */
- public byte[] getStringsArray() {
- return contextStrings;
- }
- }
-
- private static final String UST_APP_CTX_PREFIX = "$app.";
- private static final int ENTRY_LENGTH = 13;
- private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder();
- private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
- private static final SerializedContexts EMPTY_CONTEXTS = new SerializedContexts(new byte[0], new byte[0]);
-
- /**
- * From the list of requested contexts in the tracing session, look them up
- * in the {@link ContextInfoManager}, retrieve the available ones, and
- * serialize them into a byte array.
- *
- * @param enabledContexts
- * The contexts that are enabled in the tracing session (indexed
- * first by retriever name, then by index names). Should come
- * from the LTTng Agent.
- * @return The byte array representing the intersection of the requested and
- * available contexts.
- */
- public static SerializedContexts queryAndSerializeRequestedContexts(Collection<Map.Entry<String, Map<String, Integer>>> enabledContexts) {
- if (enabledContexts.isEmpty()) {
- /* Early return if there is no requested context information */
- return EMPTY_CONTEXTS;
- }
-
- ContextInfoManager contextManager;
- try {
- contextManager = ContextInfoManager.getInstance();
- } catch (IOException e) {
- /*
- * The JNI library is not available, do not send any context
- * information. No retriever could have been defined anyways.
- */
- return EMPTY_CONTEXTS;
- }
-
- /* Compute the total number of contexts (flatten the map) */
- int totalArraySize = 0;
- for (Map.Entry<String, Map<String, Integer>> contexts : enabledContexts) {
- totalArraySize += contexts.getValue().size() * ENTRY_LENGTH;
- }
-
- /* Prepare the ByteBuffer that will generate the "entries" array */
- ByteBuffer entriesBuffer = ByteBuffer.allocate(totalArraySize);
- entriesBuffer.order(NATIVE_ORDER);
- entriesBuffer.clear();
-
- /* Prepare the streams that will generate the "strings" array */
- ByteArrayOutputStream stringsBaos = new ByteArrayOutputStream();
- DataOutputStream stringsDos = new DataOutputStream(stringsBaos);
-
- try {
- for (Map.Entry<String, Map<String, Integer>> entry : enabledContexts) {
- String requestedRetrieverName = entry.getKey();
- Map<String, Integer> requestedContexts = entry.getValue();
-
- IContextInfoRetriever retriever = contextManager.getContextInfoRetriever(requestedRetrieverName);
-
- for (String requestedContext : requestedContexts.keySet()) {
- Object contextInfo;
- if (retriever == null) {
- contextInfo = null;
- } else {
- contextInfo = retriever.retrieveContextInfo(requestedContext);
- /*
- * 'contextInfo' can still be null here, which would
- * indicate the retriever does not supply this context.
- * We will still write this information so that the
- * tracer can know about it.
- */
- }
-
- /* Serialize the result to the buffers */
- // FIXME Eventually pass the retriever name only once?
- String fullContextName = (UST_APP_CTX_PREFIX + requestedRetrieverName + ':' + requestedContext);
- byte[] strArray = fullContextName.getBytes(UTF8_CHARSET);
-
- entriesBuffer.putInt(stringsDos.size());
- stringsDos.write(strArray);
- stringsDos.writeChar('\0');
-
- LttngUstAgentLogger.log(ContextInfoSerializer.class,
- "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName + '=' +
- (contextInfo == null ? "null" : contextInfo.toString()));
-
- serializeContextInfo(entriesBuffer, stringsDos, contextInfo);
- }
- }
-
- stringsDos.flush();
- stringsBaos.flush();
-
- } catch (IOException e) {
- /*
- * Should not happen because we are wrapping a
- * ByteArrayOutputStream, which writes to memory
- */
- e.printStackTrace();
- }
-
- byte[] entriesArray = entriesBuffer.array();
- byte[] stringsArray = stringsBaos.toByteArray();
- return new SerializedContexts(entriesArray, stringsArray);
- }
-
- private static final int CONTEXT_VALUE_LENGTH = 8;
-
- private static void serializeContextInfo(ByteBuffer entriesBuffer, DataOutputStream stringsDos, Object contextInfo) throws IOException {
- int remainingBytes;
- if (contextInfo == null) {
- entriesBuffer.put(DataType.NULL.getValue());
- remainingBytes = CONTEXT_VALUE_LENGTH;
-
- } else if (contextInfo instanceof Integer) {
- entriesBuffer.put(DataType.INTEGER.getValue());
- entriesBuffer.putInt(((Integer) contextInfo).intValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 4;
-
- } else if (contextInfo instanceof Long) {
- entriesBuffer.put(DataType.LONG.getValue());
- entriesBuffer.putLong(((Long) contextInfo).longValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 8;
-
- } else if (contextInfo instanceof Double) {
- entriesBuffer.put(DataType.DOUBLE.getValue());
- entriesBuffer.putDouble(((Double) contextInfo).doubleValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 8;
-
- } else if (contextInfo instanceof Float) {
- entriesBuffer.put(DataType.FLOAT.getValue());
- entriesBuffer.putFloat(((Float) contextInfo).floatValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 4;
-
- } else if (contextInfo instanceof Byte) {
- entriesBuffer.put(DataType.BYTE.getValue());
- entriesBuffer.put(((Byte) contextInfo).byteValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 1;
-
- } else if (contextInfo instanceof Short) {
- entriesBuffer.put(DataType.SHORT.getValue());
- entriesBuffer.putShort(((Short) contextInfo).shortValue());
- remainingBytes = CONTEXT_VALUE_LENGTH - 2;
-
- } else if (contextInfo instanceof Boolean) {
- entriesBuffer.put(DataType.BOOLEAN.getValue());
- boolean b = ((Boolean) contextInfo).booleanValue();
- /* Converted to one byte, write 1 for true, 0 for false */
- entriesBuffer.put((byte) (b ? 1 : 0));
- remainingBytes = CONTEXT_VALUE_LENGTH - 1;
-
- } else {
- /* Also includes the case of Character. */
- /*
- * We'll write the object as a string, into the strings array. We
- * will write the corresponding offset to the entries array.
- */
- String str = contextInfo.toString();
- byte[] strArray = str.getBytes(UTF8_CHARSET);
-
- entriesBuffer.put(DataType.STRING.getValue());
-
- entriesBuffer.putInt(stringsDos.size());
- stringsDos.write(strArray);
- stringsDos.writeChar('\0');
-
- remainingBytes = CONTEXT_VALUE_LENGTH - 4;
- }
- entriesBuffer.position(entriesBuffer.position() + remainingBytes);
- }
-}