BIO_S_BIO(3SSL)
π BIO_S_BIO β the BIO pair glow-up (OpenSSL, 3SSL) π
okay bestie, sit down, hydrate, and let's talk about the BIO pair β it's genuinely one of the most iconic little machines in OpenSSL and nobody talks about it enough. This page covers BIO_s_bio, BIO_make_bio_pair, BIO_destroy_bio_pair, BIO_shutdown_wr, BIO_set_write_buf_size, BIO_get_write_buf_size, BIO_new_bio_pair, BIO_get_write_guarantee, BIO_ctrl_get_write_guarantee, BIO_get_read_request, BIO_ctrl_get_read_request, BIO_ctrl_reset_read_request, BIO_nread0, BIO_nread, BIO_nwrite0, and BIO_nwrite. That's the whole girl group. π«Ά
β¨ NAME β¨
BIO_s_bio, BIO_make_bio_pair, BIO_destroy_bio_pair, BIO_shutdown_wr, BIO_set_write_buf_size, BIO_get_write_buf_size, BIO_new_bio_pair, BIO_get_write_guarantee, BIO_ctrl_get_write_guarantee, BIO_get_read_request, BIO_ctrl_get_read_request, BIO_ctrl_reset_read_request, BIO_nread0, BIO_nread, BIO_nwrite0, BIO_nwrite β aka the BIO pair BIO. no cap, that's literally the whole NAME section and we're keeping it.
πΈ SYNOPSIS πΈ
#include <openssl/bio.h>
const BIO_METHOD *BIO_s_bio(void);
int BIO_make_bio_pair(BIO *b1, BIO *b2);
int BIO_destroy_bio_pair(BIO *b);
int BIO_shutdown_wr(BIO *b);
int BIO_set_write_buf_size(BIO *b, long size);
size_t BIO_get_write_buf_size(BIO *b, long size);
int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, BIO **bio2, size_t writebuf2);
int BIO_get_write_guarantee(BIO *b);
size_t BIO_ctrl_get_write_guarantee(BIO *b);
int BIO_get_read_request(BIO *b);
size_t BIO_ctrl_get_read_request(BIO *b);
int BIO_ctrl_reset_read_request(BIO *b);
int BIO_nread0(BIO *bio, char **buf);
int BIO_nread(BIO *bio, char **buf, int num);
int BIO_nwrite0(BIO *bio, char **buf);
int BIO_nwrite(BIO *bio, char **buf, int num);
look at her. so demure. so mindful. β¨
π¦ DESCRIPTION π¦
BIO_s_bio() hands you the method for a BIO pair. And what is a BIO pair, you ask? It's two source/sink BIOs, and here's the tea: whatever you write into either half gets buffered, and then you can read it out of the other half. They're two halves of one whole, fr fr. π«Ά
π Important, bestie: both halves usually have to be handled by the same application thread, because no locking happens on the internal data structures. Not thread-safe, not even a little bit. Don't put one half in another thread and act surprised, okay?
Since BIO chains typically end in a source/sink BIO, you can absolutely make that end one half of a BIO pair and have the entire chain's data processed under your own application's control. main character energy for your I/O pipeline. π
The classic use case? Putting TLS/SSL I/O under application control. That's your move when you want a non-standard transport for TLS/SSL, or when the normal socket routines just aren't giving what they need to give.
- Calls to
BIO_read_ex()read data from the buffer β or request a retry if there's no data available. π - Calls to
BIO_write_ex()place data in the buffer β or request a retry if the buffer is full. π - The standard calls
BIO_ctrl_pending()andBIO_ctrl_wpending()tell you how much pending data is chillin' in the read buffer or the write buffer. BIO_reset()clears any data in the write buffer. (note: the write buffer, bestie.)BIO_make_bio_pair()joins two separate BIOs into one connected pair. πBIO_destroy_pair()destroys the association between two connected BIOs. And btw β freeing up any half of the pair will automatically destroy the association. iconic behavior.BIO_shutdown_wr()closes down a BIOb. After this call, no further writes on BIObare allowed (they will return an error). Reads on the other half will return any pending data, or EOF once all pending data has been read. πBIO_set_write_buf_size()sets the write buffer size of BIObtosize. If the size isn't initialized, a default value is used β currently 17K, sufficient for a maximum size TLS record. (yes, 17K. that's the number, don't @ me.)BIO_get_write_buf_size()returns the size of the write buffer.BIO_new_bio_pair()is the combo meal π β it combines calls toBIO_new(),BIO_make_bio_pair(), andBIO_set_write_buf_size()to create a connected pair of BIOsbio1andbio2, with write buffer sizeswritebuf1andwritebuf2. If either size is zero, the default size is used.
π¨ Heads up:BIO_new_bio_pair()does not check whetherbio1orbio2point to some other BIO. It just overwrites the values, and it does not callBIO_free(). If you had something living there before, it's gone now. π
BIO_get_write_guarantee() and BIO_ctrl_get_write_guarantee() return the maximum length of data that can currently be written to the BIO. Write more than that and BIO_write_ex() will either return a value less than the amount you requested, or β if the buffer is full β request a retry. Also, tiny detail that matters: BIO_ctrl_get_write_guarantee() is a function, while BIO_get_write_guarantee() is a macro. different girls, same energy. π
BIO_get_read_request() and BIO_ctrl_get_read_request() return the amount of data requested β or the buffer size if that's less β in the case where the last read attempt at the other half of the BIO pair failed because the buffer was empty. This is how you figure out how much data you should write to the BIO so the next read actually succeeds. It's most useful in TLS/SSL apps, where the amount of data read is usually meaningful rather than just a buffer size. π
After a successful read, this call returns zero. It will also return zero once new data has been written satisfying the read request β or even just part of it. And note, bestie: BIO_get_read_request() never returns an amount larger than what BIO_get_write_guarantee() returns.
BIO_ctrl_reset_read_request() can also be used to reset the value returned by BIO_get_read_request() back to zero. clean slate, fresh start. β¨
π§ Non-copying Interface π§
BIO_nread0(), BIO_nread(), BIO_nwrite0(), and BIO_nwrite() give you a non-copying interface for reading from and writing to BIO pairs. These functions let you access the internal buffer directly, which means you skip the overhead of copying data around. efficient queen. π
BIO_nread0() returns, in *buf, a pointer to the start of the available data in the peer's write buffer, and returns the number of bytes available. So you're reading straight out of the buffer, no copying, no drama. It does not consume the data β you need a subsequent call to BIO_nread() to actually advance the buffer position.
BIO_nread() is like BIO_nread0() but it also advances the read position by up to num bytes, and returns the actual number of bytes consumed. The *buf pointer is set to the start of the data that was consumed. And because the data counts as consumed after this call, the pointer returned by BIO_nread() should not be used afterwards unless the caller also controls the writing side. The typical pattern is: call BIO_nread0() first, use the data, then call BIO_nread() to consume it. chef's kiss sequencing. π
BIO_nwrite0() returns, in *buf, a pointer to the start of the available space in the write buffer, plus the number of bytes that can be written. Write directly into the buffer, no copying required. It does not commit the data β you need a subsequent call to BIO_nwrite() to update the buffer length.
BIO_nwrite() is like BIO_nwrite0() but it also commits up to num bytes as written, returning the actual number of bytes committed. The *buf pointer is set to the start of the region that was committed.
π No seriously bestie: only callBIO_nwrite()after the data has actually been written to the buffer you got fromBIO_nwrite0(), because committing is what signals data availability to the reading side. Don't commit vibes you haven't actually delivered.
And a note on the ring buffer implementation: if wrapping around would be required, BIO_nread0() and BIO_nwrite0() may return less than the total available space. In such cases you may need a second call to get at the remaining data or space. it's giving "two trips to the fridge." π
π NOTES π
Both halves of a BIO pair should be freed. Even if one half gets implicitly freed through a BIO_free_all() or SSL_free() call, the other half still needs to be freed. both halves, bestie. both. π«Ά
When you use these in bidirectional applications (TLS/SSL, looking at you), be careful to flush any data in the write buffer. Here's how: call BIO_pending() on the other half of the pair, and if any data is pending, read it and send it to the underlying transport. And this has to happen before any normal processing (like calling select()) that happens due to a request combined with BIO_should_read() being true.
π¨ Why this matters, for real: imagine a case where a request is sent usingBIO_write_ex()and a response is read withBIO_read_ex()β this can legitimately happen during a TLS/SSL handshake.BIO_write_ex()will succeed and place data in the write buffer.BIO_read_ex()will initially fail andBIO_should_read()will betrue. Now if the application then waits for data to be available on the underlying transport before flushing the write buffer, it will never succeed β because the request was never sent! π eternal purgatory. don't do this.
BIO_eof() is true if there's no data in the peer BIO and the peer BIO has been shutdown.
Also, a little "these are macros" service announcement: BIO_make_bio_pair(), BIO_destroy_bio_pair(), BIO_shutdown_wr(), BIO_set_write_buf_size(), BIO_get_write_buf_size(), BIO_get_write_guarantee(), and BIO_get_read_request() are implemented as macros. π£
β RETURN VALUES β
BIO_new_bio_pair() returns 1 on success, with the new BIOs available in bio1 and bio2. On failure it returns 0, with NULL pointers stored into the locations for bio1 and bio2. Check the error stack for more info, bestie. π
π [XXXXX: More return values need to be added here] β yeah, that's literally in the source docs. the OpenSSL girlies left a note for themselves. we're keeping it fr fr. π
BIO_nread0() returns the number of bytes available for reading, 0 if the peer has closed and no data remains (EOF), or -1 if no data is currently available (a retry may be appropriate). If the BIO is not initialized, -2 is returned. π
BIO_nwrite0() returns the number of bytes of space available for writing, or -1 if no space is currently available (a retry may be appropriate) or the BIO has been closed. If the BIO is not initialized, -2 is returned. π
BIO_nread() and BIO_nwrite() return the number of bytes consumed or committed respectively β or the same error values as BIO_nread0() and BIO_nwrite0(). π
okay that's part 1 and we are not done yet β more of this page is coming, so don't go anywhere bestie. π
π EXAMPLES (bestie let's get PRACTICAL) π
The BIO pair is literally how you get full control over the network access of your application, no cap. Your app can just call select() on the socket whenever it wants, and it never has to go through the SSL-interface at all. Main character energy fr fr. π
BIO *internal_bio, *network_bio;
...
BIO_new_bio_pair(&internal_bio, 0, &network_bio, 0);
SSL_set_bio(ssl, internal_bio, internal_bio);
SSL_operations(); /* e.g. SSL_read and SSL_write */
...
application | TLS-engine
| |
+----------> SSL_operations()
| /\ ||
| || \/
| BIO-pair (internal_bio)
| BIO-pair (network_bio)
| || /\
| \/ ||
+-----------< BIO_operations()
| |
| |
socket
...
SSL_free(ssl); /* implicitly frees internal_bio */
BIO_free(network_bio);
...
And here's the β¨importantβ¨ chaos: because the BIO pair only buffers the data and never directly touches the connection, it behaves nonblocking and will return as soon as the write buffer is full or the read buffer is drained. So then it's your app's job to flush the write buffer and/or fill the read buffer. It's giving teamwork. π«Ά
Use BIO_ctrl_pending() to clock whether data is buffered in the BIO and needs to be transferred to the network. Use BIO_ctrl_get_read_request() to find out how many bytes must be written into the buffer before the SSL_operation() can successfully be continued. iykyk. π
A typical usage pattern for the non-copying write interface is:
int ret;
char *buf;
ret = BIO_nwrite0(bio, &buf);
if (ret > 0) {
/* write up to 'ret' bytes directly to 'buf' */
memcpy(buf, data, len);
BIO_nwrite(bio, &buf, len); /* commit the write */
}
A typical usage pattern for the non-copying read interface is:
int ret;
char *buf;
ret = BIO_nread0(bio, &buf);
if (ret > 0) {
/* read up to 'ret' bytes directly from 'buf' */
process_data(buf, ret);
BIO_nread(bio, &buf, ret); /* consume the data */
}
π WARNINGS (okay bestie, listen up) π
Because the data is buffered, SSL_operation() may return with an ERROR_SSL_WANT_READ condition β and yet there's STILL data sitting in the write buffer. πΈ
Not me warning you twice: an application must not rely on the error value of SSL_operation(), and it must assure that the write buffer is always flushed first. Otherwise a deadlock may occur, because the peer might be waiting for that data before being able to continue. π
So don't be messy about this one. Flush first, always. Periodt.
π¦ SEE ALSO (the besties to go visit) π¦
π COPYRIGHT π
Copyright 2000-2026 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.
β¨ that's the whole page bestie, you ate it β¨ ππ
Sourced from version 4.0.3, dated 2026-09-29 β BIO_S_BIO(3SSL). Go flush your write buffers and slay the day, ily π«Άπ
π¦ where this page comes from
- ubuntu stonking: libssl-doc 4.0.3-1ubuntu1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms