nyara 0.0.1.pre.2 → 0.0.1.pre.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/request.c CHANGED
@@ -1,187 +1,17 @@
1
+ /* request parsing and request object */
2
+
1
3
  #include "nyara.h"
2
4
  #include <ruby/encoding.h>
3
- #include <multipart_parser.h>
4
- #include <errno.h>
5
- #ifndef write
6
- #include <unistd.h>
7
- #endif
8
-
9
- typedef struct {
10
- http_parser hparser;
11
- multipart_parser* mparser;
12
- enum http_method method;
13
- VALUE header;
14
- VALUE accept; // mime array sorted with q
15
- VALUE format; // string ext without dot
16
- VALUE fiber;
17
- VALUE scope; // mapped prefix
18
- VALUE path_with_query;
19
- VALUE path;
20
- VALUE query;
21
- VALUE last_field;
22
- VALUE last_value;
23
- VALUE self;
24
- int fd;
25
-
26
- // response
27
- int status;
28
- VALUE response_content_type;
29
- VALUE response_header;
30
- VALUE response_header_extra_lines;
31
- } Request;
32
-
33
- // typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
34
- // typedef int (*http_cb) (http_parser*);
35
-
36
- static ID id_not_found;
5
+ #include "request.h"
6
+
37
7
  static VALUE str_html;
38
8
  static rb_encoding* u8_encoding;
39
9
  static VALUE request_class;
40
- static VALUE method_override_key;
41
- static VALUE str_accept;
42
- static VALUE nyara_http_methods;
43
-
44
- static VALUE fd_request_map;
45
- #define MAX_RECEIVE_DATA 65536
46
- static char received_data[MAX_RECEIVE_DATA];
47
-
48
- static VALUE _fiber_func(VALUE _, VALUE args) {
49
- VALUE instance = rb_ary_pop(args);
50
- VALUE meth = rb_ary_pop(args);
51
- rb_apply(instance, SYM2ID(meth), args);
52
- return Qnil;
53
- }
54
-
55
- static void _upcase_method(VALUE str) {
56
- char* s = RSTRING_PTR(str);
57
- long len = RSTRING_LEN(str);
58
- for (long i = 0; i < len; i++) {
59
- if (s[i] >= 'a' && s[i] <= 'z') {
60
- s[i] = 'A' + (s[i] - 'a');
61
- }
62
- }
63
- }
64
-
65
- static inline void _close_fd(int fd) {
66
- rb_hash_delete(fd_request_map, INT2FIX(fd));
67
- close(fd);
68
- }
69
-
70
- static int on_url(http_parser* parser, const char* s, size_t len) {
71
- Request* p = (Request*)parser;
72
- p->method = parser->method;
73
-
74
- if (p->path_with_query == Qnil) {
75
- p->path_with_query = rb_str_new(s, len);
76
- } else {
77
- rb_str_cat(p->path_with_query, s, len);
78
- }
79
- return 0;
80
- }
81
-
82
- static int on_message_complete(http_parser* parser) {
83
- Request* p = (Request*)parser;
84
- if (p->fiber == Qnil) {
85
- return 1;
86
- } else {
87
- VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
88
- if (state == Qnil) { // terminated (todo check raise error)
89
- if (p->status == 200) {
90
- write(p->fd, "0\r\n\r\n", 5);
91
- }
92
- _close_fd(p->fd);
93
- p->fd = 0;
94
- } else if (SYM2ID(state) == rb_intern("term_close")) {
95
- write(p->fd, "0\r\n\r\n", 5);
96
- _close_fd(p->fd);
97
- p->fd = 0;
98
- }
99
- return 0;
100
- }
101
- }
102
-
103
- static int on_header_field(http_parser* parser, const char* s, size_t len) {
104
- Request* p = (Request*)parser;
105
- if (p->last_field == Qnil) {
106
- p->last_field = rb_str_new(s, len);
107
- p->last_value = Qnil;
108
- } else {
109
- rb_str_cat(p->last_field, s, len);
110
- }
111
- return 0;
112
- }
113
-
114
- static int on_header_value(http_parser* parser, const char* s, size_t len) {
115
- Request* p = (Request*)parser;
116
- if (p->last_field == Qnil) {
117
- if (p->last_value == Qnil) {
118
- rb_bug("on_header_value called when neither last_field nor last_value exist");
119
- return 1;
120
- }
121
- rb_str_cat(p->last_value, s, len);
122
- } else {
123
- nyara_headerlize(p->last_field);
124
- p->last_value = rb_str_new(s, len);
125
- rb_hash_aset(p->header, p->last_field, p->last_value);
126
- p->last_field = Qnil;
127
- }
128
- return 0;
129
- }
130
-
131
- // may override POST by _method in query
132
- static void _parse_path_and_query(Request* p) {
133
- char* s = RSTRING_PTR(p->path_with_query);
134
- long len = RSTRING_LEN(p->path_with_query);
135
- long query_i = nyara_parse_path(p->path, s, len);
136
- if (query_i < len) {
137
- nyara_parse_param(p->query, s + query_i, len - query_i);
138
-
139
- // do method override with _method=xxx in query
140
- if (p->method == HTTP_POST) {
141
- VALUE meth = rb_hash_aref(p->query, method_override_key);
142
- if (TYPE(meth) == T_STRING) {
143
- _upcase_method(meth);
144
- VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
145
- if (meth_num != Qnil) {
146
- p->method = FIX2INT(meth_num);
147
- }
148
- }
149
- }
150
- }
151
- }
10
+ static VALUE sym_writing;
152
11
 
