parse(ByteBuffer)   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
cc 2
crap 2
1
/*
2
 * This file is part of SingleInstance.
3
 *
4
 * SingleInstance is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * SingleInstance is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with SingleInstance.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.singleinstance.ipc;
21
22
import java.nio.BufferUnderflowException;
23
import java.nio.ByteBuffer;
24
import java.nio.channels.SelectionKey;
25
import java.util.ArrayList;
26
import java.util.List;
27
28
/**
29
 * Parse the incoming data to get IPC packet messages
30
 * The packet format is :
31
 * Packet {
32
 *     nameLength: short
33
 *     dataLength: short
34
 *     name: string
35
 *     data: byte[]
36
 * }
37
 *
38
 * Note: The parser instance must not be shared between clients
39
 *
40
 * Usage:
41
 * <pre>{@code
42
 *     ByteBuffer buffer = xxx;
43
 *     SelectionKey key = xxx;
44
 *
45
 *     // Data available for read
46
 *     if (key.isReadable()) {
47
 *         // Get the parser instance for the client
48
 *         ProtocolParser parser = ProtocolParser.forKey(key);
49
 *
50
 *         // Read from socket, to the buffer
51
 *         SocketChannel client = (SocketChannel) key.channel();
52
 *         client.read(buffer);
53
 *         buffer.flip();
54
 *
55
 *         // Parsing incoming packets
56
 *         parser.parse(buffer);
57
 *
58
 *         for (ProtocolParser.Packet packet : parser.packets()) {
59
 *             // Handle packet
60
 *         }
61
 *
62
 *         // Clear already handled packets
63
 *         parser.clear();
64
 *     }
65
 * }</pre>
66
 */
67 1
final public class ProtocolParser {
68 1
    final private List<Message> packets = new ArrayList<>();
69
70
    private byte[] nameBuffer;
71
    private int nameOffset;
72
73
    private byte[] dataBuffer;
74
    private int dataOffset;
75
76
    /**
77
     * Parse packets from the buffer
78
     * Handle partial packet : bufferize partial data for next call
79
     *
80
     * @param buffer The input buffer
81
     */
82
    public void parse(ByteBuffer buffer) {
83
        SimpleMessage packet;
84
85 1
        while ((packet = parsePacket(buffer)) != null) {
86 1
            packets.add(packet);
87
        }
88 1
    }
89
90
    /**
91
     * Get all parsed packets
92
     *
93
     * @return messages
94
     */
95
    public List<Message> packets() {
96 1
        return packets;
97
    }
98
99
    /**
100
     * Clear parsed packets
101
     * After call this method, {@link ProtocolParser#packets()} will return an empty list
102
     */
103
    public void clear() {
104 1
        packets.clear();
105 1
    }
106
107
    private SimpleMessage parsePacket(ByteBuffer buffer) {
108
        try {
109 1
            if (nameBuffer == null) {
110 1
                nameBuffer = new byte[buffer.getShort()];
111
            }
112
113 1
            if (dataBuffer == null) {
114 1
                dataBuffer = new byte[buffer.getShort()];
115
            }
116 1
        } catch (BufferUnderflowException e) {
117
            // Ignore
118 1
            return null;
119 1
        }
120
121 1
        if (nameOffset != nameBuffer.length) {
122 1
            nameOffset += read(buffer, nameBuffer, nameOffset);
123
124 1
            if (nameOffset != nameBuffer.length) {
125 1
                return null;
126
            }
127
        }
128
129 1
        if (dataOffset != dataBuffer.length) {
130 1
            dataOffset += read(buffer, dataBuffer, dataOffset);
131
132 1
            if (dataOffset != dataBuffer.length) {
133 1
                return null;
134
            }
135
        }
136
137 1
        SimpleMessage packet = new SimpleMessage(new String(nameBuffer), dataBuffer);
138
139 1
        nameBuffer = null;
140 1
        nameOffset = 0;
141 1
        dataBuffer = null;
142 1
        dataOffset = 0;
143
144 1
        return packet;
145
    }
146
147
    /**
148
     * Read byte array from buffer
149
     *
150
     * @return Number of read bytes
151
     */
152
    private int read(ByteBuffer src, byte[] dst, int offset) {
153 1
        int toRead = Math.min(dst.length - offset, src.remaining());
154 1
        src.get(dst, offset, toRead);
155
156 1
        return toRead;
157
    }
158
159
    /**
160
     * Get the {@link ProtocolParser} instance related to the key
161
     * If the parser is not attached, a new instance will be created
162
     *
163
     * @param key The key to attach
164
     *
165
     * @return The parser instance
166
     */
167
    static public ProtocolParser forKey(SelectionKey key) {
168 1
        if (key.attachment() instanceof ProtocolParser) {
169 1
            return (ProtocolParser) key.attachment();
170
        }
171
172 1
        ProtocolParser parser = new ProtocolParser();
173 1
        key.attach(parser);
174
175 1
        return parser;
176
    }
177
178
    /**
179
     * Format message to packet format bytes array
180
     *
181
     * @param message Message to format
182
     *
183
     * @return Formatted message
184
     */
185
    static public byte[] toBytes(Message message) {
186 1
        final byte[] name = message.name().getBytes();
187 1
        final byte[] data = message.data();
188
189 1
        ByteBuffer buffer = ByteBuffer.allocate(4 + name.length + data.length);
190
191 1
        buffer.putShort((short) name.length);
192 1
        buffer.putShort((short) data.length);
193 1
        buffer.put(name);
194 1
        buffer.put(data);
195
196 1
        return buffer.array();
197
    }
198
}
199