Skip to content

Commit cd12c60

Browse files
committed
bio: add a BIO method that wraps IO-like object
Implement a minimum BIO_METHOD required for SSL/TLS. The underlying IO-like object must implement the following methods: - #read_nonblock(len, exception: false) - #write_nonblock(str, exception: false) - #flush The IO-like object is also required to implement several other methods to function in a later commit in this series.
1 parent a8caa63 commit cd12c60

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

ext/openssl/ossl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,7 @@ Init_openssl(void)
11501150
/*
11511151
* Init components
11521152
*/
1153+
Init_ossl_bio();
11531154
Init_ossl_bn();
11541155
Init_ossl_cipher();
11551156
Init_ossl_config();

ext/openssl/ossl_bio.c

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,190 @@ ossl_membio2str(BIO *bio)
4040

4141
return ret;
4242
}
43+
44+
BIO_METHOD *ossl_bio_meth;
45+
static int bio_state_idx, bio_errinfo_idx;
46+
47+
static void
48+
bio_save_error(BIO *bio, int state)
49+
{
50+
VALUE errinfo = Qnil;
51+
if (state) {
52+
errinfo = rb_errinfo();
53+
if (rb_obj_is_kind_of(errinfo, rb_eException))
54+
rb_set_errinfo(Qnil);
55+
else
56+
errinfo = Qnil;
57+
}
58+
BIO_set_ex_data(bio, bio_state_idx, (void *)(uintptr_t)state);
59+
BIO_set_ex_data(bio, bio_errinfo_idx, (void *)errinfo);
60+
}
61+
62+
int
63+
ossl_bio_restore_error(BIO *bio)
64+
{
65+
int state = (int)(uintptr_t)BIO_get_ex_data(bio, bio_state_idx);
66+
if (!state)
67+
return 0;
68+
69+
VALUE errinfo = (VALUE)BIO_get_ex_data(bio, bio_errinfo_idx);
70+
BIO_set_ex_data(bio, bio_state_idx, (void *)(uintptr_t)0);
71+
BIO_set_ex_data(bio, bio_errinfo_idx, (void *)Qnil);
72+
if (!NIL_P(errinfo))
73+
rb_set_errinfo(errinfo);
74+
return state;
75+
}
76+
77+
struct bwrite_args {
78+
BIO *bio;
79+
const char *data;
80+
size_t dlen;
81+
size_t *written;
82+
};
83+
84+
static VALUE
85+
bio_bwrite0(VALUE args)
86+
{
87+
struct bwrite_args *p = (void *)args;
88+
VALUE io = (VALUE)BIO_get_data(p->bio);
89+
BIO_clear_retry_flags(p->bio);
90+
91+
VALUE str = rb_str_new_static(p->data, p->dlen);
92+
VALUE kwargs = rb_hash_new();
93+
rb_hash_aset(kwargs, ID2SYM(rb_intern("exception")), Qfalse);
94+
VALUE funcallargs[] = { str, kwargs };
95+
VALUE ret = rb_funcallv_public_kw(io, rb_intern("write_nonblock"),
96+
2, funcallargs, RB_PASS_KEYWORDS);
97+
98+
if (RB_INTEGER_TYPE_P(ret)) {
99+
*p->written = NUM2SIZET(ret);
100+
return INT2FIX(1);
101+
}
102+
else if (ret == ID2SYM(rb_intern("wait_readable"))) {
103+
BIO_set_retry_read(p->bio);
104+
return INT2FIX(0);
105+
}
106+
else if (ret == ID2SYM(rb_intern("wait_writable"))) {
107+
BIO_set_retry_write(p->bio);
108+
return INT2FIX(0);
109+
}
110+
else {
111+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
112+
":wait_readable, or :wait_writable");
113+
}
114+
}
115+
116+
static int
117+
bio_bwrite(BIO *bio, const char *data, size_t dlen, size_t *written)
118+
{
119+
struct bwrite_args args = { bio, data, dlen, written };
120+
int state;
121+
122+
VALUE ret = rb_protect(bio_bwrite0, (VALUE)&args, &state);
123+
bio_save_error(bio, state);
124+
if (state)
125+
return 0;
126+
return FIX2INT(ret);
127+
}
128+
129+
struct bread_args {
130+
BIO *bio;
131+
char *data;
132+
size_t dlen;
133+
size_t *readbytes;
134+
};
135+
136+
static VALUE
137+
bio_bread0(VALUE args)
138+
{
139+
struct bread_args *p = (void *)args;
140+
VALUE io = (VALUE)BIO_get_data(p->bio);
141+
BIO_clear_retry_flags(p->bio);
142+
143+
VALUE kwargs = rb_hash_new();
144+
rb_hash_aset(kwargs, ID2SYM(rb_intern("exception")), Qfalse);
145+
VALUE funcallargs[] = { SIZET2NUM(p->dlen), kwargs };
146+
VALUE ret = rb_funcallv_public_kw(io, rb_intern("read_nonblock"),
147+
2, funcallargs, RB_PASS_KEYWORDS);
148+
149+
if (RB_TYPE_P(ret, T_STRING)) {
150+
size_t len = (size_t)RSTRING_LEN(ret);
151+
if (len > p->dlen)
152+
rb_raise(rb_eTypeError, "read_nonblock returned too much data");
153+
memcpy(p->data, RSTRING_PTR(ret), len);
154+
*p->readbytes = len;
155+
return INT2FIX(1);
156+
}
157+
else if (NIL_P(ret)) {
158+
BIO_set_flags(p->bio, BIO_FLAGS_IN_EOF);
159+
return INT2FIX(0);
160+
}
161+
else if (ret == ID2SYM(rb_intern("wait_readable"))) {
162+
BIO_set_retry_read(p->bio);
163+
return INT2FIX(0);
164+
}
165+
else if (ret == ID2SYM(rb_intern("wait_writable"))) {
166+
BIO_set_retry_write(p->bio);
167+
return INT2FIX(0);
168+
}
169+
else {
170+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
171+
":wait_readable, or :wait_writable");
172+
}
173+
}
174+
175+
static int
176+
bio_bread(BIO *bio, char *data, size_t dlen, size_t *readbytes)
177+
{
178+
struct bread_args args = { bio, data, dlen, readbytes };
179+
int state;
180+
181+
VALUE ret = rb_protect(bio_bread0, (VALUE)&args, &state);
182+
bio_save_error(bio, state);
183+
if (state)
184+
return 0;
185+
return FIX2INT(ret);
186+
}
187+
188+
static VALUE
189+
bio_flush0(VALUE vbio)
190+
{
191+
VALUE io = (VALUE)BIO_get_data((BIO *)vbio);
192+
return rb_funcallv_public(io, rb_intern("flush"), 0, NULL);
193+
}
194+
195+
static long
196+
bio_ctrl(BIO *bio, int cmd, long larg, void *parg)
197+
{
198+
int state;
199+
200+
switch (cmd) {
201+
case BIO_CTRL_EOF:
202+
return BIO_test_flags(bio, BIO_FLAGS_IN_EOF);
203+
case BIO_CTRL_FLUSH:
204+
rb_protect(bio_flush0, (VALUE)bio, &state);
205+
bio_save_error(bio, state);
206+
return !state;
207+
default:
208+
return 0;
209+
}
210+
}
211+
212+
void
213+
Init_ossl_bio(void)
214+
{
215+
if ((bio_state_idx = BIO_get_ex_new_index(0, NULL, NULL, NULL, NULL)) < 0 ||
216+
(bio_errinfo_idx = BIO_get_ex_new_index(0, NULL, NULL, NULL, NULL)) < 0)
217+
ossl_raise(eOSSLError, "BIO_get_ex_new_index");
218+
219+
ossl_bio_meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "Ruby IO-like object");
220+
if (!ossl_bio_meth)
221+
ossl_raise(eOSSLError, "BIO_meth_new");
222+
if (!BIO_meth_set_write_ex(ossl_bio_meth, bio_bwrite) ||
223+
!BIO_meth_set_read_ex(ossl_bio_meth, bio_bread) ||
224+
!BIO_meth_set_ctrl(ossl_bio_meth, bio_ctrl)) {
225+
BIO_meth_free(ossl_bio_meth);
226+
ossl_bio_meth = NULL;
227+
ossl_raise(eOSSLError, "BIO_meth_set_*");
228+
}
229+
}

ext/openssl/ossl_bio.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@
1313
BIO *ossl_obj2bio(volatile VALUE *);
1414
VALUE ossl_membio2str(BIO*);
1515

16+
extern BIO_METHOD *ossl_bio_meth;
17+
int ossl_bio_restore_error(BIO *bio);
18+
19+
void Init_ossl_bio(void);
20+
1621
#endif

0 commit comments

Comments
 (0)