153
- static int on_headers_complete(http_parser* parser) {
154
- Request* p = (Request*)parser;
155
- p->last_field = Qnil;
156
- p->last_value = Qnil;
157
-
158
- _parse_path_and_query(p);
159
- p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
160
- volatile RouteResult result = nyara_lookup_route(p->method, p->path, p->accept);
161
- if (RTEST(result.controller)) {
162
- rb_ary_push(result.args, rb_class_new_instance(1, &(p->self), result.controller));
163
- // result.args is on stack, no need to worry gc
164
- p->fiber = rb_fiber_new(_fiber_func, result.args);
165
- p->scope = result.scope;
166
- p->format = result.format;
167
- p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
168
- p->response_header_extra_lines = rb_ary_new();
169
- return 0;
170
- }
171
- rb_funcall(p->self, id_not_found, 0);
172
- return 1;
173
- }
174
-
175
- static http_parser_settings request_settings = {
176
- .on_message_begin = NULL,
177
- .on_url = on_url,
178
- .on_status_complete = NULL,
179
- .on_header_field = on_header_field,
180
- .on_header_value = on_header_value,
181
- .on_headers_complete = on_headers_complete,
182
- .on_body = NULL, // data_cb
183
- .on_message_complete = on_message_complete
184
- };
12
+ #define P \
13
+ Request* p;\
14
+ Data_Get_Struct(self, Request, p);
185
15
 
186
16
  static void request_mark(void* pp) {
187
17
  Request* p = pp;
@@ -199,6 +29,7 @@ static void request_mark(void* pp) {
199
29
  rb_gc_mark_maybe(p->response_content_type);
200
30
  rb_gc_mark_maybe(p->response_header);
201
31
  rb_gc_mark_maybe(p->response_header_extra_lines);
32
+ rb_gc_mark_maybe(p->watched_fds);
202
33
  }
203
34
  }
204
35
 
@@ -206,19 +37,25 @@ static void request_free(void* pp) {
206
37
  Request* p = pp;
207
38
  if (p) {
208
39
  if (p->fd) {
209
- _close_fd(p->fd);
40
+ nyara_detach_fd(p->fd);
210
41
  }
211
42
  xfree(p);
212
43
  }
213
44
  }
214
45
 
