Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
fa30ba0
Pure Pascal wire protocol client: crypto, XDR and protocol engine
mariuz Jul 31, 2026
b08f5bd
Wire protocol provider for fbintf: IFirebirdAPI without fbclient
mariuz Jul 31, 2026
ac37134
Make the negotiated protocol version capping configurable and test it
mariuz Jul 31, 2026
e32d07a
CI matrix for Firebird 3, 4, 5 and 6, and the wire protocol documenta…
mariuz Jul 31, 2026
076db39
Take the column character set from the describe response, and fix the
mariuz Jul 31, 2026
08350eb
Record the CI verified server matrix in the documentation
mariuz Jul 31, 2026
306e6a4
Add a roadmap for the wire protocol client
mariuz Jul 31, 2026
81db415
Add the design document for roadmap milestone 01: Run the existing te…
mariuz Aug 1, 2026
01221fb
Add the design document for roadmap milestone 02: Events
mariuz Aug 1, 2026
dd3dccc
Add the design document for roadmap milestone 03: Services
mariuz Aug 1, 2026
e191384
Add the design document for roadmap milestone 04: A Delphi transport
mariuz Aug 1, 2026
c94c7e8
Add the design document for roadmap milestone 05: Array columns
mariuz Aug 1, 2026
fa6e533
Add the design document for roadmap milestone 06: Statement timeouts …
mariuz Aug 1, 2026
e9ec2bd
Add the design document for roadmap milestone 07: Scrollable cursors
mariuz Aug 1, 2026
30bef27
Add the design document for roadmap milestone 08: The batch API
mariuz Aug 1, 2026
ac72748
Add the design document for roadmap milestone 09: Inline blobs
mariuz Aug 1, 2026
864bb1e
Add the design document for roadmap milestone 10: Firebird 6 protocol 20
mariuz Aug 1, 2026
c981deb
Add the design document for roadmap milestone 11: Wire compression
mariuz Aug 1, 2026
184494b
Add the design document for roadmap milestone 12: Engine message text
mariuz Aug 1, 2026
9b46e14
Implement the services API for the wire protocol provider
mariuz Aug 1, 2026
1a8eacb
Record the CI verified test totals for the services milestone
mariuz Aug 1, 2026
59a11b8
Run the full test suite over the wire provider
mariuz Aug 1, 2026
d8b311d
Merge the test suite milestone so events can be tested with it
mariuz Aug 1, 2026
a99c03d
Implement events for the wire protocol provider
mariuz Aug 1, 2026
3ae5c9b
Take the suite reference log from the CI environment
mariuz Aug 1, 2026
8ec7922
Merge the CI flavoured reference log fix from milestone 1
mariuz Aug 1, 2026
b8a4d47
Make events testable in CI and the suite comparison environment proof
mariuz Aug 1, 2026
a15f250
Normalise the remaining run dependent suite output values
mariuz Aug 1, 2026
d6351e2
Merge remote-tracking branch 'origin/roadmap/01-testsuite' into roadm…
mariuz Aug 1, 2026
cdafa50
Merge remote-tracking branch 'origin/roadmap/02-events' into roadmap/…
mariuz Aug 1, 2026
0808399
Implement array columns for the wire protocol provider
mariuz Aug 1, 2026
e1b43b7
Take the arrays flavoured suite reference log from the CI environment
mariuz Aug 1, 2026
d87d9c9
Record the transaction pinning consequence of the column variable leak
mariuz Aug 1, 2026
680c364
Merge remote-tracking branch 'origin/roadmap/05-arrays' into roadmap/…
mariuz Aug 1, 2026
58b1761
Implement statement timeouts and operation cancellation
mariuz Aug 1, 2026
11f93a6
Merge remote-tracking branch 'origin/roadmap/06-cancel-timeouts' into…
mariuz Aug 1, 2026
fa12ef1
Implement scrollable cursors and raise the protocol offer to 18
mariuz Aug 1, 2026
8650a91
Make the WireTest scroll section self contained
mariuz Aug 1, 2026
fc1d55a
Take the protocol 18 suite reference log from the CI environment
mariuz Aug 1, 2026
6edb154
Merge remote-tracking branch 'origin/roadmap/07-scrollable-cursors' i…
mariuz Aug 1, 2026
e34f219
Implement the batch API for the wire protocol provider
mariuz Aug 1, 2026
ccbb72a
Take the batch flavoured suite reference log from the CI environment
mariuz Aug 1, 2026
620b2e8
Merge remote-tracking branch 'origin/roadmap/08-batch-api' into roadm…
mariuz Aug 1, 2026
e5ee248
Implement inline blobs and raise the protocol offer to 19
mariuz Aug 1, 2026
b6d70f0
Take the protocol 19 suite reference log from the CI environment
mariuz Aug 1, 2026
0477aed
Merge remote-tracking branch 'origin/roadmap/09-inline-blobs' into ro…
mariuz Aug 1, 2026
c16c382
Negotiate protocol 20 with Firebird 6: schemas and the prepare flags
mariuz Aug 1, 2026
6948af1
Take the protocol 20 suite reference log from the CI environment
mariuz Aug 1, 2026
d96de60
Merge remote-tracking branch 'origin/roadmap/10-protocol-20' into roa…
mariuz Aug 1, 2026
54654e0
Implement zlib wire compression beneath the cipher
mariuz Aug 1, 2026
9fd48ce
Merge remote-tracking branch 'origin/roadmap/11-wire-compression' int…
mariuz Aug 1, 2026
abdd1e9
Format engine error messages without firebird.msg
mariuz Aug 1, 2026
6942c14
Take the message text flavoured suite reference log from CI
mariuz Aug 1, 2026
4ddb33d
Merge pull request #2 from mariuz/roadmap/01-testsuite
mariuz Aug 1, 2026
5127aba
Merge pull request #3 from mariuz/roadmap/02-events
mariuz Aug 1, 2026
636fbc2
Merge pull request #6 from mariuz/roadmap/05-arrays
mariuz Aug 1, 2026
1b3b606
Merge pull request #7 from mariuz/roadmap/06-cancel-timeouts
mariuz Aug 1, 2026
b8e8402
Merge pull request #8 from mariuz/roadmap/07-scrollable-cursors
mariuz Aug 1, 2026
63cb3f1
Merge pull request #9 from mariuz/roadmap/08-batch-api
mariuz Aug 1, 2026
04c61d1
Merge pull request #10 from mariuz/roadmap/09-inline-blobs
mariuz Aug 1, 2026
785d0f4
Merge pull request #11 from mariuz/roadmap/10-protocol-20
mariuz Aug 1, 2026
f01c14c
Merge pull request #12 from mariuz/roadmap/11-wire-compression
mariuz Aug 1, 2026
0c12dc2
Merge pull request #13 from mariuz/roadmap/12-message-text
mariuz Aug 1, 2026
e0f9ce7
Merge remote-tracking branch 'origin/pure-pascal-wire-protocol' into …
mariuz Aug 1, 2026
e02aa12
Update the suite tables for the services section landing on the full …
mariuz Aug 1, 2026
bee5c8d
Merge pull request #4 from mariuz/roadmap/03-services
mariuz Aug 1, 2026
216d0f9
Merge remote-tracking branch 'origin/pure-pascal-wire-protocol' into …
mariuz Aug 1, 2026
0e7a726
Add the Delphi transport branches and package the wire units for Delphi
mariuz Aug 1, 2026
9ef6aa4
Merge pull request #5 from mariuz/roadmap/04-delphi-transport
mariuz Aug 1, 2026
5d2edd7
Regenerate the wire reference log for the services landing
mariuz Aug 1, 2026
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
324 changes: 324 additions & 0 deletions .github/workflows/wire-protocol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
name: Wire protocol client

