Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public interface SearchResponseHandler
* @param server Server that replied to a search request
* @param version Server version
* @param guid Globally unique ID of the server
* @param tcp Does server require TLS?
* @param tls Does server require TLS?
*/
void handleSearchResponse(int channel_id, InetSocketAddress server, int version, Guid guid, boolean tls);
}
Expand Down Expand Up @@ -250,15 +250,15 @@ private boolean handleBeacon(final InetSocketAddress from, final byte version,
else
server = new InetSocketAddress(addr, port);

final String protocol = PVAString.decodeString(buffer);
if (! "tcp".equals(protocol))
{
logger.log(Level.WARNING, "PVA Server " + from + " sent beacon for protocol '" + protocol + "'");
return false;
}

try
{
final String protocol = PVAString.decodeString(buffer);
if (! "tcp".equals(protocol))
{
logger.log(Level.WARNING, "PVA Server " + from + " sent beacon for protocol '" + protocol + "'");
return false;
}

// Decode optional server status (likely null)
final PVATypeRegistry types = new PVATypeRegistry();
final PVAData server_status = types.decodeType("", buffer);
Expand Down
23 changes: 18 additions & 5 deletions core/pva/src/main/java/org/epics/pva/common/PVAHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.epics.pva.exceptions.PVAProtocolException;

/** PVA Message Header
*
* <pre>
Expand Down Expand Up @@ -174,9 +176,9 @@ public static void encodeMessageHeader(final ByteBuffer buffer, byte flags, fina
* @param buffer Buffer as start of protocol header
* @param expect_server Expect a server message? Else client message
* @return Expected total message size (header + payload)
* @throws Exception on protocol violation
* @throws PVAProtocolException on protocol violation
*/
public static int checkMessageAndGetSize(final ByteBuffer buffer, final boolean expect_server) throws Exception
public static int checkMessageAndGetSize(final ByteBuffer buffer, final boolean expect_server) throws PVAProtocolException
{
if (buffer.position() < PVAHeader.HEADER_SIZE)
return PVAHeader.HEADER_SIZE;
Expand All @@ -185,19 +187,19 @@ public static int checkMessageAndGetSize(final ByteBuffer buffer, final boolean
// parsing the initial set of bytes
final byte magic = buffer.get(0);
if (magic != PVAHeader.PVA_MAGIC)
throw new Exception(String.format("Message lacks magic 0x%02X, got 0x%02X", PVAHeader.PVA_MAGIC, magic));
throw new PVAProtocolException(String.format("Message lacks magic 0x%02X, got 0x%02X", PVAHeader.PVA_MAGIC, magic));

final byte version = buffer.get(1);
if (version < PVAHeader.REQUIRED_PVA_PROTOCOL_REVISION)
throw new Exception("Cannot handle protocol version " + version +
throw new PVAProtocolException("Cannot handle protocol version " + version +
", expect version " +
PVAHeader.REQUIRED_PVA_PROTOCOL_REVISION +
" or higher");

final byte flags = buffer.get(2);
final boolean is_server = (flags & PVAHeader.FLAG_SERVER) != 0;
if (is_server != expect_server)
throw new Exception(expect_server ? "Expected server message" : "Expected client message");
throw new PVAProtocolException(expect_server ? "Expected server message" : "Expected client message");

// With each received message, check the byte order
// and adjust buffer to read further content which usually
Expand All @@ -213,6 +215,17 @@ public static int checkMessageAndGetSize(final ByteBuffer buffer, final boolean

// Application messages are followed by this number of data bytes
final int payload = buffer.getInt(PVAHeader.HEADER_OFFSET_PAYLOAD_SIZE);
// Java implementation for now does not handle large 'unsigned' sizes,
// limited to the positive range of a signed int.
// Could use `Integer.toUnsignedLong(payload)`, but JDK API
// like buffer buffer.remaining() or buffer.get(10) is using int,
// so us updating to long would be of limited use
if (payload < 0)
throw new PVAProtocolException("Payload size " + payload +
" exceeds max signed integer " + Integer.toHexString(Integer.MAX_VALUE));
// Could check against a PVA variant of EPICS_CA_MAX_ARRAY_BYTES,
// but PVA design specifically aims to use all available memory
// without self-enforced limitations (confirmed in 2026-07-10 EPICS code telecon)

// Total message size: Header followed by data
return PVAHeader.HEADER_SIZE + payload;
Expand Down
68 changes: 44 additions & 24 deletions core/pva/src/main/java/org/epics/pva/common/SearchRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
@SuppressWarnings("nls")
public class SearchRequest
{
private static final String LOG_PVA_CLIENT = "PVA Client ";
private static final String LOG_SENT_SEARCH = " sent search #";

/** Channel with CID to be searched */
public static class Channel
{
Expand Down Expand Up @@ -61,7 +64,7 @@ public String toString()
{
return "'" + name + "' [CID " + cid + "]";
}
};
}

/** Server should reply with its GUID and empty CID list
* even if it does not host any of the searched channels
Expand Down Expand Up @@ -125,7 +128,7 @@ public static SearchRequest decode(final OriginTag origin, final InetSocketAddre
// plus the list of names.
if (payload < 4+1+3+16+2+1+2)
{
logger.log(Level.WARNING, "PVA client " + from + " sent only " + payload + " bytes for search request");
logger.log(Level.WARNING, () -> "PVA client " + from + " sent only " + payload + " bytes for search request");
return null;
}
final SearchRequest search = new SearchRequest();
Expand All @@ -151,7 +154,7 @@ public static SearchRequest decode(final OriginTag origin, final InetSocketAddre
}
catch (Exception ex)
{
logger.log(Level.WARNING, "PVA Client " + from + " sent search #" + search.seq + " with invalid address");
logger.log(Level.WARNING, () -> LOG_PVA_CLIENT + from + LOG_SENT_SEARCH + search.seq + " with invalid address");
return null;
}
int port = Short.toUnsignedInt(buffer.getShort());
Expand All @@ -173,15 +176,23 @@ public static SearchRequest decode(final OriginTag origin, final InetSocketAddre
boolean tcp = search.tls = false;
int count = Byte.toUnsignedInt(buffer.get());
String unknown_protocol = "<none>";
for (int i=0; i<count; ++i)
try
{
final String protocol = PVAString.decodeString(buffer);
if ("tls".equals(protocol))
search.tls = true;
else if ("tcp".equals(protocol))
tcp = true;
else
unknown_protocol = protocol;
for (int i=0; i<count; ++i)
{
final String protocol = PVAString.decodeString(buffer);
if ("tls".equals(protocol))
search.tls = true;
else if ("tcp".equals(protocol))
tcp = true;
else
unknown_protocol = protocol;
}
}
catch (Exception ex)
{
logger.log(Level.WARNING, ex, () -> LOG_PVA_CLIENT + from + LOG_SENT_SEARCH + search.seq + " with invalid protocol");
return null;
}

// Loop over searched channels
Expand All @@ -190,28 +201,37 @@ else if ("tcp".equals(protocol))
if (count == 0)
{ // pvlist request
search.channels = null;
logger.log(Level.FINER, () -> "PVA Client " + from + " sent search #" + search.seq + " to list servers");
logger.log(Level.FINER, () -> LOG_PVA_CLIENT + from + LOG_SENT_SEARCH + search.seq + " to list servers");
}
else
{ // Channel search request
if (! (tcp || search.tls))
{
logger.log(Level.WARNING, "PVA Client " + from + " sent search #" + search.seq + " for protocol '" + unknown_protocol + "', need 'tcp' or 'tls'");
final String unsupported_protocol = unknown_protocol;
logger.log(Level.WARNING, () -> LOG_PVA_CLIENT + from + LOG_SENT_SEARCH + search.seq + " for protocol '" + unsupported_protocol + "', need 'tcp' or 'tls'");
return null;
}
search.channels = new ArrayList<>(count);
for (int i=0; i<count; ++i)
try
{
final int cid = buffer.getInt();
final String name = PVAString.decodeString(buffer);
logger.log(Level.FINER, () -> "PVA Client " + from + " sent search #" + search.seq + " for " + name + " [cid " + cid + "]"
+ ", reply addr " + orig_response_addr
+ (orig_response_addr.equals(search.client) ? "" : ", using " + search.client)
+ (search.tls ? " (TLS)" : "")
+ (search.unicast ? " (unicast)" : "")
+ (search.reply_required ? " (reply required)" : "")
+ (search.reply_to_src_port ? (origin == null ? " (reply to source port)" : " (reply to source port ignored because of origin tag)") : ""));
search.channels.add(new Channel(cid, name));
for (int i=0; i<count; ++i)
{
final int cid = buffer.getInt();
final String name = PVAString.decodeString(buffer);
logger.log(Level.FINER, () -> LOG_PVA_CLIENT + from + LOG_SENT_SEARCH + search.seq + " for " + name + " [cid " + cid + "]"
+ ", reply addr " + orig_response_addr
+ (orig_response_addr.equals(search.client) ? "" : ", using " + search.client)
+ (search.tls ? " (TLS)" : "")
+ (search.unicast ? " (unicast)" : "")
+ (search.reply_required ? " (reply required)" : "")
+ (search.reply_to_src_port ? (origin == null ? " (reply to source port)" : " (reply to source port ignored because of origin tag)") : ""));
search.channels.add(new Channel(cid, name));
}
}
catch (Exception ex)
{
logger.log(Level.WARNING, ex, () -> LOG_PVA_CLIENT + from + " sent damaged search #" + search.seq);
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@
result.found = PVABool.decodeBoolean(buffer);

final int count = Short.toUnsignedInt(buffer.getShort());
if (count*Integer.BYTES > buffer.remaining())
throw new Exception("PVA Server sent search reply #" + result.seq + " for " + count + " CIDs " +

Check warning on line 101 in core/pva/src/main/java/org/epics/pva/common/SearchResponse.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ_x3ZYsHEaWdj6QFA9c&open=AZ_x3ZYsHEaWdj6QFA9c&pullRequest=3878

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a unit test that checks this exception is raised.

" with only " + buffer.remaining() + " bytes in buffer");
result.cid = new int[count];
for (int i=0; i<count; ++i)
result.cid[i] = buffer.getInt();
Expand Down
5 changes: 5 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVAAnyArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package org.epics.pva.data;

import org.epics.pva.exceptions.PVAArraySizeException;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.BitSet;
Expand Down Expand Up @@ -128,6 +130,9 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception {
public void decode(PVATypeRegistry types, ByteBuffer buffer) throws Exception {

final int count = PVASize.decodeSize(buffer);
// Each array element needs at least the 'non_null' byte
if (count < 0 || count > buffer.remaining())
throw new PVAArraySizeException(count, buffer.remaining());
// Try to re-use elements
PVAny[] new_elements = elements;
if (new_elements == null || new_elements.length != count)
Expand Down
5 changes: 4 additions & 1 deletion core/pva/src/main/java/org/epics/pva/data/PVABitSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@

/** @param buffer Source buffer
* @return Decoded bits
* @throws Exception on error
*/
public static BitSet decodeBitSet(final ByteBuffer buffer)
public static BitSet decodeBitSet(final ByteBuffer buffer) throws Exception

Check warning on line 32 in core/pva/src/main/java/org/epics/pva/data/PVABitSet.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ_x3ZV2HEaWdj6QFA9U&open=AZ_x3ZV2HEaWdj6QFA9U&pullRequest=3878
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || size > buffer.remaining())
throw new Exception("Bitset size " + size + " with only " + buffer.remaining() + " bytes in buffer");

Check warning on line 36 in core/pva/src/main/java/org/epics/pva/data/PVABitSet.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ_x3ZV2HEaWdj6QFA9T&open=AZ_x3ZV2HEaWdj6QFA9T&pullRequest=3878
final byte[] bytes = new byte[size];
buffer.get(bytes);
return BitSet.valueOf(bytes);
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVABoolArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -95,6 +96,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || size > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining());
final boolean[] new_value = new boolean[size];
for (int i=0; i<size; ++i)
new_value[i] = buffer.get() != 0;
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVAByteArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -114,6 +115,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || size > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining());
final byte[] new_value = new byte[size];
buffer.get(new_value);
value = new_value;
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVADoubleArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -98,6 +99,8 @@ public void encodeType(final ByteBuffer buffer, final BitSet described) throws E
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || (long) size * Double.BYTES > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining(), Double.BYTES);
final double[] new_value = new double[size];
for (int i=0; i<size; ++i)
new_value[i] = buffer.getDouble();
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVAFloatArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -99,6 +100,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || (long) size * Float.BYTES > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining(), Float.BYTES);
final float[] new_value = new float[size];
for (int i=0; i<size; ++i)
new_value[i] = buffer.getFloat();
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVAIntArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -111,6 +112,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || (long) size * Integer.BYTES > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining(), Integer.BYTES);
final int[] new_value = new int[size];
for (int i=0; i<size; ++i)
new_value[i] = buffer.getInt();
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVALongArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -111,6 +112,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || (long) size * Long.BYTES > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining(), Long.BYTES);
final long[] new_value = new long[size];
for (int i=0; i<size; ++i)
new_value[i] = buffer.getLong();
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVAShortArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;

