Repository: bkaradzic/bnet
Branch: master
Commit: 9e8917cb4eb7
Files: 19
Total size: 67.1 KB
Directory structure:
gitextract_o06ygfsr/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── examples/
│ ├── 00-chat/
│ │ └── chat.cpp
│ ├── 01-http/
│ │ └── http.cpp
│ ├── common/
│ │ ├── common.h
│ │ └── dbg.h
│ └── runtime/
│ └── .gitignore
├── include/
│ └── bnet/
│ └── bnet.h
├── makefile
├── scripts/
│ ├── bnet.lua
│ └── genie.lua
└── src/
├── bnet.cpp
├── bnet_p.h
└── inet_socket.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
end_of_line = lf
max_line_length = 100
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
max_line_length = 80
================================================
FILE: .gitattributes
================================================
*.c eol=lf
*.cpp eol=lf
*.h eol=lf
*.sc eol=lf
*.sh eol=lf
*.m eol=lf
*.mm eol=lf
*.md eol=lf
*.lua eol=lf
*.mk eol=lf
makefile eol=lf
================================================
FILE: .github/FUNDING.yml
================================================
github: [bkaradzic]
================================================
FILE: .github/workflows/main.yml
================================================
name: CI
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
on:
push:
pull_request:
jobs:
msvc:
strategy:
fail-fast: true
matrix:
include: [
{ config: Debug, platform: x64, bindir: 'win64_vs2022', genie-action: 'vs2022', solution-ext: 'sln' },
{ config: Release, platform: x64, bindir: 'win64_vs2022', genie-action: 'vs2022', solution-ext: 'sln' },
]
name: msvc-${{ matrix.config }}-${{ matrix.platform }}
runs-on: windows-latest
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- name: Prepare
uses: microsoft/setup-msbuild@v2
- name: Build
shell: cmd
run: |
cd bnet
..\bx\tools\bin\windows\genie.exe ${{ matrix.genie-action }}
msbuild ".build/projects/${{ matrix.genie-action }}/bnet.${{ matrix.solution-ext }}" /m /v:minimal /p:Configuration=${{ matrix.config }} /p:Platform=${{ matrix.platform }}
mingw:
strategy:
fail-fast: true
matrix:
include: [
{ msystem: MINGW64, project: 'mingw-gcc', bindir: 'win64_mingw-gcc' },
{ msystem: CLANG64, project: 'mingw-clang', bindir: 'win64_mingw-clang' },
]
name: mingw-${{ matrix.msystem }}
runs-on: windows-2022
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- name: Prepare
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
install: make
pacboy: cc:p
- name: Build
shell: msys2 {0}
run: |
cd bnet
make ${{ matrix.project }}-release64 -j$(nproc) AR=ar CC=cc CXX=c++ MINGW=$MINGW_PREFIX
linux:
strategy:
fail-fast: true
matrix:
include: [
{ compiler: gcc, config: debug, binsuffix: Debug, bindir: linux64_gcc },
{ compiler: gcc, config: release, binsuffix: Release, bindir: linux64_gcc },
{ compiler: clang, config: debug, binsuffix: Debug, bindir: linux64_clang },
{ compiler: clang, config: release, binsuffix: Release, bindir: linux64_clang },
]
name: linux-${{ matrix.compiler }}-${{ matrix.config }}64
runs-on: ubuntu-24.04
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- name: Build
run: |
cd bnet
make -j$(nproc) linux-${{ matrix.compiler }}-${{ matrix.config }}64
osx:
strategy:
fail-fast: true
matrix:
include: [
{ runs-on: macos-14, arch: osx-arm64, bindir: osx-arm64, config: debug, binsuffix: Debug },
{ runs-on: macos-14, arch: osx-arm64, bindir: osx-arm64, config: release, binsuffix: Release },
]
name: osx-${{ matrix.arch }}-${{ matrix.config }}
runs-on: ${{ matrix.runs-on }}
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- name: Build
run: |
cd bnet
make -j$(sysctl -n hw.physicalcpu) ${{ matrix.arch }}-${{ matrix.config }}
android:
strategy:
fail-fast: true
matrix:
include: [
{ project: android-arm, bindir: android-arm },
{ project: android-arm64, bindir: android-arm64 },
]
name: ${{ matrix.project }}
runs-on: ubuntu-24.04
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r27c
- name: Build
env:
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
ANDROID_NDK_ARM: ${{ steps.setup-ndk.outputs.ndk-path }}
ANDROID_NDK_ARM64: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
cd bnet
make -j$(nproc) ${{ matrix.project }}-release
emscripten:
strategy:
fail-fast: true
matrix:
include: [
{ config: debug, binsuffix: Debug },
{ config: release, binsuffix: Release },
]
name: wasm-${{ matrix.config }}
runs-on: ubuntu-24.04
steps:
- name: Checkout bnet
uses: actions/checkout@v6
with:
path: bnet
- name: Checkout bx
uses: actions/checkout@v6
with:
repository: bkaradzic/bx
path: bx
- uses: mymindstorm/setup-emsdk@v14
with:
version: 5.0.2
- name: Build
run: |
cd bnet
make -j$(nproc) wasm-${{ matrix.config }} EMSCRIPTEN=$EMSDK/upstream/emscripten
================================================
FILE: .gitignore
================================================
.build/
.debug/
.svn/
tags
.DS_Store
================================================
FILE: LICENSE
================================================
Copyright 2010-2026 Branimir Karadzic
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
[bnet](https://github.com/bkaradzic/bnet) - Message oriented networking library
===============================================================================
Message oriented networking library using TCP transport.
[](https://github.com/bkaradzic/bnet/actions)
[](https://bkaradzic.github.io/bgfx/license.html)
[](https://discord.gg/9eMbv7J)
Contact
-------
[@bkaradzic](https://twitter.com/bkaradzic)
Project page
https://github.com/bkaradzic/bnet
[License (BSD 2-clause)](https://github.com/bkaradzic/bnet/blob/master/LICENSE)
-------------------------------------------------------------------------------
Copyright 2010-2026 Branimir Karadzic
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: examples/00-chat/chat.cpp
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#include
#include
#include
#include
#include
#include
#include
static const char* s_certs[] = {
"-----BEGIN CERTIFICATE-----\n"
"MIICDTCCAXYCCQDQyM1G6kagwzANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV\n"
"UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEVMBMGA1UE\n"
"ChMMQ2FyYm9uIEdhbWVzMB4XDTExMDgxMzIxMDY1N1oXDTExMDkxMjIxMDY1N1ow\n"
"SzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Nl\n"
"YXR0bGUxFTATBgNVBAoTDENhcmJvbiBHYW1lczCBnzANBgkqhkiG9w0BAQEFAAOB\n"
"jQAwgYkCgYEArVlCwFQMdhbitUzhuXb8r5NNjr3mgJAfXqpgG19r8IQDI/ueD4DY\n"
"ueLa+34VVWwaP27D3XIrAi7+WmGzUGf47b138F98E49HAYyoHnr/ww1mCGU4Jf8t\n"
"6JDREeyjqaFKhU1+zNjsyonooL1tPpw7u5fwjdrc6PX0qCKHHdLU+Q0CAwEAATAN\n"
"BgkqhkiG9w0BAQUFAAOBgQBm+bpTDyJttfpMeKkfmtZH828fX7qzdoj9Qs+G6Dyc\n"
"NqROo3yQiZfT8rhvyU9MdEhQcE53Eh+RA4uqAz3vkQH39mRcyrcyO3ktvCUC+0YY\n"
"WT7lu6St2t+/RgXY6ghaYCb8ko31HIJYcINZUXSBtpDYeZtRCS/nUb6LelMaOXDE\n"
"1A==\n"
"-----END CERTIFICATE-----\n"
,
NULL
};
static const char* s_key =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXAIBAAKBgQCtWULAVAx2FuK1TOG5dvyvk02OveaAkB9eqmAbX2vwhAMj+54P\n"
"gNi54tr7fhVVbBo/bsPdcisCLv5aYbNQZ/jtvXfwX3wTj0cBjKgeev/DDWYIZTgl\n"
"/y3okNER7KOpoUqFTX7M2OzKieigvW0+nDu7l/CN2tzo9fSoIocd0tT5DQIDAQAB\n"
"AoGBAImw1Oyf1iYWl40avFDsyllLz9cJ0AVedQxkmGIlsT8iHLyAKFR4K627G+WX\n"
"iKqJa2/nM3y6Kp9ZZH+2CxBbBcXBib9thsv3uZ0GPVU03RVZoOc+oia1BefhQ8qg\n"
"3nZd8yWe99a+VJnDrPA+QJ/qrL7KrstqKcYkOB35Yt+iDCUBAkEA44/sAt77sIlJ\n"
"1YB6RAL1G0Ocq3X9MQXGXFd6atRP+Lld1gfgvT1H01y6ERXCD4g6+dc+ICcIeuCi\n"
"9nJULJ9VGQJBAMMC9jO+65P2n10CmnyIiYk3nzoWAsPxxyG64XymmG1PZU1hffV3\n"
"wx1uztDs/v4F9OBxF+Xh+mC6Ovwofj7MrhUCQHGNvOjF2nSCXYyjet97VlIPkBtj\n"
"Wj/fMNedc2HhpjJoVXHbJoNoE/JdwB+MavUTNtK7XK3wrGOcutUdwfEuZOkCQFLA\n"
"VP1MTOcyxhlP24Jw5fwGUFjzsiS32kpj5P9iKlhoUpJthme9dFxvAvABQYtFt83t\n"
"77grFnYpUJJkFH5NmKkCQBk80OK/HBQNlmDkcdFpSDgAH3qHxcljL4XOopDNz5fe\n"
"cW6N7/0QrO2GhTp7JNXIdYx35iTHTY6poO0uNAwdrgE=\n"
"-----END RSA PRIVATE KEY-----\n"
;
void printMsg(const bnet::Message* _msg)
{
uint16_t len = _msg->size;
char* temp = (char*)BX_STACK_ALLOC(len);
bx::memCopy(temp, &_msg->data[1], len-1);
temp[len-1] = '\0';
printf("UserMessage %d: %s\n", _msg->data[0], temp);
}
int main(int _argc, const char* _argv[])
{
bx::CommandLine cmdLine(_argc, _argv);
uint16_t port = 1337;
const char* portOpt = cmdLine.findOption('p');
if (NULL != portOpt)
{
int32_t result;
if (bx::fromString(&result, portOpt)
&& result < UINT16_MAX)
{
port = uint16_t(result);
}
}
bool server = cmdLine.hasArg('s', "server");
if (server)
{
bnet::init(10, 1, s_certs);
uint32_t ip = bnet::toIpv4("localhost");
bnet::listen(ip, port, false, s_certs[0], s_key);
}
else
{
bnet::init(1, 0, s_certs);
const char* host = cmdLine.findOption('h', "host");
uint32_t ip = bnet::toIpv4(NULL == host ? "localhost" : host);
bnet::Handle handle = bnet::connect(ip, port, false, true);
const char* hello = "hello there!";
uint16_t len = (uint16_t)strlen(hello);
bnet::Message* msg = bnet::alloc(handle, len+1);
msg->data[0] = bnet::MessageId::UserDefined;
bx::memCopy(&msg->data[1], hello, len);
bnet::send(msg);
}
bool cont = true;
while (server || cont)
{
bnet::Message* msg = bnet::recv();
if (NULL != msg)
{
if (bnet::MessageId::UserDefined > msg->data[0])
{
switch (msg->data[0])
{
case bnet::MessageId::ListenFailed:
printf("Listen failed port is already in use?\n");
cont = server = false;
break;
case bnet::MessageId::IncomingConnection:
{
{
bnet::Handle listen = { *( (uint16_t*)&msg->data[1]) };
uint32_t rip = *( (uint32_t*)&msg->data[3]);
uint16_t rport = *( (uint16_t*)&msg->data[7]);
printf("%d.%d.%d.%d:%d connected\n"
, rip>>24
, (rip>>16)&0xff
, (rip>>8)&0xff
, rip&0xff
, rport
);
bnet::stop(listen);
}
uint32_t ip = bnet::toIpv4("localhost");
bnet::listen(ip, port, false, s_certs[0], s_key);
}
break;
case bnet::MessageId::LostConnection:
printf("disconnected\n");
cont = false;
break;
case bnet::MessageId::ConnectFailed:
printf("%d\n", msg->data[0]);
cont = false;
bnet::disconnect(msg->handle);
break;
case bnet::MessageId::RawData:
break;
default:
// fail...
break;
}
}
else
{
printMsg(msg);
bnet::Handle handle = msg->handle;
{
const char* ping = "ping!";
const char* pong = "pong!";
const char* hello = server ? ping : pong;
uint16_t len = (uint16_t)strlen(hello);
bnet::Message* omsg = bnet::alloc(handle, len+1);
omsg->data[0] = bnet::MessageId::UserDefined+1;
bx::memCopy(&omsg->data[1], hello, len);
bnet::send(omsg);
}
}
bnet::release(msg);
}
}
bnet::shutdown();
return bx::kExitSuccess;
}
================================================
FILE: examples/01-http/http.cpp
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#include
#include
#include
#include
#include
bnet::Handle httpSendRequest(uint32_t _ip, uint16_t _port, const char* _request, bool secure)
{
bnet::Handle handle = bnet::connect(_ip, _port, true, secure);
bnet::Message* out = bnet::alloc(handle, (uint16_t)bx::strLen(_request) );
bx::memCopy(out->data, _request, out->size);
bnet::send(out);
bnet::notify(handle, UINT64_C(0x123456789ABCDEF) );
return handle;
}
static const char* s_cert[] = {
// Equifax Secure CA
"-----BEGIN CERTIFICATE-----\n"
"MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV\n"
"UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy\n"
"dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1\n"
"MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx\n"
"dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B\n"
"AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f\n"
"BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A\n"
"cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC\n"
"AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ\n"
"MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm\n"
"aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw\n"
"ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj\n"
"IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF\n"
"MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA\n"
"A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y\n"
"7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh\n"
"1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4\n"
"-----END CERTIFICATE-----\n"
,
"-----BEGIN CERTIFICATE-----\n"
"MIICDTCCAXYCCQDQyM1G6kagwzANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV\n"
"UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEVMBMGA1UE\n"
"ChMMQ2FyYm9uIEdhbWVzMB4XDTExMDgxMzIxMDY1N1oXDTExMDkxMjIxMDY1N1ow\n"
"SzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Nl\n"
"YXR0bGUxFTATBgNVBAoTDENhcmJvbiBHYW1lczCBnzANBgkqhkiG9w0BAQEFAAOB\n"
"jQAwgYkCgYEArVlCwFQMdhbitUzhuXb8r5NNjr3mgJAfXqpgG19r8IQDI/ueD4DY\n"
"ueLa+34VVWwaP27D3XIrAi7+WmGzUGf47b138F98E49HAYyoHnr/ww1mCGU4Jf8t\n"
"6JDREeyjqaFKhU1+zNjsyonooL1tPpw7u5fwjdrc6PX0qCKHHdLU+Q0CAwEAATAN\n"
"BgkqhkiG9w0BAQUFAAOBgQBm+bpTDyJttfpMeKkfmtZH828fX7qzdoj9Qs+G6Dyc\n"
"NqROo3yQiZfT8rhvyU9MdEhQcE53Eh+RA4uqAz3vkQH39mRcyrcyO3ktvCUC+0YY\n"
"WT7lu6St2t+/RgXY6ghaYCb8ko31HIJYcINZUXSBtpDYeZtRCS/nUb6LelMaOXDE\n"
"1A==\n"
"-----END CERTIFICATE-----\n"
,
NULL
};
int main(int /*_argc*/, const char* /*_argv*/[])
{
bnet::init(1, 0, s_cert);
const char* url = "http://gravatar.com/avatar/cc47d6856403a62afc5c74d269b7e610.png";
// const char* url = "https://encrypted.google.com/";
bx::UrlView urlView;
urlView.parse(url);
bool secure = false;
uint32_t port = 0;
if (0 == bx::strCmpI("http", urlView.get(bx::UrlView::Scheme) ) )
{
port = 80;
}
else if (0 == bx::strCmpI("https", urlView.get(bx::UrlView::Scheme) ) )
{
port = 443;
secure = true;
}
if (0 != port)
{
char host[1024];
strCopy(host, BX_COUNTOF(host), urlView.get(bx::UrlView::Host) );
uint32_t ip = bnet::toIpv4(host);
if (!urlView.get(bx::UrlView::Port).isEmpty() )
{
bx::fromString(&port, urlView.get(bx::UrlView::Port) );
}
char path[1024];
strCopy(path, BX_COUNTOF(path), urlView.get(bx::UrlView::Path) );
char header[1024];
bx::snprintf(header
, sizeof(header)
, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n"
, path
, host
);
bnet::Handle handle = httpSendRequest(ip, uint16_t(port), header, secure);
uint32_t size = 0;
uint8_t* data = NULL;
bool cont = bnet::isValid(handle);
if (cont)
{
bx::printf("Connecting to %s (%d.%d.%d.%d:%d)\n"
, url
, ip>>24
, (ip>>16)&0xff
, (ip>>8)&0xff
, ip&0xff
, port
);
}
while (cont)
{
bnet::Message* msg = bnet::recv();
if (NULL != msg)
{
if (bnet::MessageId::UserDefined > msg->data[0])
{
switch (msg->data[0])
{
case bnet::MessageId::Notify:
bx::printf("notify!\n");
break;
case bnet::MessageId::LostConnection:
case bnet::MessageId::ConnectFailed:
cont = false;
if (NULL != data)
{
bx::FileWriter writer;
bx::Error err;
if (bx::open(&writer, "http.txt", false, &err) )
{
bx::write(&writer, data, size, &err);
bx::close(&writer);
bx::printf("Received total %d. Data saved into http.txt.\n", size);
}
else
{
bx::printf("Failed to open http.txt!");
}
}
break;
case bnet::MessageId::RawData:
{
bx::printf("# raw %d bytes.\n", msg->size);
uint32_t pos = size;
size += msg->size-1;
data = (uint8_t*)realloc(data, size+1);
bx::memCopy(&data[pos], &msg->data[1], msg->size-1);
data[size-1] = '\0';
}
break;
}
}
bnet::release(msg);
}
}
}
bnet::shutdown();
return 0;
}
================================================
FILE: examples/common/common.h
================================================
/*
* Copyright 2011-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#ifndef __COMMON_H__
#define __COMMON_H__
#include "dbg.h"
#if 0
# define BX_TRACE(_format, ...) \
do { \
DBG(BX_FILE_LINE_LITERAL "BGFX " _format "\n", ##__VA_ARGS__); \
} while(0)
# define BX_WARN(_condition, _format, ...) \
do { \
if (!(_condition) ) \
{ \
DBG("WARN " _format, ##__VA_ARGS__); \
} \
} while(0)
# define BX_CHECK(_condition, _format, ...) \
do { \
if (!(_condition) ) \
{ \
DBG("CHECK " _format, ##__VA_ARGS__); \
bx::debugBreak(); \
} \
} while(0)
#endif // 0
#include
#include
#endif // __COMMON_H__
================================================
FILE: examples/common/dbg.h
================================================
/*
* Copyright 2011-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
##ifndef DBG_H_HEADER_GUARD
#define DBG_H_HEADER_GUARD
#include
#define DBG_STRINGIZE(_x) DBG_STRINGIZE_(_x)
#define DBG_STRINGIZE_(_x) #_x
#define DBG_FILE_LINE_LITERAL "" __FILE__ "(" DBG_STRINGIZE(__LINE__) "): "
#define DBG(_format, ...) bx::debugPrintf(DBG_FILE_LINE_LITERAL "" _format "\n", ##__VA_ARGS__)
#endif // DBG_H_HEADER_GUARD
================================================
FILE: examples/runtime/.gitignore
================================================
http.txt
================================================
FILE: include/bnet/bnet.h
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#ifndef BNET_H_HEADER_GUARD
#define BNET_H_HEADER_GUARD
#include // uint32_t
#include // NULL
#define BNET_HANDLE(_name) struct _name { uint16_t idx; }
namespace bx { struct AllocatorI; }
namespace bnet
{
BNET_HANDLE(Handle);
static const Handle invalidHandle = { UINT16_MAX };
static const uint16_t maxMessageSize = UINT16_MAX;
struct MessageId
{
enum Enum
{
Notify = 1,
IncomingConnection,
LostConnection,
ListenFailed,
ConnectFailed,
RawData,
UserDefined = 8
};
};
struct DisconnectReason
{
enum Enum
{
None,
HostClosed,
RecvFailed,
SendFailed,
InvalidMessageId,
};
};
/// Returned by `bnet::alloc` or `bnet::recv` call.
struct Message
{
uint8_t* data; //< Message data.
uint16_t size; //< Message size.
Handle handle; //< Connection handle.
};
typedef Message IncomingMessage;
typedef Message OutgoingMessage;
/// Returns is handle is valid.
inline bool isValid(Handle _handle) { return invalidHandle.idx != _handle.idx; }
/// Initialize networking.
///
/// @param _maxConnections Maximum concurrent outgoing connections.
/// @param _maxListenSockets Maximum number of listen ports.
/// @param _certs SSL certificates.
/// @param _allocator Custom allocator.
///
void init(uint16_t _maxConnections, uint16_t _maxListenSockets = 0, const char* _certs[] = NULL, bx::AllocatorI* _allocator = NULL);
/// Shutdown networking.
void shutdown();
/// Start listen for incoming connections.
///
/// @returns Handle to connection object.
///
Handle listen(uint32_t _ip, uint16_t _port, bool _raw = false, const char* _cert = NULL, const char* _key = NULL);
/// Stop listening for incoming connections.
///
/// @param _handle Handle to connection object.
///
void stop(Handle _handle);
/// Connect to remote host.
///
/// @param _ip IPv4 address.
/// @param _port Port.
/// @param _raw Non-structured messages. When this is `false` bnet
/// frames messages.
/// @param _secure Create TLS/SSL connection.
///
/// @returns Handle to connection object.
///
Handle connect(uint32_t _ip, uint16_t _port, bool _raw = false, bool _secure = false);
/// Disconnect from remote host.
///
/// @param _handle Handle to connection object.
/// @param _finish Send all pending messages before closing
/// connection.
///
void disconnect(Handle _handle, bool _finish = false);
/// Notify sender when all prior messages are sent.
void notify(Handle _handle, uint64_t _userData = 0);
/// Allocate outgoing message.
///
/// @param _handle Handle to connection object.
/// @param _size Message size.
///
/// @returns Outgoing message object.
///
OutgoingMessage* alloc(Handle _handle, uint16_t _size);
/// Send message.
///
/// @param Message object allocated with `bnet::alloc` call.
///
void send(OutgoingMessage* _msg);
/// Process receive.
///
/// @returns Incoming message object. Must be released by calling `bnet::release`.
///
IncomingMessage* recv();
/// Release incoming message.
///
/// @param Message returned by `bnet::recv` call.
///
void release(IncomingMessage* _msg);
/// Convert name to IP address.
///
/// @param _addr Name or IPv4 string.
///
/// @returns IPv4 address.
///
uint32_t toIpv4(const char* _addr = "");
} // namespace bnet
#endif // BNET_H_HEADER_GUARD
================================================
FILE: makefile
================================================
#
# Copyright 2011-2026 Branimir Karadzic. All rights reserved.
# License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
#
UNAME := $(shell uname)
ifeq ($(UNAME),$(filter $(UNAME),Linux Darwin))
ifeq ($(UNAME),$(filter $(UNAME),Darwin))
OS=darwin
else
OS=linux
endif
help:
@echo Available targets:
@grep -E "^[a-zA-Z0-9_-]+:.*?## .*$$" $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
else
OS=windows
help: projgen
endif
BX_DIR?=../bx
GENIE?=$(BX_DIR)/tools/bin/$(OS)/genie $(EXTRA_GENIE_ARGS)
.PHONY: help
clean: ## Clean all intermediate files.
@echo Cleaning...
-@rm -rf .build
@mkdir .build
projgen: ## Generate project files for all configurations.
$(GENIE) vs2022
$(GENIE) --gcc=mingw-gcc gmake
$(GENIE) --gcc=mingw-clang gmake
$(GENIE) --gcc=linux-gcc gmake
$(GENIE) --gcc=linux-clang gmake
$(GENIE) --gcc=osx-arm64 gmake
$(GENIE) --gcc=osx-x64 gmake
$(GENIE) --xcode=osx xcode9
$(GENIE) --xcode=ios xcode9
$(GENIE) --gcc=android-arm gmake
$(GENIE) --gcc=android-arm64 gmake
$(GENIE) --gcc=ios-arm64 gmake
$(GENIE) --gcc=rpi gmake
.build/projects/gmake-android-arm:
$(GENIE) --gcc=android-arm gmake
android-arm-debug: .build/projects/gmake-android-arm ## Build - Android ARM Debug
$(MAKE) -R -C .build/projects/gmake-android-arm config=debug
android-arm-release: .build/projects/gmake-android-arm ## Build - Android ARM Release
$(MAKE) -R -C .build/projects/gmake-android-arm config=release
android-arm: android-arm-debug android-arm-release ## Build - Android ARM Debug and Release
.build/projects/gmake-android-arm64:
$(GENIE) --gcc=android-arm64 gmake
android-arm64-debug: .build/projects/gmake-android-arm64 ## Build - Android ARM64 Debug
$(MAKE) -R -C .build/projects/gmake-android-arm64 config=debug
android-arm64-release: .build/projects/gmake-android-arm64 ## Build - Android ARM64 Release
$(MAKE) -R -C .build/projects/gmake-android-arm64 config=release
android-arm64: android-arm64-debug android-arm64-release ## Build - Android ARM64 Debug and Release
.build/projects/gmake-wasm:
$(GENIE) --gcc=wasm gmake
wasm-debug: .build/projects/gmake-wasm ## Build - Emscripten Debug
$(MAKE) -R -C .build/projects/gmake-wasm config=debug
wasm-release: .build/projects/gmake-wasm ## Build - Emscripten Release
$(MAKE) -R -C .build/projects/gmake-wasm config=release
wasm: wasm-debug wasm-release ## Build - Emscripten Debug and Release
.build/projects/gmake-linux-gcc:
$(GENIE) --gcc=linux-gcc gmake
linux-gcc-debug64: .build/projects/gmake-linux-gcc ## Build - Linux GCC x64 Debug
$(MAKE) -R -C .build/projects/gmake-linux-gcc config=debug64
linux-gcc-release64: .build/projects/gmake-linux-gcc ## Build - Linux GCC x64 Release
$(MAKE) -R -C .build/projects/gmake-linux-gcc config=release64
linux-gcc: linux-gcc-debug64 linux-gcc-release64 ## Build - Linux GCC x86/x64 Debug and Release
.build/projects/gmake-linux-clang:
$(GENIE) --gcc=linux-clang gmake
linux-clang-debug64: .build/projects/gmake-linux-clang ## Build - Linux Clang x64 Debug
$(MAKE) -R -C .build/projects/gmake-linux-clang config=debug64
linux-clang-release64: .build/projects/gmake-linux-clang ## Build - Linux Clang x64 Release
$(MAKE) -R -C .build/projects/gmake-linux-clang config=release64
linux-clang: linux-clang-debug64 linux-clang-release64 ## Build - Linux Clang x86/x64 Debug and Release
.build/projects/gmake-mingw-gcc:
$(GENIE) --gcc=mingw-gcc gmake
mingw-gcc-debug32: .build/projects/gmake-mingw-gcc ## Build - MinGW GCC x86 Debug
$(MAKE) -R -C .build/projects/gmake-mingw-gcc config=debug32
mingw-gcc-release32: .build/projects/gmake-mingw-gcc ## Build - MinGW GCC x86 Release
$(MAKE) -R -C .build/projects/gmake-mingw-gcc config=release32
mingw-gcc-debug64: .build/projects/gmake-mingw-gcc ## Build - MinGW GCC x64 Debug
$(MAKE) -R -C .build/projects/gmake-mingw-gcc config=debug64
mingw-gcc-release64: .build/projects/gmake-mingw-gcc ## Build - MinGW GCC x64 Release
$(MAKE) -R -C .build/projects/gmake-mingw-gcc config=release64
mingw-gcc: mingw-gcc-debug32 mingw-gcc-release32 mingw-gcc-debug64 mingw-gcc-release64 ## Build - MinGW GCC x86/x64 Debug and Release
.build/projects/gmake-mingw-clang:
$(GENIE) --gcc=mingw-clang gmake
mingw-clang-debug32: .build/projects/gmake-mingw-clang ## Build - MinGW Clang x86 Debug
$(MAKE) -R -C .build/projects/gmake-mingw-clang config=debug32
mingw-clang-release32: .build/projects/gmake-mingw-clang ## Build - MinGW Clang x86 Release
$(MAKE) -R -C .build/projects/gmake-mingw-clang config=release32
mingw-clang-debug64: .build/projects/gmake-mingw-clang ## Build - MinGW Clang x64 Debug
$(MAKE) -R -C .build/projects/gmake-mingw-clang config=debug64
mingw-clang-release64: .build/projects/gmake-mingw-clang ## Build - MinGW Clang x64 Release
$(MAKE) -R -C .build/projects/gmake-mingw-clang config=release64
mingw-clang: mingw-clang-debug32 mingw-clang-release32 mingw-clang-debug64 mingw-clang-release64 ## Build - MinGW Clang x86/x64 Debug and Release
.build/projects/vs2022:
$(GENIE) vs2022
vs2022-debug32: .build/projects/vs2022 ## Build - vs2022 x86 Debug
devenv .build/projects/vs2022/bnet.sln /Build "Debug|Win32"
vs2022-release32: .build/projects/vs2022 ## Build - vs2022 x86 Release
devenv .build/projects/vs2022/bnet.sln /Build "Release|Win32"
vs2022-debug64: .build/projects/vs2022 ## Build - vs2022 x64 Debug
devenv .build/projects/vs2022/bnet.sln /Build "Debug|x64"
vs2022-release64: .build/projects/vs2022 ## Build - vs2022 x64 Release
devenv .build/projects/vs2022/bnet.sln /Build "Release|x64"
vs2022: vs2022-debug32 vs2022-release32 vs2022-debug64 vs2022-release64 ## Build - vs2022 x86/x64 Debug and Release
.build/projects/gmake-osx-arm64:
$(GENIE) --gcc=osx-arm64 gmake
osx-arm64-debug: .build/projects/gmake-osx-arm64 ## Build - macOS ARM Debug
$(MAKE) -C .build/projects/gmake-osx-arm64 config=debug
osx-arm64-release: .build/projects/gmake-osx-arm64 ## Build - macOS ARM Release
$(MAKE) -C .build/projects/gmake-osx-arm64 config=release
osx-arm64: osx-arm64-debug osx-arm64-release ## Build - macOS ARM Debug and Release
.build/projects/gmake-osx-x64:
$(GENIE) --gcc=osx-x64 gmake
osx-x64-debug: .build/projects/gmake-osx-x64 ## Build - macOS x64 Debug
$(MAKE) -C .build/projects/gmake-osx-x64 config=debug
osx-x64-release: .build/projects/gmake-osx-x64 ## Build - macOS x64 Release
$(MAKE) -C .build/projects/gmake-osx-x64 config=release
osx-x64: osx-x64-debug osx-x64-release ## Build - macOS x64 Debug and Release
.build/projects/gmake-ios-arm64:
$(GENIE) --gcc=ios-arm64 gmake
ios-arm64-debug: .build/projects/gmake-ios-arm64 ## Build - iOS ARM64 Debug
$(MAKE) -R -C .build/projects/gmake-ios-arm64 config=debug
ios-arm64-release: .build/projects/gmake-ios-arm64 ## Build - iOS ARM64 Release
$(MAKE) -R -C .build/projects/gmake-ios-arm64 config=release
ios-arm64: ios-arm64-debug ios-arm64-release ## Build - iOS ARM64 Debug and Release
.build/projects/gmake-rpi:
$(GENIE) --gcc=rpi gmake
rpi-debug: .build/projects/gmake-rpi ## Build - RasberryPi Debug
$(MAKE) -R -C .build/projects/gmake-rpi config=debug
rpi-release: .build/projects/gmake-rpi ## Build - RasberryPi Release
$(MAKE) -R -C .build/projects/gmake-rpi config=release
rpi: rpi-debug rpi-release ## Build - RasberryPi Debug and Release
================================================
FILE: scripts/bnet.lua
================================================
project "bnet"
uuid "e72d44a0-ab28-11e0-9f1c-0800200c9a66"
kind "StaticLib"
includedirs {
path.join(BNET_DIR, "include"),
}
files {
path.join(BNET_DIR, "include/**.h"),
path.join(BNET_DIR, "src/**.cpp"),
path.join(BNET_DIR, "src/**.h"),
}
using_bx()
configuration { "x32", "vs*" }
includedirs { path.join(BNET_DIR, "3rdparty/openssl/lib/win32_", _ACTION, "include") }
configuration { "x64", "vs*" }
includedirs { path.join(BNET_DIR, "3rdparty/openssl/lib/win64_", _ACTION, "include") }
configuration { "android-arm7" }
includedirs { path.join(BNET_DIR, "3rdparty/openssl/lib/android_arm7/include") }
configuration { "default-linux", "x32" }
includedirs { path.join(BNET_DIR, "3rdparty/openssl/lib/linux-generic32/include") }
configuration { "default-linux", "x64" }
includedirs { path.join(BNET_DIR, "3rdparty/openssl/lib/linux-generic64/include") }
configuration {}
copyLib()
================================================
FILE: scripts/genie.lua
================================================
--
-- Copyright 2010-2026 Branimir Karadzic. All rights reserved.
-- License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
--
newoption {
trigger = "with-openssl",
description = "Enable OpenSSL integration.",
}
solution "bnet"
configurations {
"Debug",
"Release",
}
platforms {
"x32",
"x64",
"Xbox360",
"Native", -- for targets where bitness is not specified
}
language "C++"
BNET_DIR = (path.getabsolute("..") .. "/")
BX_DIR = os.getenv("BX_DIR")
local BNET_BUILD_DIR = path.join(BNET_DIR, ".build")
local BNET_THIRD_PARTY_DIR = path.join(BNET_DIR, "3rdparty")
if not BX_DIR then
BX_DIR = path.getabsolute(path.join(BNET_DIR, "../bx") )
end
defines {
"BX_CONFIG_ENABLE_MSVC_LEVEL4_WARNINGS=1"
}
dofile (path.join(BX_DIR, "scripts/toolchain.lua") )
toolchain(BNET_BUILD_DIR, BNET_THIRD_PARTY_DIR)
function copyLib()
end
group "libs"
dofile(path.join(BX_DIR, "scripts/bx.lua"))
dofile "bnet.lua"
function exampleProject(_name)
project ("example-" .. _name)
uuid (os.uuid("example-" .. _name))
kind "ConsoleApp"
configuration {}
debugdir (path.join(BNET_DIR, "examples/runtime") )
includedirs {
path.join(BNET_DIR, "include"),
}
files {
path.join(BNET_DIR, "examples", _name, "**.cpp"),
path.join(BNET_DIR, "examples", _name, "**.h"),
}
links {
"bnet",
}
using_bx()
configuration { "vs* or mingw*" }
links {
"psapi",
"ws2_32",
}
if _OPTIONS["with-openssl"] then
configuration { "x32", "vs*" }
libdirs { path.join(BNET_DIR, "3rdparty/openssl/lib/win32_", _ACTION, "lib") }
configuration { "x64", "vs*" }
libdirs { path.join(BNET_DIR, "3rdparty/openssl/lib/win64_", _ACTION, "lib") }
configuration { "vs* or mingw*" }
links {
"libeay32",
"ssleay32",
}
end
configuration { "android*" }
kind "ConsoleApp"
targetextension ".so"
linkoptions {
"-shared",
}
configuration { "osx*" }
linkoptions {
"-framework Cocoa",
}
configuration { "ios*" }
kind "ConsoleApp"
linkoptions {
"-framework CoreFoundation",
"-framework Foundation",
"-framework UIKit",
"-framework QuartzCore",
}
configuration {}
strip()
end
group "examples"
exampleProject("00-chat")
exampleProject("01-http")
================================================
FILE: src/bnet.cpp
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#include "bnet_p.h"
#include
#include
namespace bnet
{
static bx::DefaultAllocator s_allocatorStub;
bx::AllocatorI* g_allocator = &s_allocatorStub;
#if BNET_CONFIG_OPENSSL && BX_CONFIG_DEBUG
static void getSslErrorInfo()
{
BIO* bio = BIO_new(BIO_s_mem());
ERR_print_errors(bio);
BUF_MEM *bptr;
BIO_get_mem_ptr(bio, &bptr);
BX_TRACE("OpenSSL Error: %.*s", bptr->length, bptr->data);
BIO_free(bio);
}
# define TRACE_SSL_ERROR() getSslErrorInfo()
#else
# define TRACE_SSL_ERROR()
#endif // BNET_CONFIG_OPENSSL && BX_CONFIG_DEBUG
int getLastError()
{
#if BX_PLATFORM_WINDOWS
return WSAGetLastError();
#elif BX_PLATFORM_LINUX \
|| BX_PLATFORM_ANDROID \
|| BX_PLATFORM_EMSCRIPTEN \
|| BX_PLATFORM_OSX \
|| BX_PLATFORM_IOS
return errno;
#else
return 0;
#endif // BX_PLATFORM_
}
#if BNET_CONFIG_OPENSSL
#else
static int sslDummyContext;
#endif
bool isInProgress()
{
#if BX_PLATFORM_WINDOWS
return WSAEINPROGRESS == getLastError();
#else
return EINPROGRESS == getLastError();
#endif // BX_PLATFORM_WINDOWS
}
bool isWouldBlock()
{
#if BX_PLATFORM_WINDOWS
return WSAEWOULDBLOCK == getLastError();
#else
return EWOULDBLOCK == getLastError();
#endif // BX_PLATFORM_WINDOWS
}
void setNonBlock(SOCKET _socket)
{
#if BX_PLATFORM_WINDOWS
unsigned long opt = 1 ;
::ioctlsocket(_socket, FIONBIO, &opt);
#elif BX_PLATFORM_LINUX \
|| BX_PLATFORM_ANDROID \
|| BX_PLATFORM_EMSCRIPTEN \
|| BX_PLATFORM_OSX \
|| BX_PLATFORM_IOS
::fcntl(_socket, F_SETFL, O_NONBLOCK);
#else
BX_UNUSED(_socket);
#endif // BX_PLATFORM_
}
static void setSockOpts(SOCKET _socket)
{
int result;
int win = 256<<10;
result = ::setsockopt(_socket, SOL_SOCKET, SO_RCVBUF, (char*)&win, sizeof(win) );
result = ::setsockopt(_socket, SOL_SOCKET, SO_SNDBUF, (char*)&win, sizeof(win) );
int noDelay = 1;
result = ::setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&noDelay, sizeof(noDelay) );
BX_UNUSED(result);
}
class Connection
{
public:
Connection()
: m_socket(INVALID_SOCKET)
, m_handle(invalidHandle)
, m_incomingBuffer( (uint8_t*)bx::alloc(g_allocator, BNET_CONFIG_MAX_INCOMING_BUFFER_SIZE) )
, m_incoming(BNET_CONFIG_MAX_INCOMING_BUFFER_SIZE)
, m_recv(m_incoming, (char*)m_incomingBuffer)
#if BNET_CONFIG_OPENSSL
, m_ssl(NULL)
#endif // BNET_CONFIG_OPENSSL
, m_len(-1)
, m_raw(false)
, m_tcpHandshake(true)
, m_sslHandshake(false)
{
}
~Connection()
{
bx::free(g_allocator, m_incomingBuffer);
}
void connect(Handle _handle, uint32_t _ip, uint16_t _port, bool _raw, SSL_CTX* _sslCtx)
{
init(_handle, _raw);
m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == m_socket)
{
ctxPush(m_handle, MessageId::ConnectFailed);
return;
}
setSockOpts(m_socket);
const bool ssl = _sslCtx != NULL;
int err = connectsocket(m_socket, _ip, _port, ssl);
if (0 != err
&& !(isInProgress() || isWouldBlock() ) )
{
BX_TRACE("Connect %d - Connect failed. %d", m_handle, getLastError() );
::closesocket(m_socket);
m_socket = INVALID_SOCKET;
ctxPush(m_handle, MessageId::ConnectFailed);
return;
}
setNonBlock(m_socket);
#if BNET_CONFIG_OPENSSL
if (ssl)
{
m_sslHandshake = true;
m_ssl = SSL_new(_sslCtx);
SSL_set_fd(m_ssl, (int)m_socket);
SSL_set_connect_state(m_ssl);
SSL_write(m_ssl, NULL, 0);
}
#else
BX_UNUSED(_sslCtx);
#endif // BNET_CONFIG_OPENSSL
}
void accept(Handle _handle, Handle _listenHandle, SOCKET _socket, uint32_t _ip, uint16_t _port, bool _raw, SSL_CTX* _sslCtx, X509* _cert, EVP_PKEY* _key)
{
init(_handle, _raw);
m_socket = _socket;
Message* msg = msgAlloc(m_handle, 9, true);
msg->data[0] = MessageId::IncomingConnection;
*( (uint16_t*)&msg->data[1]) = _listenHandle.idx;
*( (uint32_t*)&msg->data[3]) = _ip;
*( (uint16_t*)&msg->data[7]) = _port;
ctxPush(msg);
#if BNET_CONFIG_OPENSSL
if (NULL != _sslCtx)
{
m_sslHandshake = true;
m_ssl = SSL_new(_sslCtx);
int result;
result = SSL_use_certificate(m_ssl, _cert);
result = SSL_use_PrivateKey(m_ssl, _key);
result = SSL_set_fd(m_ssl, (int)m_socket);
BX_UNUSED(result);
SSL_set_accept_state(m_ssl);
SSL_read(m_ssl, NULL, 0);
}
#else
BX_UNUSED(_sslCtx);
BX_UNUSED(_cert);
BX_UNUSED(_key);
#endif // BNET_CONFIG_OPENSSL
}
void disconnect(DisconnectReason::Enum _reason = DisconnectReason::None)
{
#if BNET_CONFIG_OPENSSL
if (m_ssl)
{
SSL_shutdown(m_ssl);
SSL_free(m_ssl);
m_ssl = NULL;
}
#endif // BNET_CONFIG_OPENSSL
if (INVALID_SOCKET != m_socket)
{
::closesocket(m_socket);
m_socket = INVALID_SOCKET;
}
for (Message* msg = m_outgoing.pop(); NULL != msg; msg = m_outgoing.pop() )
{
release(msg);
}
if (_reason != DisconnectReason::None)
{
Message* msg = msgAlloc(m_handle, 2, true);
msg->data[0] = MessageId::LostConnection;
msg->data[1] = uint8_t(_reason);
ctxPush(msg);
}
}
void send(Message* _msg)
{
BX_ASSERT(m_raw || _msg->data[0] >= MessageId::UserDefined, "Sending message with MessageId below UserDefined is not allowed!");
if (INVALID_SOCKET != m_socket)
{
m_outgoing.push(_msg);
update();
}
}
void update()
{
if (INVALID_SOCKET != m_socket)
{
updateSocket();
if (!m_tcpHandshake
&& !m_sslHandshake)
{
updateIncomingMessages();
}
}
}
bool hasSocket() const
{
return INVALID_SOCKET != m_socket;
}
private:
void init(Handle _handle, bool _raw)
{
m_handle = _handle;
m_tcpHandshake = true;
m_sslHandshake = false;
m_tcpHandshakeTimeout = bx::getHPCounter() + bx::getHPFrequency()*BNET_CONFIG_CONNECT_TIMEOUT_SECONDS;
m_len = -1;
m_raw = _raw;
}
void read(bx::WriteRingBuffer& _out, uint32_t _len)
{
bx::ReadRingBuffer incoming(m_incoming, (char*)m_incomingBuffer, _len);
_out.write(incoming, _len);
incoming.end();
}
void read(uint32_t _len)
{
m_incoming.consume(_len);
}
void read(char* _data, uint32_t _len)
{
bx::ReadRingBuffer incoming(m_incoming, (char*)m_incomingBuffer, _len);
incoming.read(_data, _len);
incoming.end();
}
void peek(char* _data, uint32_t _len)
{
bx::ReadRingBuffer incoming(m_incoming, (char*)m_incomingBuffer, _len);
incoming.read(_data, _len);
}
void updateIncomingMessages()
{
if (m_raw)
{
uint16_t available = uint16_t(bx::min(m_incoming.getNumUsed(), maxMessageSize-1) );
if (0 < available)
{
Message* msg = msgAlloc(m_handle, available+1, true);
msg->data[0] = MessageId::RawData;
read( (char*)&msg->data[1], available);
ctxPush(msg);
}
}
else
{
uint32_t available = bx::min(m_incoming.getNumUsed(), maxMessageSize);
while (0 < available)
{
if (-1 == m_len)
{
if (2 > available)
{
return;
}
else
{
uint16_t len;
read((char*)&len, 2);
m_len = bx::toHostEndian(len, true);
}
}
else
{
if (m_len > int(available) )
{
return;
}
else
{
Message* msg = msgAlloc(m_handle, uint16_t(m_len), true);
read( (char*)msg->data, m_len);
uint8_t id = msg->data[0];
if (id < MessageId::UserDefined)
{
msgRelease(msg);
BX_TRACE("Disconnect %d - Invalid message id.", m_handle);
disconnect(DisconnectReason::InvalidMessageId);
return;
}
ctxPush(msg);
m_len = -1;
}
}
available = bx::min(m_incoming.getNumUsed(), maxMessageSize);
}
}
}
void updateSocket()
{
if (updateTcpHandshake()
&& updateSslHandshake() )
{
int bytes;
#if BNET_CONFIG_OPENSSL
if (NULL != m_ssl)
{
bytes = m_recv.recv(m_ssl);
}
else
#endif // BNET_CONFIG_OPENSSL
{
bytes = m_recv.recv(m_socket);
}
if (1 > bytes)
{
if (0 == bytes)
{
BX_TRACE("Disconnect %d - Host closed connection.", m_handle);
disconnect(DisconnectReason::HostClosed);
return;
}
else if (!isWouldBlock() )
{
TRACE_SSL_ERROR();
BX_TRACE("Disconnect %d - Receive failed. %d", m_handle, getLastError() );
disconnect(DisconnectReason::RecvFailed);
return;
}
}
if (!m_sslHandshake)
{
if (m_raw)
{
for (Message* msg = m_outgoing.peek(); NULL != msg; msg = m_outgoing.peek() )
{
Internal::Enum id = Internal::Enum(*(msg->data - 2) );
if (Internal::None != id)
{
if (!processInternal(id, msg) )
{
return;
}
}
else if (!send( (char*)msg->data, msg->size) )
{
return;
}
release(m_outgoing.pop() );
}
}
else
{
for (Message* msg = m_outgoing.peek(); NULL != msg; msg = m_outgoing.peek() )
{
Internal::Enum id = Internal::Enum(*(msg->data - 2) );
if (Internal::None != id)
{
*( (uint16_t*)msg->data - 1) = msg->size;
if (!processInternal(id, msg) )
{
return;
}
}
else
{
*( (uint16_t*)msg->data - 1) = bx::toLittleEndian(msg->size);
if (!send( (char*)msg->data - 2, msg->size+2) )
{
return;
}
}
release(m_outgoing.pop() );
}
}
}
}
}
bool processInternal(Internal::Enum _id, Message* _msg)
{
switch (_id)
{
case Internal::Disconnect:
{
Message* msg = msgAlloc(_msg->handle, 2, true);
msg->data[0] = 0;
msg->data[1] = Internal::Disconnect;
ctxPush(msg);
BX_TRACE("Disconnect %d - Client closed connection (finish).", m_handle);
disconnect();
}
return false;
case Internal::Notify:
{
Message* msg = msgAlloc(_msg->handle, _msg->size+1, true);
msg->data[0] = MessageId::Notify;
bx::memCopy(&msg->data[1], _msg->data, _msg->size);
ctxPush(msg);
}
return true;
default:
break;
}
BX_ASSERT(false, "You should not be here!");
return true;
}
bool updateTcpHandshake()
{
if (!m_tcpHandshake)
{
return true;
}
uint64_t now = bx::getHPCounter();
if (now > m_tcpHandshakeTimeout)
{
BX_TRACE("Disconnect %d - Connect timeout.", m_handle);
ctxPush(m_handle, MessageId::ConnectFailed);
disconnect();
return false;
}
m_tcpHandshake = !issocketready(m_socket);
return !m_tcpHandshake;
}
bool updateSslHandshake()
{
#if BNET_CONFIG_OPENSSL
if (NULL != m_ssl
&& m_sslHandshake)
{
int err = SSL_do_handshake(m_ssl);
if (1 == err)
{
m_sslHandshake = false;
# if BX_CONFIG_DEBUG
X509* cert = SSL_get_peer_certificate(m_ssl);
BX_TRACE("Server certificate:");
char* temp;
temp = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
BX_TRACE("\t subject: %s", temp);
OPENSSL_free(temp);
temp = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
BX_TRACE("\t issuer: %s", temp);
OPENSSL_free(temp);
X509_free(cert);
# endif // BX_CONFIG_DEBUG
long result = SSL_get_verify_result(m_ssl);
if (X509_V_OK != result)
{
BX_TRACE("Disconnect %d - SSL verify failed %d.", m_handle, result);
ctxPush(m_handle, MessageId::ConnectFailed);
disconnect();
return false;
}
BX_TRACE("SSL connection using %s", SSL_get_cipher(m_ssl) );
}
else
{
int sslError = SSL_get_error(m_ssl, err);
switch (sslError)
{
case SSL_ERROR_WANT_READ:
SSL_read(m_ssl, NULL, 0);
break;
case SSL_ERROR_WANT_WRITE:
SSL_write(m_ssl, NULL, 0);
break;
default:
TRACE_SSL_ERROR();
break;
}
}
}
#endif // BNET_CONFIG_OPENSSL
return true;
}
bool send(const char* _data, uint32_t _len)
{
int bytes;
uint32_t offset = 0;
do
{
#if BNET_CONFIG_OPENSSL
if (NULL != m_ssl)
{
bytes = SSL_write(m_ssl
, &_data[offset]
, _len
);
}
else
#endif // BNET_CONFIG_OPENSSL
{
bytes = ::send(m_socket
, &_data[offset]
, _len
, 0
);
}
if (0 > bytes)
{
if (-1 == bytes
&& !isWouldBlock() )
{
BX_TRACE("Disconnect %d - Send failed.", m_handle);
disconnect(DisconnectReason::SendFailed);
return false;
}
}
else
{
_len -= bytes;
offset += bytes;
}
} while (0 < _len);
return true;
}
uint64_t m_tcpHandshakeTimeout;
SOCKET m_socket;
Handle m_handle;
uint8_t* m_incomingBuffer;
bx::RingBufferControl m_incoming;
RecvRingBuffer m_recv;
MessageQueue m_outgoing;
#if BNET_CONFIG_OPENSSL
SSL* m_ssl;
#endif // BNET_CONFIG_OPENSSL
int m_len;
bool m_raw;
bool m_tcpHandshake;
bool m_sslHandshake;
};
typedef FreeList Connections;
class ListenSocket
{
public:
ListenSocket()
: m_socket(INVALID_SOCKET)
, m_handle(invalidHandle)
, m_raw(false)
, m_secure(false)
, m_cert(NULL)
, m_key(NULL)
{
}
~ListenSocket()
{
close();
}
void close()
{
if (INVALID_SOCKET != m_socket)
{
::closesocket(m_socket);
m_socket = INVALID_SOCKET;
}
#if BNET_CONFIG_OPENSSL
if (NULL != m_cert)
{
X509_free(m_cert);
m_cert = NULL;
}
if (NULL != m_key)
{
EVP_PKEY_free(m_key);
m_key = NULL;
}
#endif // BNET_CONFIG_OPENSSL
}
void listen(Handle _handle, uint32_t _ip, uint16_t _port, bool _raw, const char* _cert, const char* _key)
{
m_handle = _handle;
m_raw = _raw;
#if BNET_CONFIG_OPENSSL
if (NULL != _cert)
{
BIO* mem = BIO_new_mem_buf(const_cast(_cert), -1);
m_cert = PEM_read_bio_X509(mem, NULL, NULL, NULL);
BIO_free(mem);
}
if (NULL != _key)
{
BIO* mem = BIO_new_mem_buf(const_cast(_key), -1);
m_key = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL);
BIO_free(mem);
}
m_secure = NULL != m_key && NULL != m_cert;
#endif // BNET_CONFIG_OPENSSL
if (!m_secure
&& (NULL != _cert || NULL != _key) )
{
#if BNET_CONFIG_OPENSSL
BX_TRACE("Certificate of key is not set correctly.");
#else
BX_TRACE("BNET_CONFIG_OPENSSL is not enabled.");
#endif // BNET_CONFIG_OPENSSL
ctxPush(m_handle, MessageId::ListenFailed);
return;
}
m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == m_socket)
{
BX_TRACE("Create socket failed.");
ctxPush(m_handle, MessageId::ListenFailed);
return;
}
setSockOpts(m_socket);
m_addr.sin_family = AF_INET;
m_addr.sin_addr.s_addr = htonl(_ip);
m_addr.sin_port = htons(_port);
if (SOCKET_ERROR == ::bind(m_socket, (sockaddr*)&m_addr, sizeof(m_addr) )
|| SOCKET_ERROR == ::listen(m_socket, SOMAXCONN) )
{
::closesocket(m_socket);
m_socket = INVALID_SOCKET;
BX_TRACE("Bind or listen socket failed.");
ctxPush(m_handle, MessageId::ListenFailed);
return;
}
setNonBlock(m_socket);
}
void update()
{
sockaddr_in addr;
socklen_t len = sizeof(addr);
SOCKET socket = ::accept(m_socket, (sockaddr*)&addr, &len);
if (INVALID_SOCKET != socket)
{
uint32_t ip = ntohl(addr.sin_addr.s_addr);
uint16_t port = ntohs(addr.sin_port);
ctxAccept(m_handle, socket, ip, port, m_raw, m_cert, m_key);
}
}
private:
sockaddr_in m_addr;
SOCKET m_socket;
Handle m_handle;
bool m_raw;
bool m_secure;
X509* m_cert;
EVP_PKEY* m_key;
};
typedef FreeList ListenSockets;
class Context
{
public:
Context()
: m_connections(NULL)
, m_listenSockets(NULL)
, m_sslCtx(NULL)
, m_sslCtxServer(NULL)
{
}
~Context()
{
}
void init(uint16_t _maxConnections, uint16_t _maxListenSockets, const char* _certs[])
{
#if BNET_CONFIG_OPENSSL
CRYPTO_get_mem_functions(&m_sslMalloc, &m_sslRealloc, &m_sslFree);
CRYPTO_set_mem_functions(sslMalloc, sslRealloc, sslFree);
SSL_library_init();
# if BX_CONFIG_DEBUG
SSL_load_error_strings();
# endif // BX_CONFIG_DEBUG
m_sslCtx = SSL_CTX_new(SSLv23_client_method() );
SSL_CTX_set_verify(m_sslCtx, SSL_VERIFY_NONE, NULL);
if (NULL != _certs)
{
X509_STORE* store = SSL_CTX_get_cert_store(m_sslCtx);
for (const char** cert = _certs; NULL != *cert; ++cert )
{
BIO* mem = BIO_new_mem_buf(const_cast(*cert), -1);
X509* x509 = PEM_read_bio_X509(mem, NULL, NULL, NULL);
X509_STORE_add_cert(store, x509);
X509_free(x509);
BIO_free(mem);
}
}
if (_maxListenSockets)
{
m_sslCtxServer = SSL_CTX_new(SSLv23_server_method());
}
#else
m_sslCtx = &sslDummyContext;
m_sslCtxServer = &sslDummyContext;
BX_UNUSED(_certs);
#endif // BNET_CONFIG_OPENSSL
_maxConnections = _maxConnections == 0 ? 1 : _maxConnections;
m_connections = BX_NEW(g_allocator, Connections)(_maxConnections);
if (0 != _maxListenSockets)
{
m_listenSockets = BX_NEW(g_allocator, ListenSockets)(_maxListenSockets);
}
}
void shutdown()
{
for (Message* msg = m_incoming.pop(); NULL != msg; msg = m_incoming.pop() )
{
release(msg);
}
bx::deleteObject(g_allocator, m_connections);
if (NULL != m_listenSockets)
{
bx::deleteObject(g_allocator, m_listenSockets);
}
#if BNET_CONFIG_OPENSSL
if (NULL != m_sslCtx)
{
SSL_CTX_free(m_sslCtx);
}
m_sslCtx = NULL;
if (NULL != m_sslCtxServer)
{
SSL_CTX_free(m_sslCtxServer);
}
m_sslCtxServer = NULL;
CRYPTO_set_mem_functions(m_sslMalloc, m_sslRealloc, m_sslFree);
#endif // BNET_CONFIG_OPENSSL
}
Handle listen(uint32_t _ip, uint16_t _port, bool _raw, const char* _cert, const char* _key)
{
ListenSocket* listenSocket = m_listenSockets->create();
if (NULL != listenSocket)
{
Handle handle = { m_listenSockets->getHandle(listenSocket) };
listenSocket->listen(handle, _ip, _port, _raw, _cert, _key);
return handle;
}
return invalidHandle;
}
void stop(Handle _handle)
{
ListenSocket* listenSocket = { m_listenSockets->getFromHandle(_handle.idx) };
listenSocket->close();
m_listenSockets->destroy(listenSocket);
}
Handle accept(Handle _listenHandle, SOCKET _socket, uint32_t _ip, uint16_t _port, bool _raw, X509* _cert, EVP_PKEY* _key)
{
Connection* connection = m_connections->create();
if (NULL != connection)
{
Handle handle = { m_connections->getHandle(connection) };
bool secure = NULL != _cert && NULL != _key;
connection->accept(handle, _listenHandle, _socket, _ip, _port, _raw, secure?m_sslCtxServer:NULL, _cert, _key);
return handle;
}
return invalidHandle;
}
Handle connect(uint32_t _ip, uint16_t _port, bool _raw, bool _secure)
{
Connection* connection = m_connections->create();
if (NULL != connection)
{
Handle handle = { m_connections->getHandle(connection) };
connection->connect(handle, _ip, _port, _raw, _secure?m_sslCtx:NULL);
return handle;
}
return invalidHandle;
}
void disconnect(Handle _handle, bool _finish)
{
BX_ASSERT(_handle.idx < m_connections->getMaxHandles(), "Invalid handle %d!", _handle.idx);
Connection* connection = { m_connections->getFromHandle(_handle.idx) };
if (_finish
&& connection->hasSocket() )
{
Message* msg = msgAlloc(_handle, 0, false, Internal::Disconnect);
connection->send(msg);
}
else
{
BX_TRACE("Disconnect %d - Client closed connection.", _handle);
connection->disconnect();
Message* msg = msgAlloc(_handle, 2, true);
msg->data[0] = 0;
msg->data[1] = Internal::Disconnect;
ctxPush(msg);
}
}
void notify(Handle _handle, uint64_t _userData)
{
BX_ASSERT(_handle.idx == invalidHandle.idx // loopback
|| _handle.idx < m_connections->getMaxHandles(), "Invalid handle %d!", _handle.idx);
if (invalidHandle.idx != _handle.idx)
{
Message* msg = msgAlloc(_handle, sizeof(_userData), false, Internal::Notify);
bx::memCopy(msg->data, &_userData, sizeof(_userData) );
Connection* connection = m_connections->getFromHandle(_handle.idx);
connection->send(msg);
}
else
{
// loopback
Message* msg = msgAlloc(_handle, sizeof(_userData)+1, true);
msg->data[0] = MessageId::Notify;
bx::memCopy(&msg->data[1], &_userData, sizeof(_userData) );
ctxPush(msg);
}
}
void send(Message* _msg)
{
BX_ASSERT(_msg->handle.idx == invalidHandle.idx // loopback
|| _msg->handle.idx < m_connections->getMaxHandles(), "Invalid handle %d!", _msg->handle.idx);
if (invalidHandle.idx != _msg->handle.idx)
{
Connection* connection = m_connections->getFromHandle(_msg->handle.idx);
connection->send(_msg);
}
else
{
// loopback
push(_msg);
}
}
Message* recv()
{
if (NULL != m_listenSockets)
{
for (uint16_t ii = 0, num = m_listenSockets->getNumHandles(); ii < num; ++ii)
{
ListenSocket* listenSocket = m_listenSockets->getFromHandleAt(ii);
listenSocket->update();
}
}
for (uint16_t ii = 0, num = m_connections->getNumHandles(); ii < num; ++ii)
{
Connection* connection = m_connections->getFromHandleAt(ii);
connection->update();
}
Message* msg = m_incoming.pop();
while (NULL != msg)
{
if (invalidHandle.idx == msg->handle.idx) // loopback
{
return msg;
}
Connection* connection = m_connections->getFromHandle(msg->handle.idx);
uint8_t id = msg->data[0];
if (0 == id
&& Internal::Disconnect == msg->data[1])
{
m_connections->destroy(connection);
}
else if (connection->hasSocket() || MessageId::UserDefined > id)
{
return msg;
}
release(msg);
msg = m_incoming.pop();
}
return msg;
}
void push(Message* _msg)
{
m_incoming.push(_msg);
}
private:
Connections* m_connections;
ListenSockets* m_listenSockets;
MessageQueue m_incoming;
#if BNET_CONFIG_OPENSSL
static void* sslMalloc(size_t _size)
{
return BX_ALLOC(g_allocator, _size);
}
static void* sslRealloc(void* _ptr, size_t _size)
{
return BX_REALLOC(g_allocator, _ptr, _size);
}
static void sslFree(void* _ptr)
{
return BX_FREE(g_allocator, _ptr);
}
typedef void* (*MallocFn)(size_t _size);
MallocFn m_sslMalloc;
typedef void* (*ReallocFn)(void* _ptr, size_t _size);
ReallocFn m_sslRealloc;
typedef void (*FreeFn)(void* _ptr);
FreeFn m_sslFree;
#endif // BNET_CONFIG_OPENSSL
SSL_CTX* m_sslCtx;
SSL_CTX* m_sslCtxServer;
};
static Context s_ctx;
Handle ctxAccept(Handle _listenHandle, SOCKET _socket, uint32_t _ip, uint16_t _port, bool _raw, X509* _cert, EVP_PKEY* _key)
{
return s_ctx.accept(_listenHandle, _socket, _ip, _port, _raw, _cert, _key);
}
void ctxPush(Handle _handle, MessageId::Enum _id)
{
Message* msg = msgAlloc(_handle, 1, true);
msg->data[0] = uint8_t(_id);
s_ctx.push(msg);
}
void ctxPush(Message* _msg)
{
s_ctx.push(_msg);
}
Message* msgAlloc(Handle _handle, uint16_t _size, bool _incoming, Internal::Enum _type)
{
uint16_t offset = _incoming ? 0 : 2;
Message* msg = (Message*)bx::alloc(g_allocator, sizeof(Message) + offset + _size);
msg->size = _size;
msg->handle = _handle;
uint8_t* data = (uint8_t*)msg + sizeof(Message);
data[0] = uint8_t(_type);
msg->data = data + offset;
return msg;
}
void msgRelease(Message* _msg)
{
bx::free(g_allocator, _msg);
}
void init(uint16_t _maxConnections, uint16_t _maxListenSockets, const char* _certs[], bx::AllocatorI* _allocator)
{
if (NULL != _allocator)
{
g_allocator = _allocator;
}
#if BX_PLATFORM_WINDOWS
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif // BX_PLATFORM_WINDOWS
s_ctx.init(_maxConnections, _maxListenSockets, _certs);
}
void shutdown()
{
s_ctx.shutdown();
#if BX_PLATFORM_WINDOWS
WSACleanup();
#endif // BX_PLATFORM_WINDOWS
}
Handle listen(uint32_t _ip, uint16_t _port, bool _raw, const char* _cert, const char* _key)
{
return s_ctx.listen(_ip, _port, _raw, _cert, _key);
}
void stop(Handle _handle)
{
return s_ctx.stop(_handle);
}
Handle connect(uint32_t _ip, uint16_t _port, bool _raw, bool _secure)
{
return s_ctx.connect(_ip, _port, _raw, _secure);
}
void disconnect(Handle _handle, bool _finish)
{
s_ctx.disconnect(_handle, _finish);
}
void notify(Handle _handle, uint64_t _userData)
{
s_ctx.notify(_handle, _userData);
}
OutgoingMessage* alloc(Handle _handle, uint16_t _size)
{
return msgAlloc(_handle, _size);
}
void release(IncomingMessage* _msg)
{
msgRelease(_msg);
}
void send(OutgoingMessage* _msg)
{
s_ctx.send(_msg);
}
IncomingMessage* recv()
{
return s_ctx.recv();
}
uint32_t toIpv4(const char* _addr)
{
uint32_t a0, a1, a2, a3;
char dummy;
if (4 == sscanf(_addr, "%d.%d.%d.%d%c", &a0, &a1, &a2, &a3, &dummy)
&& a0 <= 0xff
&& a1 <= 0xff
&& a2 <= 0xff
&& a3 <= 0xff)
{
return (a0<<24) | (a1<<16) | (a2<<8) | a3;
}
uint32_t ip = 0;
struct addrinfo* result = NULL;
struct addrinfo hints;
bx::memSet(&hints, 0, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
int res = getaddrinfo(_addr, NULL, &hints, &result);
if (0 == res)
{
while (result)
{
sockaddr_in* addr = (sockaddr_in*)result->ai_addr;
if (AF_INET == result->ai_family
&& INADDR_LOOPBACK != addr->sin_addr.s_addr)
{
ip = ntohl(addr->sin_addr.s_addr);
break;
}
result = result->ai_next;
}
}
if (NULL != result)
{
freeaddrinfo(result);
}
return ip;
}
} // namespace bnet
================================================
FILE: src/bnet_p.h
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#ifndef BNET_P_H_HEADER_GUARD
#define BNET_P_H_HEADER_GUARD
#include
#include
#ifndef BNET_CONFIG_OPENSSL
# define BNET_CONFIG_OPENSSL 0 //(BX_PLATFORM_WINDOWS && BX_COMPILER_MSVC) || BX_PLATFORM_ANDROID || BX_PLATFORM_LINUX
#endif // BNET_CONFIG_OPENSSL
#ifndef BNET_CONFIG_CONNECT_TIMEOUT_SECONDS
# define BNET_CONFIG_CONNECT_TIMEOUT_SECONDS 5
#endif // BNET_CONFIG_CONNECT_TIMEOUT_SECONDS
#ifndef BNET_CONFIG_MAX_INCOMING_BUFFER_SIZE
# define BNET_CONFIG_MAX_INCOMING_BUFFER_SIZE (64<<10)
#endif // BNET_CONFIG_MAX_INCOMING_BUFFER_SIZE
#if BX_PLATFORM_WINDOWS
# if BX_PLATFORM_WINDOWS
# if !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0501
# endif
# include
# endif
# define socklen_t int32_t
# include "inet_socket.h"
#elif BX_PLATFORM_LINUX \
|| BX_PLATFORM_ANDROID \
|| BX_PLATFORM_EMSCRIPTEN \
|| BX_PLATFORM_OSX \
|| BX_PLATFORM_IOS
# include
# include // errno
# include
# include
# include
# include
# include // gettimeofday
# include // inet_addr
# include
# include
typedef int SOCKET;
typedef linger LINGER;
typedef hostent HOSTENT;
typedef in_addr IN_ADDR;
# define SOCKET_ERROR (-1)
# define INVALID_SOCKET (-1)
# define closesocket close
# include "inet_socket.h"
#endif // BX_PLATFORM_
#include
#include
#include
#include
#include
#include // placement new
#include // sscanf
#if BNET_CONFIG_OPENSSL
# include
# include
# include
#else
# define SSL_CTX void
# define X509 void
# define EVP_PKEY void
#endif // BNET_CONFIG_OPENSSL
#include
namespace bnet
{
struct Internal
{
enum Enum
{
None,
Disconnect,
Notify,
};
};
extern bx::AllocatorI* g_allocator;
Handle ctxAccept(Handle _listenHandle, SOCKET _socket, uint32_t _ip, uint16_t _port, bool _raw, X509* _cert, EVP_PKEY* _key);
void ctxPush(Handle _handle, MessageId::Enum _id);
void ctxPush(Message* _msg);
Message* msgAlloc(Handle _handle, uint16_t _size, bool _incoming = false, Internal::Enum _type = Internal::None);
void msgRelease(Message* _msg);
template
class FreeList
{
public:
FreeList(uint16_t _max)
{
m_memBlock = bx::alloc(g_allocator, _max*sizeof(Ty) );
m_handleAlloc = bx::createHandleAlloc(g_allocator, _max);
}
~FreeList()
{
bx::destroyHandleAlloc(g_allocator, m_handleAlloc);
bx::free(g_allocator, m_memBlock);
}
Ty* create()
{
uint16_t handle = m_handleAlloc->alloc();
if (handle == bx::kInvalidHandle) {
return NULL;
}
Ty* first = reinterpret_cast(m_memBlock);
Ty* obj = &first[handle];
obj = ::new (obj) Ty;
return obj;
}
template Ty* create(Arg0 _a0)
{
uint16_t handle = m_handleAlloc->alloc();
if (handle == bx::kInvalidHandle) {
return NULL;
}
Ty* first = reinterpret_cast(m_memBlock);
Ty* obj = &first[handle];
obj = ::new (obj) Ty(_a0);
return obj;
}
template Ty* create(Arg0 _a0, Arg1 _a1)
{
uint16_t handle = m_handleAlloc->alloc();
if (handle == bx::kInvalidHandle) {
return NULL;
}
Ty* first = reinterpret_cast(m_memBlock);
Ty* obj = &first[handle];
obj = ::new (obj) Ty(_a0, _a1);
return obj;
}
template Ty* create(Arg0 _a0, Arg1 _a1, Arg2 _a2)
{
uint16_t handle = m_handleAlloc->alloc();
if (handle == bx::kInvalidHandle) {
return NULL;
}
Ty* first = reinterpret_cast(m_memBlock);
Ty* obj = &first[handle];
obj = ::new (obj) Ty(_a0, _a1, _a2);
return obj;
}
void destroy(Ty* _obj)
{
_obj->~Ty();
m_handleAlloc->free(getHandle(_obj) );
}
uint16_t getHandle(Ty* _obj) const
{
Ty* first = reinterpret_cast(m_memBlock);
return (uint16_t)(_obj - first);
}
Ty* getFromHandle(uint16_t _index)
{
Ty* first = reinterpret_cast(m_memBlock);
return &first[_index];
}
uint16_t getNumHandles() const
{
return m_handleAlloc->getNumHandles();
}
uint16_t getMaxHandles() const
{
return m_handleAlloc->getMaxHandles();
}
Ty* getFromHandleAt(uint16_t _at)
{
uint16_t handle = m_handleAlloc->getHandleAt(_at);
return getFromHandle(handle);
}
private:
void* m_memBlock;
bx::HandleAlloc* m_handleAlloc;
};
class RecvRingBuffer
{
BX_CLASS(RecvRingBuffer
, NO_DEFAULT_CTOR
, NO_COPY
);
public:
RecvRingBuffer(bx::RingBufferControl& _control, char* _buffer)
: m_control(_control)
, m_write(_control.m_current)
, m_reserved(0)
, m_buffer(_buffer)
{
}
~RecvRingBuffer()
{
}
int recv(SOCKET _socket)
{
m_reserved += m_control.reserve(UINT32_MAX);
uint32_t end = (m_write + m_reserved) % m_control.m_size;
uint32_t wrap = end < m_write ? m_control.m_size - m_write : m_reserved;
char* to = &m_buffer[m_write];
int bytes = ::recv(_socket
, to
, wrap
, 0
);
if (0 < bytes)
{
m_write += bytes;
m_write %= m_control.m_size;
m_reserved -= bytes;
m_control.commit(bytes);
}
return bytes;
}
#if BNET_CONFIG_OPENSSL
int recv(SSL* _ssl)
{
m_reserved += m_control.reserve(-1);
uint32_t end = (m_write + m_reserved) % m_control.m_size;
uint32_t wrap = end < m_write ? m_control.m_size - m_write : m_reserved;
char* to = &m_buffer[m_write];
int bytes = SSL_read(_ssl
, to
, wrap
);
if (0 < bytes)
{
m_write += bytes;
m_write %= m_control.m_size;
m_reserved -= bytes;
m_control.commit(bytes);
}
return bytes;
}
#endif // BNET_CONFIG_OPENSSL
private:
bx::RingBufferControl& m_control;
uint32_t m_write;
uint32_t m_reserved;
char* m_buffer;
};
class MessageQueue
{
public:
MessageQueue()
{
}
~MessageQueue()
{
}
void push(Message* _msg)
{
m_queue.push_back(_msg);
}
Message* peek()
{
if (!m_queue.empty() )
{
return m_queue.front();
}
return NULL;
}
Message* pop()
{
if (!m_queue.empty() )
{
Message* msg = m_queue.front();
m_queue.pop_front();
return msg;
}
return NULL;
}
private:
std::list m_queue;
};
} // namespace bnet
#endif // BNET_P_H_HEADER_GUARD
================================================
FILE: src/inet_socket.h
================================================
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bnet/blob/master/LICENSE
*/
#ifndef BNET_INET_SOCKET_H_HEADER_GUARD
#define BNET_INET_SOCKET_H_HEADER_GUARD
static int connectsocket(SOCKET socket, uint32_t _ip, uint16_t _port, bool /*_secure*/)
{
sockaddr_in addr;
bx::memSet(&addr, 0, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(_ip);
addr.sin_port = htons(_port);
union
{
sockaddr* sa;
sockaddr_in* sain;
} saintosa;
saintosa.sain = &addr;
return ::connect(socket, saintosa.sa, sizeof(addr) );
}
static bool issocketready(SOCKET socket)
{
fd_set rfds;
FD_ZERO(&rfds);
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(socket, &rfds);
FD_SET(socket, &wfds);
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int result = ::select( (int)socket + 1 /*nfds is ignored on windows*/, &rfds, &wfds, NULL, &timeout);
return result > 0;
}
#endif // BNET_INET_SOCKET_H_HEADER_GUARD