aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorheil <heil@terminal-consulting.de>2015-04-16 12:04:10 +0200
committerheil <heil@terminal-consulting.de>2015-04-16 12:04:10 +0200
commita412694bbc7376721c5411ae8a6d58aaeea3c6d8 (patch)
tree64acb23985b0664eeab3975c3c4b12c23f523a85
parent76150309a80d87a6f11b4e2822ba14adce7c1a5b (diff)
haproxy: include patches from mainline
- [PATCH 3/9] BUG/MEDIUM: Do not consider an agent check as failed on - [PATCH 4/9] BUG/MEDIUM: peers: correctly configure the client timeout - [PATCH 5/9] BUG/MEDIUM: buffer: one byte miss in buffer free space - [PATCH 6/9] BUG/MAJOR: http: don't read past buffer's end in - [PATCH 7/9] BUG/MEDIUM: http: the function "(req|res)-replace-value" - [PATCH 8/9] BUG/MINOR: compression: consider the expansion factor in - [PATCH 9/9] BUG/MEDIUM: http: hdr_cnt would not count any header when Signed-off-by: heil <heil@terminal-consulting.de>
-rw-r--r--net/haproxy/Makefile2
-rw-r--r--net/haproxy/patches/0003-BUG-MEDIUM-Do-not-consider-an-agent-check-as-failed-.patch43
-rw-r--r--net/haproxy/patches/0004-BUG-MEDIUM-peers-correctly-configure-the-client-time.patch33
-rw-r--r--net/haproxy/patches/0005-BUG-MEDIUM-buffer-one-byte-miss-in-buffer-free-space.patch30
-rw-r--r--net/haproxy/patches/0006-BUG-MAJOR-http-don-t-read-past-buffer-s-end-in-http_.patch52
-rw-r--r--net/haproxy/patches/0007-BUG-MEDIUM-http-the-function-req-res-replace-value-d.patch171
-rw-r--r--net/haproxy/patches/0008-BUG-MINOR-compression-consider-the-expansion-factor-.patch43
-rw-r--r--net/haproxy/patches/0009-BUG-MEDIUM-http-hdr_cnt-would-not-count-any-header-w.patch70
8 files changed, 443 insertions, 1 deletions
diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile
index 38ce8fd5a..7ed23696d 100644
--- a/net/haproxy/Makefile
+++ b/net/haproxy/Makefile
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=haproxy
PKG_VERSION:=1.5.11
-PKG_RELEASE:=02
+PKG_RELEASE:=09
PKG_SOURCE:=haproxy-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=http://haproxy.1wt.eu/download/1.5/src/
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_NAME)-$(PKG_VERSION)
diff --git a/net/haproxy/patches/0003-BUG-MEDIUM-Do-not-consider-an-agent-check-as-failed-.patch b/net/haproxy/patches/0003-BUG-MEDIUM-Do-not-consider-an-agent-check-as-failed-.patch
new file mode 100644
index 000000000..1f257efb0
--- /dev/null
+++ b/net/haproxy/patches/0003-BUG-MEDIUM-Do-not-consider-an-agent-check-as-failed-.patch
@@ -0,0 +1,43 @@
+From bfb8f885955efa1ef90f79595f16a01e30fd0dcf Mon Sep 17 00:00:00 2001
+From: Simon Horman <horms@verge.net.au>
+Date: Thu, 26 Feb 2015 11:26:17 +0900
+Subject: [PATCH 3/9] BUG/MEDIUM: Do not consider an agent check as failed on
+ L7 error
+
+As failure to connect to the agent check is not sufficient to mark it as
+failed it stands to reason that an L7 error shouldn't either.
+
+Without this fix if an L7 error occurs, for example of connectivity to the
+agent is lost immediately after establishing a connection to it, then the
+agent check will be considered to have failed and thus may end up with zero
+health. Once this has occurred if the primary health check also reaches
+zero health, which is likely if connectivity to the server is lost, then
+the server will be marked as down and not be marked as up again until a
+successful agent check occurs regardless of the success of any primary
+health checks.
+
+This behaviour is not correct as a failed agent check should never cause a
+server to be marked as down or by extension continue to be marked as down.
+
+Signed-off-by: Simon Horman <horms@verge.net.au>
+(cherry picked from commit eaabd52e29a29187f9829fe727028a6ca530cbf9)
+---
+ src/checks.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/checks.c b/src/checks.c
+index b9048da..71debb6 100644
+--- a/src/checks.c
++++ b/src/checks.c
+@@ -246,7 +246,7 @@ static void set_server_check_status(struct check *check, short status, const cha
+ * cause the server to be marked down.
+ */
+ if ((!(check->state & CHK_ST_AGENT) ||
+- (check->status >= HCHK_STATUS_L7TOUT)) &&
++ (check->status >= HCHK_STATUS_L57DATA)) &&
+ (check->health >= check->rise)) {
+ s->counters.failed_checks++;
+ report = 1;
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0004-BUG-MEDIUM-peers-correctly-configure-the-client-time.patch b/net/haproxy/patches/0004-BUG-MEDIUM-peers-correctly-configure-the-client-time.patch
new file mode 100644
index 000000000..8d33bb56d
--- /dev/null
+++ b/net/haproxy/patches/0004-BUG-MEDIUM-peers-correctly-configure-the-client-time.patch
@@ -0,0 +1,33 @@
+From fc940eb2bf0bbd7adf5b283f28bcff136501ae7f Mon Sep 17 00:00:00 2001
+From: Willy Tarreau <w@1wt.eu>
+Date: Fri, 13 Mar 2015 16:18:25 +0100
+Subject: [PATCH 4/9] BUG/MEDIUM: peers: correctly configure the client timeout
+
+The peers frontend timeout was mistakenly set on timeout.connect instead
+of timeout.client, resulting in no timeout being applied to the peers
+connections. The impact is just that peers can establish connections and
+remain connected until they speak. Once they start speaking, only one of
+them will still be accepted, and old sessions will be killed, so the
+problem is limited. This fix should however be backported to 1.5 since
+it was introduced in 1.5-dev3 with peers.
+(cherry picked from commit 9ff95bb18c4cd9ae747fa5b3bef6d3f94e54172f)
+---
+ src/cfgparse.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/cfgparse.c b/src/cfgparse.c
+index a91e027..b7613b8 100644
+--- a/src/cfgparse.c
++++ b/src/cfgparse.c
+@@ -1834,7 +1834,7 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
+ curpeers->peers_fe->cap = PR_CAP_FE;
+ curpeers->peers_fe->maxconn = 0;
+ curpeers->peers_fe->conn_retries = CONN_RETRIES;
+- curpeers->peers_fe->timeout.connect = 5000;
++ curpeers->peers_fe->timeout.client = MS_TO_TICKS(5000);
+ curpeers->peers_fe->accept = peer_accept;
+ curpeers->peers_fe->options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
+ curpeers->peers_fe->conf.args.file = curpeers->peers_fe->conf.file = strdup(file);
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0005-BUG-MEDIUM-buffer-one-byte-miss-in-buffer-free-space.patch b/net/haproxy/patches/0005-BUG-MEDIUM-buffer-one-byte-miss-in-buffer-free-space.patch
new file mode 100644
index 000000000..bdc674c0a
--- /dev/null
+++ b/net/haproxy/patches/0005-BUG-MEDIUM-buffer-one-byte-miss-in-buffer-free-space.patch
@@ -0,0 +1,30 @@
+From b92902814f796bb1dc24bab2179000caceb5b151 Mon Sep 17 00:00:00 2001
+From: Thierry FOURNIER <tfournier@exceliance.fr>
+Date: Tue, 10 Mar 2015 01:55:01 +0100
+Subject: [PATCH 5/9] BUG/MEDIUM: buffer: one byte miss in buffer free space
+ check
+
+Space is not avalaible only if the end of the data inserted
+is strictly greater than the end of buffer. If these two value
+are equal, the space is avamaible.
+(cherry picked from commit fdda6777bffb4f933569c609ba54e24ea5eabf29)
+---
+ src/buffer.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/buffer.c b/src/buffer.c
+index 9037dd3..8d2644e 100644
+--- a/src/buffer.c
++++ b/src/buffer.c
+@@ -46,7 +46,7 @@ int buffer_replace2(struct buffer *b, char *pos, char *end, const char *str, int
+
+ delta = len - (end - pos);
+
+- if (bi_end(b) + delta >= b->data + b->size)
++ if (bi_end(b) + delta > b->data + b->size)
+ return 0; /* no space left */
+
+ if (buffer_not_empty(b) &&
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0006-BUG-MAJOR-http-don-t-read-past-buffer-s-end-in-http_.patch b/net/haproxy/patches/0006-BUG-MAJOR-http-don-t-read-past-buffer-s-end-in-http_.patch
new file mode 100644
index 000000000..66e0c69ef
--- /dev/null
+++ b/net/haproxy/patches/0006-BUG-MAJOR-http-don-t-read-past-buffer-s-end-in-http_.patch
@@ -0,0 +1,52 @@
+From 8e05ac2044c6523c867ceaaae1f10486370eec89 Mon Sep 17 00:00:00 2001
+From: Thierry FOURNIER <tfournier@haproxy.com>
+Date: Mon, 16 Mar 2015 11:14:41 +0100
+Subject: [PATCH 6/9] BUG/MAJOR: http: don't read past buffer's end in
+ http_replace_value
+
+The function http_replace_value use bad variable to detect the end
+of the input string.
+
+Regression introduced by the patch "MEDIUM: regex: Remove null
+terminated strings." (c9c2daf2)
+
+We need to backport this patch int the 1.5 stable branch.
+
+WT: there is no possibility to overwrite existing data as we only read
+ past the end of the request buffer, to copy into the trash. The copy
+ is bounded by buffer_replace2(), just like the replacement performed
+ by exp_replace(). However if a buffer happens to contain non-zero data
+ up to the next unmapped page boundary, there's a theorical risk of
+ crashing the process despite this not being reproducible in tests.
+ The risk is low because "http-request replace-value" did not work due
+ to this bug so that probably means it's not used yet.
+(cherry picked from commit 534101658d6e19aeb598bf7833a8ce167498c4ed)
+---
+ src/proto_http.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/proto_http.c b/src/proto_http.c
+index 705f3b4..f53b5e2 100644
+--- a/src/proto_http.c
++++ b/src/proto_http.c
+@@ -3206,7 +3206,7 @@ static int http_replace_value(struct my_regex *re, char *dst, uint dst_size, cha
+
+ /* look for delim. */
+ p_delim = p;
+- while (p_delim < p + len && *p_delim != delim)
++ while (p_delim < val + len && *p_delim != delim)
+ p_delim++;
+
+ if (regex_exec_match2(re, p, p_delim-p, MAX_MATCH, pmatch)) {
+@@ -3230,7 +3230,7 @@ static int http_replace_value(struct my_regex *re, char *dst, uint dst_size, cha
+ return -1;
+
+ /* end of the replacements. */
+- if (p_delim >= p + len)
++ if (p_delim >= val + len)
+ break;
+
+ /* Next part. */
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0007-BUG-MEDIUM-http-the-function-req-res-replace-value-d.patch b/net/haproxy/patches/0007-BUG-MEDIUM-http-the-function-req-res-replace-value-d.patch
new file mode 100644
index 000000000..5d4dbc0af
--- /dev/null
+++ b/net/haproxy/patches/0007-BUG-MEDIUM-http-the-function-req-res-replace-value-d.patch
@@ -0,0 +1,171 @@
+From 06170c50ae5cd0fb23510b832826f7e63a5a8894 Mon Sep 17 00:00:00 2001
+From: Thierry FOURNIER <tfournier@haproxy.com>
+Date: Mon, 16 Mar 2015 23:23:53 +0100
+Subject: [PATCH 7/9] BUG/MEDIUM: http: the function "(req|res)-replace-value"
+ doesn't respect the HTTP syntax
+
+These function used an invalid header parser.
+ - The trailing white-spaces were embedded in the replacement regex,
+ - The double-quote (") containing comma (,) were not respected.
+
+This patch replace this parser by the "official" parser http_find_header2().
+(cherry picked from commit 191f9efdc58f21af1d9dde3db5ba198d7f1ce22e)
+---
+ src/proto_http.c | 126 +++++++++++++++----------------------------------------
+ 1 file changed, 34 insertions(+), 92 deletions(-)
+
+diff --git a/src/proto_http.c b/src/proto_http.c
+index f53b5e2..c49c4f4 100644
+--- a/src/proto_http.c
++++ b/src/proto_http.c
+@@ -3179,113 +3179,55 @@ static inline void inet_set_tos(int fd, struct sockaddr_storage from, int tos)
+ #endif
+ }
+
+-/* Returns the number of characters written to destination,
+- * -1 on internal error and -2 if no replacement took place.
+- */
+-static int http_replace_header(struct my_regex *re, char *dst, uint dst_size, char *val, int len,
+- const char *rep_str)
+-{
+- if (!regex_exec_match2(re, val, len, MAX_MATCH, pmatch))
+- return -2;
+-
+- return exp_replace(dst, dst_size, val, rep_str, pmatch);
+-}
+-
+-/* Returns the number of characters written to destination,
+- * -1 on internal error and -2 if no replacement took place.
+- */
+-static int http_replace_value(struct my_regex *re, char *dst, uint dst_size, char *val, int len, char delim,
+- const char *rep_str)
+-{
+- char* p = val;
+- char* dst_end = dst + dst_size;
+- char* dst_p = dst;
+-
+- for (;;) {
+- char *p_delim;
+-
+- /* look for delim. */
+- p_delim = p;
+- while (p_delim < val + len && *p_delim != delim)
+- p_delim++;
+-
+- if (regex_exec_match2(re, p, p_delim-p, MAX_MATCH, pmatch)) {
+- int replace_n = exp_replace(dst_p, dst_end - dst_p, p, rep_str, pmatch);
+-
+- if (replace_n < 0)
+- return -1;
+-
+- dst_p += replace_n;
+- } else {
+- uint len = p_delim - p;
+-
+- if (dst_p + len >= dst_end)
+- return -1;
+-
+- memcpy(dst_p, p, len);
+- dst_p += len;
+- }
+-
+- if (dst_p >= dst_end)
+- return -1;
+-
+- /* end of the replacements. */
+- if (p_delim >= val + len)
+- break;
+-
+- /* Next part. */
+- *dst_p++ = delim;
+- p = p_delim + 1;
+- }
+-
+- return dst_p - dst;
+-}
+-
+ static int http_transform_header(struct session* s, struct http_msg *msg, const char* name, uint name_len,
+ char* buf, struct hdr_idx* idx, struct list *fmt, struct my_regex *re,
+ struct hdr_ctx* ctx, int action)
+ {
++ int (*http_find_hdr_func)(const char *name, int len, char *sol,
++ struct hdr_idx *idx, struct hdr_ctx *ctx);
++ struct chunk *replace = get_trash_chunk();
++ struct chunk *output = get_trash_chunk();
++
++ replace->len = build_logline(s, replace->str, replace->size, fmt);
++ if (replace->len >= replace->size - 1)
++ return -1;
++
+ ctx->idx = 0;
+
+- while (http_find_full_header2(name, name_len, buf, idx, ctx)) {
++ /* Choose the header browsing function. */
++ switch (action) {
++ case HTTP_REQ_ACT_REPLACE_VAL:
++ case HTTP_RES_ACT_REPLACE_VAL:
++ http_find_hdr_func = http_find_header2;
++ break;
++ case HTTP_REQ_ACT_REPLACE_HDR:
++ case HTTP_RES_ACT_REPLACE_HDR:
++ http_find_hdr_func = http_find_full_header2;
++ break;
++ default: /* impossible */
++ return -1;
++ }
++
++ while (http_find_hdr_func(name, name_len, buf, idx, ctx)) {
+ struct hdr_idx_elem *hdr = idx->v + ctx->idx;
+ int delta;
+- char* val = (char*)ctx->line + ctx->val;
+- char* val_end = (char*)ctx->line + hdr->len;
+- char* reg_dst_buf;
+- uint reg_dst_buf_size;
+- int n_replaced;
+-
+- trash.len = build_logline(s, trash.str, trash.size, fmt);
+-
+- if (trash.len >= trash.size - 1)
+- return -1;
++ char *val = ctx->line + ctx->val;
++ char* val_end = val + ctx->vlen;
+
+- reg_dst_buf = trash.str + trash.len + 1;
+- reg_dst_buf_size = trash.size - trash.len - 1;
++ if (!regex_exec_match2(re, val, val_end-val, MAX_MATCH, pmatch))
++ continue;
+
+- switch (action) {
+- case HTTP_REQ_ACT_REPLACE_VAL:
+- case HTTP_RES_ACT_REPLACE_VAL:
+- n_replaced = http_replace_value(re, reg_dst_buf, reg_dst_buf_size, val, val_end-val, ',', trash.str);
+- break;
+- case HTTP_REQ_ACT_REPLACE_HDR:
+- case HTTP_RES_ACT_REPLACE_HDR:
+- n_replaced = http_replace_header(re, reg_dst_buf, reg_dst_buf_size, val, val_end-val, trash.str);
+- break;
+- default: /* impossible */
++ output->len = exp_replace(output->str, output->size, val, replace->str, pmatch);
++ if (output->len == -1)
+ return -1;
+- }
+
+- switch (n_replaced) {
+- case -1: return -1;
+- case -2: continue;
+- }
+-
+- delta = buffer_replace2(msg->chn->buf, val, val_end, reg_dst_buf, n_replaced);
++ delta = buffer_replace2(msg->chn->buf, val, val_end, output->str, output->len);
+
+ hdr->len += delta;
+ http_msg_move_end(msg, delta);
++
++ /* Adjust the length of the current value of the index. */
++ ctx->vlen += delta;
+ }
+
+ return 0;
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0008-BUG-MINOR-compression-consider-the-expansion-factor-.patch b/net/haproxy/patches/0008-BUG-MINOR-compression-consider-the-expansion-factor-.patch
new file mode 100644
index 000000000..b8d01f69a
--- /dev/null
+++ b/net/haproxy/patches/0008-BUG-MINOR-compression-consider-the-expansion-factor-.patch
@@ -0,0 +1,43 @@
+From 9b9531d90dfd8a334958d23394afafd0185bfa21 Mon Sep 17 00:00:00 2001
+From: Willy Tarreau <w@1wt.eu>
+Date: Sat, 28 Mar 2015 12:20:33 +0100
+Subject: [PATCH 8/9] BUG/MINOR: compression: consider the expansion factor in
+ init
+
+When checking if the buffer is large enough, we used to rely on a fixed
+size that was "apparently" enough. We need to consider the expansion
+factor of deflate-encoded streams instead, which is of 5 bytes per 32kB.
+The previous value was OK till 128kB buffers but became wrong past that.
+It's totally harmless since we always keep the reserve when compressiong,
+so there's 1kB or so available, which is enough for buffers as large as
+6.5 MB, but better fix the check anyway.
+
+This fix could be backported into 1.5 since compression was added there.
+(cherry picked from commit 2aee2215c908c6997addcd1714b5b10f73c0703d)
+---
+ src/compression.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/src/compression.c b/src/compression.c
+index 3d6085e..d55f14e 100644
+--- a/src/compression.c
++++ b/src/compression.c
+@@ -130,9 +130,12 @@ int http_compression_buffer_init(struct session *s, struct buffer *in, struct bu
+ {
+ int left;
+
+- /* not enough space */
+- if (in->size - buffer_len(in) < 40)
+- return -1;
++ /* output stream requires at least 10 bytes for the gzip header, plus
++ * at least 8 bytes for the gzip trailer (crc+len), plus a possible
++ * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
++ */
++ if (in->size - buffer_len(in) < 20 + 5 * ((in->i + 32767) >> 15))
++ return -1;
+
+ /* We start by copying the current buffer's pending outgoing data into
+ * a new temporary buffer that we initialize with a new empty chunk.
+--
+2.0.5
+
diff --git a/net/haproxy/patches/0009-BUG-MEDIUM-http-hdr_cnt-would-not-count-any-header-w.patch b/net/haproxy/patches/0009-BUG-MEDIUM-http-hdr_cnt-would-not-count-any-header-w.patch
new file mode 100644
index 000000000..cd258da5e
--- /dev/null
+++ b/net/haproxy/patches/0009-BUG-MEDIUM-http-hdr_cnt-would-not-count-any-header-w.patch
@@ -0,0 +1,70 @@
+From 2943734024525d4b9aeec13cca2c1d230c358ee5 Mon Sep 17 00:00:00 2001
+From: Willy Tarreau <w@1wt.eu>
+Date: Wed, 1 Apr 2015 19:16:09 +0200
+Subject: [PATCH 9/9] BUG/MEDIUM: http: hdr_cnt would not count any header when
+ called without name
+
+It's documented that these sample fetch functions should count all headers
+and/or all values when called with no name but in practice it's not what is
+being done as a missing name causes an immediate return and an absence of
+result.
+
+This bug is present in 1.5 as well and must be backported.
+(cherry picked from commit 601a4d1741100d7a861b6d9b66561335c9911277)
+---
+ src/proto_http.c | 20 ++++++++++++++------
+ 1 file changed, 14 insertions(+), 6 deletions(-)
+
+diff --git a/src/proto_http.c b/src/proto_http.c
+index c49c4f4..ccd52ad 100644
+--- a/src/proto_http.c
++++ b/src/proto_http.c
+@@ -10014,15 +10014,19 @@ smp_fetch_fhdr_cnt(struct proxy *px, struct session *l4, void *l7, unsigned int
+ struct hdr_ctx ctx;
+ const struct http_msg *msg = ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &txn->req : &txn->rsp;
+ int cnt;
++ const char *name = NULL;
++ int len = 0;
+
+- if (!args || args->type != ARGT_STR)
+- return 0;
++ if (args && args->type == ARGT_STR) {
++ name = args->data.str.str;
++ len = args->data.str.len;
++ }
+
+ CHECK_HTTP_MESSAGE_FIRST();
+
+ ctx.idx = 0;
+ cnt = 0;
+- while (http_find_full_header2(args->data.str.str, args->data.str.len, msg->chn->buf->p, idx, &ctx))
++ while (http_find_full_header2(name, len, msg->chn->buf->p, idx, &ctx))
+ cnt++;
+
+ smp->type = SMP_T_UINT;
+@@ -10101,15 +10105,19 @@ smp_fetch_hdr_cnt(struct proxy *px, struct session *l4, void *l7, unsigned int o
+ struct hdr_ctx ctx;
+ const struct http_msg *msg = ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &txn->req : &txn->rsp;
+ int cnt;
++ const char *name = NULL;
++ int len = 0;
+
+- if (!args || args->type != ARGT_STR)
+- return 0;
++ if (args && args->type == ARGT_STR) {
++ name = args->data.str.str;
++ len = args->data.str.len;
++ }
+
+ CHECK_HTTP_MESSAGE_FIRST();
+
+ ctx.idx = 0;
+ cnt = 0;
+- while (http_find_header2(args->data.str.str, args->data.str.len, msg->chn->buf->p, idx, &ctx))
++ while (http_find_header2(name, len, msg->chn->buf->p, idx, &ctx))
+ cnt++;
+
+ smp->type = SMP_T_UINT;
+--
+2.0.5
+