import org.epics.pva.PVASettings;
import org.epics.pva.exceptions.PVAArraySizeException;

/** 'Primitive' PV Access data type
* @author Kay Kasemir
Expand Down Expand Up @@ -111,6 +112,8 @@ public void encodeType(ByteBuffer buffer, BitSet described) throws Exception
public void decode(final PVATypeRegistry types, final ByteBuffer buffer) throws Exception
{
final int size = PVASize.decodeSize(buffer);
if (size < 0 || (long) size * Short.BYTES > buffer.remaining())
throw new PVAArraySizeException(size, buffer.remaining(), Short.BYTES);
// Try to re-use existing array
final short[] new_value = new short[size];
// Considered using
Expand Down
3 changes: 3 additions & 0 deletions core/pva/src/main/java/org/epics/pva/data/PVASize.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ else if (size < 254)
*/
public static final int decodeSize(final ByteBuffer buffer)
{
// XXXX Update to long, using Integer.toUnsignedLong(..)?
// JDK api like buffer.remaining() is limited to int,
// so this would have limited effect...
byte b = buffer.get();
if (b == -1)
return -1;
Expand Down
3 changes: 2 additions & 1 deletion core/pva/src/main/java/org/epics/pva/data/PVAStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ public void encode(final ByteBuffer buffer)

/** @param buffer Source buffer
* @return Decoded status
* @throws Exception on error
*/
public static PVAStatus decode(final ByteBuffer buffer)
public static PVAStatus decode(final ByteBuffer buffer) throws Exception
{
final byte b = buffer.get();
if (b == -1)
Expand Down
Loading
Loading