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
14 changes: 8 additions & 6 deletions q.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ static inline size_t ray_scalar_elem_size(int8_t type) {
#define Q_ERR (-128)

#define Q_MSG_SYNC 1
#define Q_MAX_BODY ((int64_t)256 << 20)

typedef struct {
uint8_t endianness;
Expand Down Expand Up @@ -989,10 +990,9 @@ static int q_decompress(const uint8_t *src, int64_t src_len, uint8_t **out_buf,
}
n = src[d++];
/* A back-reference expands to 2 literal + n copied bytes at result[s].
* A malformed or hostile frame can declare a tiny out_size yet expand
* past it, so reject that here instead of writing past the allocation.
* (r < s <= out_size, so the source reads below stay in bounds.) */
if (s + 2 + n > out_size) {
* Reject frames that point before a complete prior byte-pair exists or
* that would expand past the declared uncompressed size. */
if (r + 1 >= s || s + 2 + n > out_size) {
free(result);
return -1;
}
Expand Down Expand Up @@ -1144,8 +1144,10 @@ int q_exchange(int fd, const uint8_t *req, int64_t req_len, uint8_t **resp,
return -1;
}
int64_t body_len = (int64_t)header.size - (int64_t)sizeof header;
if (body_len <= 0) {
q_set_err(err, errlen, "q: empty response body");
if (body_len <= 0 || body_len > Q_MAX_BODY) {
q_set_err(err, errlen,
body_len <= 0 ? "q: empty response body"
: "q: response body too large");
return -1;
}
uint8_t *body = (uint8_t *)malloc((size_t)body_len);
Expand Down
8 changes: 6 additions & 2 deletions q_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,16 @@ static ray_t *q_read_body(ray_poll_t *poll, ray_selector_t *sel) {
* buffer is free to reuse the moment it returns. */
q_header_t hdr = cd->hdr;
int64_t id = sel->id;
ray_t *req = q_decode(sel->rx.buf->data, body, hdr.compressed, NULL, 0);
char err[128] = {0};
ray_t *req =
q_decode(sel->rx.buf->data, body, hdr.compressed, err, sizeof err);

sel->rx.read_fn = q_read_header;
ray_poll_rx_request(poll, sel, (int64_t)sizeof(q_header_t));

ray_t *result = eval_request(req);
ray_t *result = req ? eval_request(req)
: ray_error("q server: malformed request", "%s",
err[0] ? err : "q server: decode failed");
if (req)
ray_release(req);

Expand Down
42 changes: 40 additions & 2 deletions test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ find_q() {
[[ -n "$QBIN" && -x "$QBIN" ]] || QBIN=""
}

q_home_for() {
local qbin="$1" qdir parent
if command -v python3 >/dev/null 2>&1; then
qbin="$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$qbin")"
fi
qdir="$(cd "$(dirname "$qbin")" && pwd)"
parent="$(cd "$qdir/.." && pwd)"
if [[ -f "$qdir/q.k" ]]; then
printf '%s\n' "$qdir"
elif [[ -f "$parent/q.k" ]]; then
printf '%s\n' "$parent"
else
printf '%s\n' "$parent"
fi
}

PIDS=()
FDS=()
WORK="$(mktemp -d)"
Expand All @@ -77,6 +93,28 @@ wait_for_port "$HOST" "$SERVERPORT" || { echo "driver --serve did not come up on
echo "server up on $HOST:$SERVERPORT; running server round-trip tests..."
"$DRIVER" --host "$HOST" --port "$SERVERPORT" "$RFL_DIR"/server/*.rfl

echo "checking malformed Q frame handling..."
python3 - "$HOST" "$SERVERPORT" <<'PY'
import socket
import struct
import sys

host, port = sys.argv[1], int(sys.argv[2])
with socket.create_connection((host, port), 1.0) as s:
s.sendall(bytes([3, 0]))
if s.recv(1) != bytes([3]):
raise SystemExit("bad Q handshake response")
s.sendall(struct.pack("<BBBBI", 1, 1, 1, 0, 9) + b"x")
resp = s.recv(64)

if len(resp) < 9:
raise SystemExit("malformed-frame test: server closed without Q error")
if resp[:4] != bytes([1, 2, 0, 0]):
raise SystemExit(f"malformed-frame test: unexpected header {resp.hex()}")
if resp[8] != 0x80:
raise SystemExit(f"malformed-frame test: expected Q error, got {resp.hex()}")
PY

# ---- Leg 2: real-q interop against Rayforce server
find_q
if [[ -n "$QBIN" ]]; then
Expand Down Expand Up @@ -131,14 +169,14 @@ if[count bad; -2 "real-q interop FAILED: ",", " sv bad; exit 1]
-1 "real-q interop ok (",(string count names)," assertions)"
exit 0
QEOF
RFPORT="$SERVERPORT" QHOME="$(dirname "$(dirname "$QBIN")")" "$QBIN" "$QSCRIPT" -q < /dev/null
RFPORT="$SERVERPORT" QHOME="${QHOME:-$(q_home_for "$QBIN")}" "$QBIN" "$QSCRIPT" -q < /dev/null
else
echo "SKIP: no q binary found (set Q_BINARY) — skipping real-q interop + client tests"
exit 0
fi

# ---- Leg 3: client tests against real Q server
export QHOME="${QHOME:-$(dirname "$(dirname "$QBIN")")}"
export QHOME="${QHOME:-$(q_home_for "$QBIN")}"
export QLIC="${QLIC:-$QHOME}"
PORT="${PORT:-$(free_port)}"
AUTHPORT="${AUTHPORT:-$(free_port)}"
Expand Down
Loading