# Exercises the pure Pascal wire protocol client (client/wire) against every
# supported Firebird major version. The client needs no fbclient library, so
# the server runs in a container and the test reaches it over TCP only:
# nothing Firebird related is installed on the runner itself.

on:
push:
branches: [ main, 'pure-pascal-wire-protocol' ]
paths:
- 'client/**'
- 'testsuite/**'
- '.github/workflows/wire-protocol.yml'
pull_request:
paths:
- 'client/**'
- 'testsuite/**'
- '.github/workflows/wire-protocol.yml'
workflow_dispatch:

env:
FB_USER: SYSDBA
FB_PASSWORD: masterkey
# Server side paths. The client never opens these itself: it asks the
# server to, so they are resolved inside the container.
FB_DATABASE: /tmp/wiretest.fdb
FB_SCRATCH: /tmp/wirescratch.fdb

jobs:
server:
name: FB ${{ matrix.firebird }} / WireCrypt ${{ matrix.wirecrypt }}${{ matrix.wirecompression == 'true' && ' / Compressed' || '' }}
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
firebird: ['3', '4', '5', '6']
wirecrypt: ['Enabled', 'Required']
wirecompression: ['false']
include:
- firebird: '3'
image: 'firebirdsql/firebird:3'
# Firebird 3 tops out at protocol 15 and has no ChaCha plugin,
# so the connection settles on Arc4.
expected: '15'
- firebird: '4'
image: 'firebirdsql/firebird:4'
expected: '17'
- firebird: '5'
image: 'firebirdsql/firebird:5'
expected: '19'
- firebird: '6'
image: 'firebirdsql/firebird:6-snapshot'
expected: '20'
# One compression row: the zlib pipeline is protocol version
# independent, so a single server exercises it. WireTest's
# compression section negotiates it explicitly. Naming a
# different value for the wirecompression axis makes this a new
# combination rather than extending the existing FB 5 row.
- firebird: '5'
wirecrypt: 'Enabled'
wirecompression: 'true'
image: 'firebirdsql/firebird:5'
expected: '19'
exclude:
# Protocol 13, the only one Firebird 3 offers without encryption
# support, cannot satisfy WireCrypt = Required. The combination is
# covered by the newer versions.
- firebird: '3'
wirecrypt: 'Required'

