1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18#include <atomic>
19#include <stdio.h>
20#include <string.h>
21
22#include "messagequeue.h"
23
24namespace nativemididemo {
25
26static const int messageBufferSize = 64 * 1024;
27static char messageBuffer[messageBufferSize];
28static std::atomic_ullong messagesLastWritePosition;
29
30void writeMessage(const char* message)
31{
32    static unsigned long long lastWritePos = 0;
33    size_t messageLen = strlen(message);
34    if (messageLen == 0) return;
35
36    messageLen += 1; // Also count in the null terminator.
37    char buffer[1024];
38    if (messageLen >= messageBufferSize) {
39        snprintf(buffer, sizeof(buffer), "!!! Message too long: %zu bytes !!!", messageLen);
40        message = buffer;
41        messageLen = strlen(message);
42    }
43
44    size_t wrappedWritePos = lastWritePos % messageBufferSize;
45    if (wrappedWritePos + messageLen >= messageBufferSize) {
46        size_t tailLen = messageBufferSize - wrappedWritePos;
47        memset(messageBuffer + wrappedWritePos, 0, tailLen);
48        lastWritePos += tailLen;
49        wrappedWritePos = 0;
50    }
51
52    memcpy(messageBuffer + wrappedWritePos, message, messageLen);
53    lastWritePos += messageLen;
54    messagesLastWritePosition.store(lastWritePos);
55}
56
57static char messageBufferCopy[messageBufferSize];
58
59jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject)
60{
61    static unsigned long long lastReadPos = 0;
62    const char* overrunMessage = "";
63    size_t messagesCount = 0;
64    jobjectArray result = NULL;
65
66    // First we copy the portion of the message buffer into messageBufferCopy.  If after finishing
67    // the copy we notice that the writer has mutated the portion of the buffer that we were
68    // copying, we report an overrun. Afterwards we can safely read messages from the copy.
69    memset(messageBufferCopy, 0, sizeof(messageBufferCopy));
70    unsigned long long lastWritePos = messagesLastWritePosition.load();
71    if (lastWritePos - lastReadPos > messageBufferSize) {
72        overrunMessage = "!!! Message buffer overrun !!!";
73        messagesCount = 1;
74        lastReadPos = lastWritePos;
75        goto create_array;
76    }
77    if (lastWritePos == lastReadPos) return result;
78    if (lastWritePos / messageBufferSize == lastReadPos / messageBufferSize) {
79        size_t wrappedReadPos = lastReadPos % messageBufferSize;
80        memcpy(messageBufferCopy + wrappedReadPos,
81                messageBuffer + wrappedReadPos,
82                lastWritePos % messageBufferSize - wrappedReadPos);
83    } else {
84        size_t wrappedReadPos = lastReadPos % messageBufferSize;
85        memcpy(messageBufferCopy, messageBuffer, lastWritePos % messageBufferSize);
86        memcpy(messageBufferCopy + wrappedReadPos,
87                messageBuffer + wrappedReadPos,
88                messageBufferSize - wrappedReadPos);
89    }
90    {
91    unsigned long long newLastWritePos = messagesLastWritePosition.load();
92    if (newLastWritePos - lastReadPos > messageBufferSize) {
93        overrunMessage = "!!! Message buffer overrun !!!";
94        messagesCount = 1;
95        lastReadPos = lastWritePos = newLastWritePos;
96        goto create_array;
97    }
98    }
99    // Otherwise we ignore newLastWritePos, since we only have a copy of the buffer
100    // up to lastWritePos.
101
102    for (unsigned long long readPos = lastReadPos; readPos < lastWritePos; ) {
103        size_t messageLen = strlen(messageBufferCopy + (readPos % messageBufferSize));
104        if (messageLen != 0) {
105            readPos += messageLen + 1;
106            messagesCount++;
107        } else {
108            // Skip to the beginning of the buffer.
109            readPos = (readPos / messageBufferSize + 1) * messageBufferSize;
110        }
111    }
112    if (messagesCount == 0) {
113        lastReadPos = lastWritePos;
114        return result;
115    }
116
117create_array:
118    result = env->NewObjectArray(
119            messagesCount, env->FindClass("java/lang/String"), env->NewStringUTF(overrunMessage));
120    if (lastWritePos == lastReadPos) return result;
121
122    jsize arrayIndex = 0;
123    while (lastReadPos < lastWritePos) {
124        size_t wrappedReadPos = lastReadPos % messageBufferSize;
125        if (messageBufferCopy[wrappedReadPos] != '\0') {
126            jstring message = env->NewStringUTF(messageBufferCopy + wrappedReadPos);
127            env->SetObjectArrayElement(result, arrayIndex++, message);
128            lastReadPos += env->GetStringLength(message) + 1;
129            env->DeleteLocalRef(message);
130        } else {
131            // Skip to the beginning of the buffer.
132            lastReadPos = (lastReadPos / messageBufferSize + 1) * messageBufferSize;
133        }
134    }
135    return result;
136}
137
138} // namespace nativemididemo
139