Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Commit

Permalink
Fix #269; ArrayIndexOutOfBoundsException at SerialByteBuffer.resize()
Browse files Browse the repository at this point in the history
  • Loading branch information
savageautomate committed Feb 17, 2021
1 parent bca68ef commit 2570dce
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,9 @@ public synchronized int available(){
return (buffer.length - (readIndex - writeIndex));
}

private void resize(int length) {

private synchronized void resize(int length) {
int min_capacity = buffer.length + length;
int new_capacity = buffer.length;
int new_capacity = buffer.length * DEFAULT_BUFFER_SCALE_FACTOR;

// double the capacity until the buffer is large enough to accommodate the new demand
while (new_capacity < min_capacity) {
Expand All @@ -102,19 +101,72 @@ private void resize(int length) {
// create a new buffer that can hold the newly determined
// capacity and copy the bytes from the old buffer into the new buffer
byte[] new_buffer = new byte[new_capacity];
System.arraycopy(buffer, readIndex, new_buffer, 0, writeIndex);

// update pointers
buffer = new_buffer; // old buffer should get garbage collected
readIndex = 0;
writeIndex = available();
// 0123456789012345678901234567890123
// R
// W
// -------------|--------------------

// if the write index equals the read index, then there is no data in the old buffer that
// is un-read and there is no need to copy any data from the old buffer to the new resized
// buffer ... we can simply reset both the read index and write index to the zero position
// in the new buffer
if (writeIndex == readIndex) {
readIndex = 0; // reset READ pointer
writeIndex = 0; // reset WRITE pointer
}

// 0123456789012345678901234567890123
// R W
// -------|XXXXX|--------------------

// if the write index is greater than the read index, then the write data has not wrapped
// back to the beginning of the circular buffer ... we can simply copy the remaining un-read
// data in the original buffer to the new buffer and reset the read buffer to the zero index
// and adjust the write buffer to the new position in the new buffer
else if (writeIndex > readIndex) {
// copy single payload (non-wrapping) from original buffer
System.arraycopy(buffer, readIndex, new_buffer, 0, writeIndex-readIndex);
readIndex = 0; // reset READ pointer
writeIndex = writeIndex-readIndex; // adjust WRITE pointer
}

// 0123456789012345678901234567890123
// W R
// XXXXXXX|---------------------|XXXX

// it is possible that the write index can be a lower value than the read index; this happens
// when a write operation on the circular buffer has wrapped the end of the buffer and started
// writing bytes at the beginning free space ... in this case we need to copy both the un-read
// data from the read index to the end of the buffer and data at the beginning of the buffer
// up to the write index
else {
// copy two payloads (wrapping) from original circular buffer
int dataLength = buffer.length - readIndex;
System.arraycopy(buffer, readIndex, new_buffer, 0, dataLength); // copy end buffer data to new buffer
System.arraycopy(buffer, 0, new_buffer, dataLength, writeIndex); // copy start buffer data to new buffer
readIndex = 0; // reset READ pointer
writeIndex = dataLength+writeIndex; // adjust WRITE pointer
}

// System.out.println(" - READ INDEX - " + readIndex);
// System.out.println(" - WRITE INDEX - " + writeIndex);
// System.out.println(" - NEW LENGTH - " + length);
// System.out.println(" - MIN CAPACITY - " + min_capacity);
// System.out.println(" - NEW CAPACITY - " + new_capacity);
// System.out.println(" - OLD BUFFER - " + new String(buffer));
// System.out.println(" - NEW BUFFER - " + new String(new_buffer));

// update buffer object reference
// old buffer should get garbage collected
buffer = new_buffer;
}

public void write(byte[] data) throws IOException, BufferOverflowException {
public void write(byte[] data) throws IOException, BufferOverflowException {
write(data, 0, data.length);
}

public void write(byte[] data, int offset, int length) throws IOException {
public void write(byte[] data, int offset, int length) throws IOException {
while (length > 0) {
int remaining_space = remaining();
if(remaining_space < length) {
Expand Down
38 changes: 38 additions & 0 deletions pi4j-core/src/main/java/com/pi4j/util/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,42 @@ public static String concat(String ... data) {
}
return sb.toString();
}

public static String byteArrayToHex(byte[] data) {
return byteArrayToHex(data, ",");
}

public static String byteArrayToHex(byte[] data, CharSequence delimiter) {
return byteArrayToHex(data, delimiter, "0x");
}

public static String byteArrayToHex(byte[] data, CharSequence delimiter, CharSequence prefix) {

// calculate the size needed for the string builder and create a new string builder
int length = (data.length * 2) + (data.length * delimiter.length()) + (data.length * prefix.length());
StringBuilder sb = new StringBuilder(length);

// determine if delimiter and prefix are present
boolean has_delimiter = delimiter.length() > 0;
boolean has_prefix = prefix.length() > 0;
int byte_count = 0;

// iterate over all the bytes in the byte array
for(byte b: data) {
// append delimiter if previous bytes have already been included in the string builder
if(has_delimiter && byte_count > 0) sb.append(delimiter);

// append prefix
if(has_prefix) sb.append(prefix);

// append HEX representation of byte
sb.append(String.format("%02X", b));

// increment byte count
byte_count++;
}

// return HEX string
return sb.toString();
}
}
143 changes: 143 additions & 0 deletions pi4j-example/src/main/java/SerialBufferedDataExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* #%L
* **********************************************************************
* ORGANIZATION : Pi4J
* PROJECT : Pi4J :: Java Examples
* FILENAME : SerialBufferedDataExample.java
*
* This file is part of the Pi4J project. More information about
* this project can be found here: https://pi4j.com/
* **********************************************************************
* %%
* Copyright (C) 2012 - 2021 Pi4J
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import com.pi4j.io.serial.*;
import com.pi4j.util.CommandArgumentParser;
import com.pi4j.util.Console;
import com.pi4j.util.StringUtil;

import java.io.IOException;

/**
* This example code demonstrates how to perform serial communications using the Raspberry Pi.
* This example configures Pi4J to buffer received data in memory and will print the received data
* out via the console every ten seconds.
*
* @author Robert Savage
*/
public class SerialBufferedDataExample {

/**
* This example program supports the following optional command arguments/options:
* "--device (device-path)" [DEFAULT: /dev/ttyAMA0]
* "--baud (baud-rate)" [DEFAULT: 38400]
* "--data-bits (5|6|7|8)" [DEFAULT: 8]
* "--parity (none|odd|even)" [DEFAULT: none]
* "--stop-bits (1|2)" [DEFAULT: 1]
* "--flow-control (none|hardware|software)" [DEFAULT: none]
*
* @param args
* @throws InterruptedException
* @throws IOException
*/
public static void main(String args[]) throws InterruptedException, IOException {

// !! ATTENTION !!
// By default, the serial port is configured as a console port
// for interacting with the Linux OS shell. If you want to use
// the serial port in a software program, you must disable the
// OS from using this port.
//
// Please see this blog article for instructions on how to disable
// the OS console for this port:
// https://www.cube-controls.com/2015/11/02/disable-serial-port-terminal-output-on-raspbian/

// create Pi4J console wrapper/helper
// (This is a utility class to abstract some of the boilerplate code)
final Console console = new Console();

// print program title/header
console.title("<-- The Pi4J Project -->", "Serial Buffered Data Example");

// allow for user to exit program using CTRL-C
console.promptForExit();

// create an instance of the serial communications class
final Serial serial = SerialFactory.createInstance();

// enable receive data buffer
serial.setBufferingDataReceived(true);

try {
// create serial config object
SerialConfig config = new SerialConfig();

// set default serial settings (device, baud rate, flow control, etc)
//
// by default, use the DEFAULT com port on the Raspberry Pi (exposed on GPIO header)
// NOTE: this utility method will determine the default serial port for the
// detected platform and board/model. For all Raspberry Pi models
// except the 3B, it will return "/dev/ttyAMA0". For Raspberry Pi
// model 3B may return "/dev/ttyS0" or "/dev/ttyAMA0" depending on
// environment configuration.
config.device(SerialPort.getDefaultPort())
.baud(Baud._38400)
.dataBits(DataBits._8)
.parity(Parity.NONE)
.stopBits(StopBits._1)
.flowControl(FlowControl.NONE);

// parse optional command argument options to override the default serial settings.
if(args.length > 0){
config = CommandArgumentParser.getSerialConfig(config, args);
}

// display connection details
console.box(" Connecting to: " + config.toString(),
" Data received on serial port will be displayed below every ten seconds.");

// open the default serial device/port with the configuration settings
serial.open(config);

// continuous loop to keep the program running until the user terminates the program
while(console.isRunning()) {
try {
// wait 10 seconds before reading data;
// any serial data will be buffered inside Pi4J serial data receive buffer
Thread.sleep(10000);

// if any data is available in the serial receive buffer, then read it now
int available = serial.available();
if(available > 0) {
byte[] data = serial.read();
System.out.print("[" + available + " BYTES AVAILABLE] : ");
System.out.println(StringUtil.byteArrayToHex(data));
} else {
System.out.println("[NO DATA AVAILABLE]");
}
}
catch(IllegalStateException ex){
ex.printStackTrace();
}
}
}
catch(IOException ex) {
console.println(" ==>> SERIAL SETUP FAILED : " + ex.getMessage());
return;
}
}
}

0 comments on commit 2570dce

Please sign in to comment.