steps:
- uses: actions/checkout@v4

- name: Start Firebird ${{ matrix.firebird }}
run: |
docker run -d \
--name firebird \
-e FIREBIRD_ROOT_PASSWORD="${FB_PASSWORD}" \
-e FIREBIRD_CONF_WireCrypt="${{ matrix.wirecrypt }}" \
-e FIREBIRD_CONF_AuthServer="Srp256;Srp;Legacy_Auth" \
-e FIREBIRD_CONF_DatabaseAccess="Full" \
-e FIREBIRD_CONF_RemoteAuxPort="3051" \
-e FIREBIRD_CONF_WireCompression="${{ matrix.wirecompression || 'false' }}" \
-p 3050:3050 \
-p 3051:3051 \
"${{ matrix.image }}"

echo "Waiting for the server to accept connections"
ready=false
for i in $(seq 1 45); do
if docker exec firebird /opt/firebird/bin/isql \
-user "${FB_USER}" -password "${FB_PASSWORD}" -z 2>&1 \
| grep -q "ISQL Version"; then
ready=true
break
fi
sleep 2
done
if [ "$ready" != true ]; then
echo "::error::Firebird did not become ready"
docker logs firebird
exit 1
fi
docker exec firebird /opt/firebird/bin/isql \
-user "${FB_USER}" -password "${FB_PASSWORD}" -z 2>&1 | head -1

- name: Create the test database
run: |
docker exec -i firebird /opt/firebird/bin/isql \
-user "${FB_USER}" -password "${FB_PASSWORD}" -q <<SQL
create database '${FB_DATABASE}' page_size 8192 default character set UTF8;
create table SANITY (ID integer not null primary key, NAME varchar(30));
insert into SANITY values (1, 'first');
commit;
quit;
SQL
# The scratch database must not exist: the test creates and drops it.
docker exec firebird rm -f "${FB_SCRATCH}" || true

- name: Install Free Pascal
run: |
sudo apt-get update
sudo apt-get install -y fpc
echo "compiler: $(fpc -iV), target: $(fpc -iTO)-$(fpc -iTP)"

- name: Build the package
run: |
mkdir -p build
fpc -Fuclient -Fuclient/2.5 -Fuclient/3.0 -Fuclient/3.0/firebird \
-Fuclient/wire -Ficlient/include -FEbuild -B fbintf.pas

- name: Build the test
run: |
fpc -Fuclient -Fuclient/2.5 -Fuclient/3.0 -Fuclient/3.0/firebird \
-Fuclient/wire -Ficlient/include -FEbuild -B \
testsuite/WireTest.pas