215
- static Request* request_alloc() {
46
+ static Request* _request_alloc() {
216
47
  Request* p = ALLOC(Request);
217
48
  http_parser_init(&(p->hparser), HTTP_REQUEST);
49
+ p->mparser = NULL;
50
+
51
+ p->method = HTTP_GET;
52
+ p->fd = 0;
53
+ p->parse_state = 0;
54
+ p->status = 200;
55
+
218
56
  volatile VALUE header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
219
57
  volatile VALUE path = rb_enc_str_new("", 0, u8_encoding);
220
58
  volatile VALUE query = rb_class_new_instance(0, NULL, nyara_param_hash_class);
221
- p->mparser = NULL;
222
59
  p->header = header;
223
60
  p->accept = Qnil;
224
61
  p->format = Qnil;
@@ -229,66 +66,36 @@ static Request* request_alloc() {
229
66
  p->query = query;
230
67
  p->last_field = Qnil;
231
68
  p->last_value = Qnil;
232
- p->fd = 0;
233
- p->status = 200;
234
69
  p->response_content_type = Qnil;
235
70
  p->response_header = Qnil;
236
71
  p->response_header_extra_lines = Qnil;
72
+
73
+ volatile VALUE watched_fds = rb_ary_new();
74
+ p->watched_fds = watched_fds;
75
+
237
76
  p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p);
238
77
  return p;
239
78
  }
240
79
 
241
- static VALUE request_alloc_func(VALUE klass) {
242
- return request_alloc()->self;
80
+ VALUE nyara_request_new(int fd) {
81
+ Request* p = _request_alloc();
82
+ p->fd = fd;
83
+ return p->self;
243
84
  }
244
85
 
245
- /* client entrance
246
- invoke order:
247
- - find/create request
248
- - http_parser_execute
249
- - on_headers_complete => 404 or create request
250
- - on_message_complete => run action
251
- */
252
- void nyara_handle_request(int fd) {
253
- Request* p = NULL;
254
- bool first_time = false;
255
-
256
- {
257
- VALUE v_fd = INT2FIX(fd);
258
- VALUE request = rb_hash_aref(fd_request_map, v_fd);
259
- if (request == Qnil) {
260
- p = request_alloc();
261
- p->fd = fd;
262
- rb_hash_aset(fd_request_map, v_fd, p->self);
263
- first_time = true;
264
- } else {
265
- Data_Get_Struct(request, Request, p);
266
- }
267
- }
268
-
269
- long len = read(fd, received_data, MAX_RECEIVE_DATA);
270
- if (len < 0) {
271
- if (errno != EAGAIN) {
272
- // todo log the bug
273
- if (p->fd) {
274
- _close_fd(p->fd);
275
- p->fd = 0;
276
- }
277
- }
278
- } else {
279
- if (first_time && !len) {
280
- // todo log this exception
281
- return;
282
- }
283
- // note: when len == 0, means eof reached, that also informs http_parser the eof
284
- http_parser_execute(&(p->hparser), &request_settings, received_data, len);
86
+ void nyara_request_term_close(VALUE self, bool write_last_chunk) {
87
+ P;
88
+ if (write_last_chunk || p->status == 200) {
89
+ // usually this succeeds, while not, it doesn't matter cause we are closing it
90
+ # pragma GCC diagnostic push
91
+ # pragma GCC diagnostic ignored "-Wunused-result"
92
+ write(p->fd, "0\r\n\r\n", 5);
93
+ # pragma GCC diagnostic pop
285
94
  }
95
+ nyara_detach_fd(p->fd);
96
+ p->fd = 0;
286
97
  }
287
98
 
288
- #define P \
289
- Request* p;\
290
- Data_Get_Struct(self, Request, p);
291
-
292
99
  static VALUE request_http_method(VALUE self) {
293
100
  P;
294
101
  return rb_str_new2(http_method_str(p->method));
@@ -361,50 +168,72 @@ static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
361
168
  return n;
362
169
  }
363
170
 
364
- static VALUE ext_send_data(VALUE _, VALUE self, VALUE data) {
365
- P;
366
- char* buf = RSTRING_PTR(data);
367
- long len = RSTRING_LEN(data);
368
-
171
+ // return true if success
172
+ static bool _send_data(int fd, const char* buf, long len) {
369
173
  while(len) {
370
- long written = write(p->fd, buf, len);
371
- if (written == 0)
372
- return Qnil;
373
- if (written == -1) {
374
- if (errno == EWOULDBLOCK || errno == EAGAIN) {
375
- // todo enqueue data and set state
174
+ long written = write(fd, buf, len);
175
+ if (written <= 0) {
176
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
177
+ rb_fiber_yield(1, &sym_writing);
178
+ } else {
179
+ return false;
180
+ }
181
+ } else {
182
+ buf += written;
183
+ len -= written;
184
+ if (len) {
185
+ rb_fiber_yield(1, &sym_writing);
376
186
  }
377
- return Qnil;
378
187
  }
379
- buf += written;
380
- len -= written;
381
188
  }
189
+ return true;
190
+ }
191
+
192
+ static VALUE ext_request_send_data(VALUE _, VALUE self, VALUE data) {
193
+ P;
194
+ char* buf = RSTRING_PTR(data);
195
+ long len = RSTRING_LEN(data);
196
+ _send_data(p->fd, buf, len);
382
197
  return Qnil;
383
198
  }
384
199
 
385
- static VALUE ext_send_chunk(VALUE _, VALUE self, VALUE str) {
200
+ static VALUE ext_request_send_chunk(VALUE _, VALUE self, VALUE str) {
386
201
  long len = RSTRING_LEN(str);
387
202
  if (!len) {
388
203
  return Qnil;
389
204
  }
390
- // todo len overflow?
391
205
  P;
392
- long res = dprintf(p->fd, "%lx\r\n%.*s\r\n", len, (int)len, RSTRING_PTR(str));
393
- if (res < 0) {
394
- rb_raise(rb_eRuntimeError, "%s", strerror(errno));
206
+
207
+ char pre_buf[20]; // enough space to hold a long + 2 chars
208
+ long pre_len = sprintf(pre_buf, "%lx\r\n", len);
209
+ if (pre_len <= 0) {
210
+ rb_raise(rb_eRuntimeError, "fail to format chunk length for len: %ld", len);
211
+ }
212
+ bool success = \
213
+ _send_data(p->fd, pre_buf, pre_len) &&
214
+ _send_data(p->fd, RSTRING_PTR(str), len) &&
215
+ _send_data(p->fd, "\r\n", 2);
216
+
217
+ if (!success) {
218
+ rb_sys_fail("write(2)");
395
219
  }
220
+
396
221
  return Qnil;
397
222
  }
398
223
 
399
224
  // for test: find or create a request with a fd
400
- static VALUE ext_handle_request(VALUE _, VALUE v_fd) {
401
- int fd = FIX2INT(v_fd);
402
- nyara_handle_request(fd);
403
- return rb_hash_aref(fd_request_map, v_fd);
225
+ static VALUE ext_request_new(VALUE _) {
226
+ return _request_alloc()->self;
227
+ }
228
+
229
+ static VALUE ext_request_set_fd(VALUE _, VALUE self, VALUE vfd) {
230
+ P;
231
+ p->fd = NUM2INT(vfd);
232
+ return Qnil;
404
233
  }
405
234
 
406
235
  // set internal attrs in the request object
407
- static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
236
+ static VALUE ext_request_set_attrs(VALUE _, VALUE self, VALUE attrs) {
408
237
  # define ATTR(key) rb_hash_delete(attrs, ID2SYM(rb_intern(key)))
409
238
  # define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
410
239
  P;
@@ -437,22 +266,14 @@ static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
437
266
  }
438
267
 
439
268
  void Init_request(VALUE nyara, VALUE ext) {
440
- id_not_found = rb_intern("not_found");
441
269
  str_html = rb_str_new2("html");
442
270
  OBJ_FREEZE(str_html);
443
271
  rb_gc_register_mark_object(str_html);
444
272
  u8_encoding = rb_utf8_encoding();
445
- method_override_key = rb_str_new2("_method");
446
- rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
447
- nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
448
- fd_request_map = rb_hash_new();
449
- rb_gc_register_mark_object(fd_request_map);
450
- str_accept = rb_str_new2("Accept");
451
- rb_gc_register_mark_object(str_accept);
273
+ sym_writing = ID2SYM(rb_intern("writing"));
452
274
 
453
275
  // request
454
276
  request_class = rb_define_class_under(nyara, "Request", rb_cObject);
455
- rb_define_alloc_func(request_class, request_alloc_func);
456
277
  rb_define_method(request_class, "http_method", request_http_method, 0);
457
278
  rb_define_method(request_class, "header", request_header, 0);
458
279
  rb_define_method(request_class, "scope", request_scope, 0);
@@ -470,9 +291,10 @@ void Init_request(VALUE nyara, VALUE ext) {
470
291
 
471
292
  // hide internal methods in ext
472
293
  rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
473
- rb_define_singleton_method(ext, "send_data", ext_send_data, 2);
474
- rb_define_singleton_method(ext, "send_chunk", ext_send_chunk, 2);
294
+ rb_define_singleton_method(ext, "request_send_data", ext_request_send_data, 2);
295
+ rb_define_singleton_method(ext, "request_send_chunk", ext_request_send_chunk, 2);
475
296
  // for test
476
- rb_define_singleton_method(ext, "handle_request", ext_handle_request, 1);
477
- rb_define_singleton_method(ext, "set_request_attrs", ext_set_request_attrs, 2);
297
+ rb_define_singleton_method(ext, "request_new", ext_request_new, 0);
298
+ rb_define_singleton_method(ext, "request_set_fd", ext_request_set_fd, 2);
299
+ rb_define_singleton_method(ext, "request_set_attrs", ext_request_set_attrs, 2);
478
300
  }
data/ext/request.h ADDED
@@ -0,0 +1,43 @@
1
+ #pragma once
2
+
3
+ #include "nyara.h"
4
+ #include <multipart_parser.h>
5
+ #include <errno.h>
6
+ #ifndef write
7
+ #include <unistd.h>
8
+ #endif
9
+
10
+ enum ParseState {
11
+ PS_INIT, PS_HEADERS_COMPLETE, PS_MESSAGE_COMPLETE, PS_ERROR
12
+ };
13
+
14
+ typedef struct {
15
+ http_parser hparser;
16
+ multipart_parser* mparser;
17
+
18
+ enum http_method method;
19
+ int fd;
20
+ enum ParseState parse_state;
21
+ int status; // response status
22
+
23
+ VALUE self;
24
+
25
+ // request
26
+ VALUE header;
27
+ VALUE accept; // mime array sorted with q
28
+ VALUE format; // string ext without dot
29
+ VALUE fiber;
30
+ VALUE scope; // mapped prefix
31
+ VALUE path_with_query;
32
+ VALUE path;
33
+ VALUE query;
34
+ VALUE last_field;
35
+ VALUE last_value;
36
+
37
+ // response
38
+ VALUE response_content_type;
39
+ VALUE response_header;
40
+ VALUE response_header_extra_lines;
41
+
42
+ VALUE watched_fds;
43
+ } Request;
@@ -0,0 +1,123 @@
1
+ /* request parse callbacks */
2
+
3
+ #include "nyara.h"
4
+ #include "request.h"
5
+
6
+ static VALUE str_accept;
7
+ static VALUE method_override_key;
8
+ static VALUE nyara_http_methods;
9
+
10
+ static int on_url(http_parser* parser, const char* s, size_t len) {
11
+ Request* p = (Request*)parser;
12
+ p->method = parser->method;
13
+
14
+ if (p->path_with_query == Qnil) {
15
+ p->path_with_query = rb_str_new(s, len);
16
+ } else {
17
+ rb_str_cat(p->path_with_query, s, len);
18
+ }
19
+ return 0;
20
+ }
21
+
22
+ static int on_header_field(http_parser* parser, const char* s, size_t len) {
23
+ Request* p = (Request*)parser;
24
+ if (p->last_field == Qnil) {
25
+ p->last_field = rb_str_new(s, len);
26
+ p->last_value = Qnil;
27
+ } else {
28
+ rb_str_cat(p->last_field, s, len);
29
+ }
30
+ return 0;
31
+ }
32
+
33
+ static int on_header_value(http_parser* parser, const char* s, size_t len) {
34
+ Request* p = (Request*)parser;
35
+ if (p->last_field == Qnil) {
36
+ if (p->last_value == Qnil) {
37
+ p->parse_state = PS_ERROR;
38
+ return 1;
39
+ }
40
+ rb_str_cat(p->last_value, s, len);
41
+ } else {
42
+ nyara_headerlize(p->last_field);
43
+ p->last_value = rb_str_new(s, len);
44
+ rb_hash_aset(p->header, p->last_field, p->last_value);
45
+ p->last_field = Qnil;
46
+ }
47
+ return 0;
48
+ }
49
+
50
+ static void _upcase_method(VALUE str) {
51
+ char* s = RSTRING_PTR(str);
52
+ long len = RSTRING_LEN(str);
53
+ for (long i = 0; i < len; i++) {
54
+ if (s[i] >= 'a' && s[i] <= 'z') {
55
+ s[i] = 'A' + (s[i] - 'a');
56
+ }
57
+ }
58
+ }
59
+
60
+ // may override POST by _method in query
61
+ static void _parse_path_and_query(Request* p) {
62
+ char* s = RSTRING_PTR(p->path_with_query);
63
+ long len = RSTRING_LEN(p->path_with_query);
64
+ long query_i = nyara_parse_path(p->path, s, len);
65
+ if (query_i < len) {
66
+ nyara_parse_param(p->query, s + query_i, len - query_i);
67
+
68
+ // do method override with _method=xxx in query
69
+ if (p->method == HTTP_POST) {
70
+ VALUE meth = rb_hash_aref(p->query, method_override_key);
71
+ if (TYPE(meth) == T_STRING) {
72
+ _upcase_method(meth);
73
+ VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
74
+ if (meth_num != Qnil) {
75
+ p->method = FIX2INT(meth_num);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ static int on_headers_complete(http_parser* parser) {
83
+ Request* p = (Request*)parser;
84
+ p->last_field = Qnil;
85
+ p->last_value = Qnil;
86
+
87
+ _parse_path_and_query(p);
88
+ p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
89
+ p->parse_state = PS_HEADERS_COMPLETE;
90
+ return 0;
91
+ }
92
+
93
+ static int on_body(http_parser* parser, const char* s, size_t len) {
94
+ // todo
95
+ return 0;
96
+ }
97
+
98
+ static int on_message_complete(http_parser* parser) {
99
+ Request* p = (Request*)parser;
100
+ p->parse_state = PS_MESSAGE_COMPLETE;
101
+ return 0;
102
+ }
103
+
104
+ // used in event.c
105
+ http_parser_settings nyara_request_parse_settings = {
106
+ .on_message_begin = NULL,
107
+ .on_url = on_url,
108
+ .on_status_complete = NULL,
109
+ .on_header_field = on_header_field,
110
+ .on_header_value = on_header_value,
111
+ .on_headers_complete = on_headers_complete,
112
+ .on_body = on_body,
113
+ .on_message_complete = on_message_complete
114
+ };
115
+
116
+ void Init_request_parse(VALUE nyara) {
117
+ str_accept = rb_str_new2("Accept");
118
+ rb_gc_register_mark_object(str_accept);
119
+ method_override_key = rb_str_new2("_method");
120
+ OBJ_FREEZE(method_override_key);
121
+ rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
122
+ nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
123
+ }
data/ext/route.cc CHANGED
@@ -1,3 +1,5 @@
1
+ /* route register and search */
2
+
1
3
  extern "C" {
2
4
  #include "nyara.h"
3
5
  }