- name: Run the test
shell: bash
run: |
set -o pipefail
./build/WireTest "localhost:${FB_DATABASE}" \
"${FB_USER}" "${FB_PASSWORD}" \
"localhost:${FB_SCRATCH}" | tee build/wiretest.log

- name: Summarise
if: always()
run: |
{
echo "### Firebird ${{ matrix.firebird }}, WireCrypt ${{ matrix.wirecrypt }}"
echo
if [ -f build/wiretest.log ]; then
echo '```'
grep -E "^ *protocol [0-9]+|^[0-9]+ tests" build/wiretest.log || true
echo '```'
echo
echo "Protocol expected to reach ${{ matrix.expected }} on this server."
echo
if grep -q "FAIL" build/wiretest.log; then
echo "Failures:"
echo '```'
grep "FAIL" build/wiretest.log
echo '```'
fi
else
echo "The test produced no output."
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Firebird log on failure
if: failure()
run: |
echo "===== firebird.log ====="
docker exec firebird sh -c 'cat $(find / -name firebird.log 2>/dev/null | head -1)' \
2>/dev/null | tail -n 200 || echo "(no firebird.log found)"
echo "===== container ====="
docker ps -a
docker logs firebird --tail 100

offline:
# The parts that need no server: arbitrary precision arithmetic, the
# hashes and ciphers against their published vectors, the SRP exchange
# against a fixed reference exchange, and the message layout. Running
# them separately keeps a compiler or arithmetic regression from being
# reported as a server problem, and covers Windows where no Firebird
# container is available.
name: Offline tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v4

- name: Install Free Pascal
uses: gcarreno/setup-lazarus@v3
with:
lazarus-version: 'stable'

- name: Show the compiler version
shell: bash
run: fpc -iV

- name: Build and run the offline tests
shell: bash
run: |
mkdir -p build
fpc -Fuclient -Fuclient/2.5 -Fuclient/3.0 -Fuclient/3.0/firebird \
-Fuclient/wire -Ficlient/include -FEbuild -B \
testsuite/WireTest.pas
# Nothing listens on port 3051, so every section that needs a
# server reports SKIP straight away and the rest still runs.
./build/WireTest "localhost/3051:/nonexistent.fdb" SYSDBA masterkey

suite:
# The full fbintf test suite - all twenty two test programs - run over
# the wire protocol provider and compared line by line against
# testsuite/FBWirereference.log. The suite needs the employee example
# database, which the container images do not ship, so it is restored
# from the checked in backup. The reference log was produced against
# Firebird 6 (ODS 14, protocol 17), so this job runs that server.
name: Full test suite over the wire (FB 6)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Start Firebird 6
run: |
mkdir -p /tmp/fbintf-testsuite
chmod 777 /tmp/fbintf-testsuite
docker run -d \
--name firebird \
-e FIREBIRD_ROOT_PASSWORD="${FB_PASSWORD}" \
-e FIREBIRD_CONF_WireCrypt="Enabled" \
-e FIREBIRD_CONF_AuthServer="Srp256;Srp;Legacy_Auth" \
-e FIREBIRD_CONF_DatabaseAccess="Full" \
-e FIREBIRD_CONF_RemoteAuxPort="3051" \
-e FIREBIRD_CONF_WireCompression="${{ matrix.wirecompression || 'false' }}" \
-v /tmp/fbintf-testsuite:/tmp/fbintf-testsuite \
-p 3050:3050 \
-p 3051:3051 \
firebirdsql/firebird:6-snapshot
echo "Waiting for the server to accept connections"
ready=false
for i in $(seq 1 45); do
if docker exec firebird /opt/firebird/bin/isql \
-user "${FB_USER}" -password "${FB_PASSWORD}" -z 2>&1 \
| grep -q "ISQL Version"; then
ready=true
break
fi
sleep 2
done
if [ "$ready" != true ]; then
echo "::error::Firebird did not become ready"
docker logs firebird
exit 1
fi

- name: Restore the employee example database
run: |
docker cp testsuite/employee.gbk firebird:/tmp/employee.gbk
docker exec firebird /opt/firebird/bin/gbak -c \
-user "${FB_USER}" -password "${FB_PASSWORD}" \
/tmp/employee.gbk /tmp/fbintf-testsuite/employee.fdb
docker exec firebird sh -c \
'echo "employee = /tmp/fbintf-testsuite/employee.fdb" >> /opt/firebird/databases.conf'
docker exec firebird /opt/firebird/bin/isql \
-user "${FB_USER}" -password "${FB_PASSWORD}" \
-q localhost:employee <<'SQL'
select count(*) from EMPLOYEE;
quit;
SQL

- name: Install Free Pascal
run: |
sudo apt-get update
sudo apt-get install -y fpc
echo "compiler: $(fpc -iV), target: $(fpc -iTO)-$(fpc -iTP)"

- name: Run the test suite over the wire provider
run: |
testsuite/runtest.sh -a wire

- name: Check the reference log comparison
run: |
# runtest.sh always exits 0: the outcome is the diff, and the log
# must show a completed run for the diff to mean anything
if ! grep -aq "Test Suite Ends" testsuite/testout.log; then
echo "::error::The test suite did not run to completion"
tail -50 testsuite/testout.log || true
exit 1
fi
if [ -s testsuite/diff.log ]; then
echo "::error::The suite output differs from FBWirereference.log"
cat testsuite/diff.log
exit 1
fi
echo "The suite output matches the reference log"
{
echo "### Full test suite over the wire"
echo
echo "All twenty two test programs match \`FBWirereference.log\`."
} >> "$GITHUB_STEP_SUMMARY"

- name: Upload the test output
if: always()
uses: actions/upload-artifact@v4
with:
name: wire-suite-testout
path: |
testsuite/testout.log
testsuite/diff.log
if-no-files-found: ignore

- name: Server diagnostics on failure
if: failure()
run: |
docker logs firebird --tail 100
2 changes: 1 addition & 1 deletion Makefile.fpc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ version=0.0

[compiler]
unittargetdir=lib/$(CPU_TARGET)-$(OS_TARGET)
unitdir=client client/3.0/firebird client/2.5 . client/3.0 client/3.0/firebird
unitdir=client client/3.0/firebird client/2.5 . client/3.0 client/3.0/firebird client/wire
includedir=client/include
options= -MObjFPC -Scghi -O3 -l -vewnhibq $(DBG_OPTIONS)

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ See the "doc" directory for more information, installation instructions and a fu
guide to the API. fbintf also comes with a comprehensive test suite. See doc/TestSuite.pdf
for more information.

## Connecting without a client library

From this release fbintf also includes a pure Pascal implementation of the
Firebird remote (wire) protocol in `client/wire`. It talks to a Firebird
3.0, 4.0, 5.0 or 6.0 server directly over TCP and needs no fbclient library
installed, which suits containers, single file deployments and cross
compiled targets. SRP authentication and wire encryption (ChaCha64, ChaCha
and Arc4) are supported.

Code written against the fbintf interfaces runs unchanged; only the way the
API is obtained differs:

```pascal
uses IB, FBWireClientAPI;

API := WireFirebirdAPI; {instead of IB.FirebirdAPI}
```

See doc/WireProtocol.md for the full description, and client/wire/README.md
for a summary of what is and is not implemented. The implementation is FPC
only for now: the transport uses the FPC socket units.

See the "changelog" for information on changes from previous releases


15 changes: 15 additions & 0 deletions client/2.5/FB25Attachment.pas
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ TFB25Attachment = class(TFBAttachment, IAttachment, IActivityMonitor)
function GetArrayMetaData(Transaction: ITransaction; tableName, columnName: AnsiString): IArrayMetaData; override;
procedure getFBVersion(version: TStrings);
function HasScollableCursors: boolean;
{fb_cancel_operation - raises ibxeNotSupported when the loaded
library does not export it}
procedure CancelOperation(aKind: integer = fb_cancel_raise); override;
end;

implementation
Expand Down Expand Up @@ -359,5 +362,17 @@ function TFB25Attachment.HasScollableCursors: boolean;
Result := false;
end;

procedure TFB25Attachment.CancelOperation(aKind: integer);
begin
CheckHandle;
with FFirebird25ClientAPI do
begin
if not assigned(fb_cancel_operation) then
IBError(ibxeNotSupported,[nil]);
if fb_cancel_operation(StatusVector,@FHandle,aKind) > 0 then
IBDataBaseError;
end;
end;

end.

Loading