diff --git a/sys/include/net/ng_ipv6/addr.h b/sys/include/net/ng_ipv6/addr.h index be7114dbb3f8ab366ecaeff96570ab7822c9c5a0..9331f0c1bb150ffd39973697a1a92bc8b7c623ec 100644 --- a/sys/include/net/ng_ipv6/addr.h +++ b/sys/include/net/ng_ipv6/addr.h @@ -38,7 +38,13 @@ extern "C" { /** * @brief Length of an IPv6 address in bit. */ -#define NG_IPV6_ADDR_BIT_LEN (128) +#define NG_IPV6_ADDR_BIT_LEN (128) + +/** + * @brief Maximum length of an IPv6 address as string. + */ +#define NG_IPV6_ADDR_MAX_STR_LEN (sizeof("ffff:ffff:ffff:ffff:" \ + "ffff:ffff:ffff:ffff")) /** * @brief Data type to represent an IPv6 address. @@ -496,6 +502,40 @@ static inline void ng_ipv6_addr_set_solicited_nodes(ng_ipv6_addr_t *out, out->u16[7] = in->u16[7]; } +/** + * @brief Converts an IPv6 address to its string representation + * + * @see <a href="https://tools.ietf.org/html/rfc5952"> + * RFC 5952 + * </a> + * + * @param[out] result The resulting string representation + * @param[in] addr An IPv6 address + * @param[in] result_len Length of @p result_len + * + * @return @p result, on success + * @return NULL, if @p result_len was smaller than needed + * @return NULL, if @p result or @p addr was NULL + */ +char *ng_ipv6_addr_to_str(char *result, const ng_ipv6_addr_t *addr, + uint8_t result_len); + +/** + * @brief Converts an IPv6 address string representation to a byte-represented + * IPv6 address + * + * @see <a href="https://tools.ietf.org/html/rfc5952"> + * RFC 5952 + * </a> + * + * @param[in] result The resulting byte representation + * @param[in] addr An IPv6 address string representation + * + * @return @p result, on success + * @return NULL, if @p addr was malformed + * @return NULL, if @p result or @p addr was NULL + */ +ng_ipv6_addr_t *ng_ipv6_addr_from_str(ng_ipv6_addr_t *result, const char *addr); #ifdef __cplusplus } diff --git a/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_from_str.c b/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_from_str.c new file mode 100644 index 0000000000000000000000000000000000000000..2f75236b1ebccf5c0d6c89c6248fd8d8581a8ef9 --- /dev/null +++ b/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_from_str.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 1996 by Internet Software Consortium. + * Copyright (c) 2015 by Martine Lenders <mlenders@inf.fu-berlin.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/** + * @ingroup net_ipv6_addr + * @{ + * + * @file + * + * @author Paul Vixie + * @author Martine Lenders <mlenders@inf.fu-berlin.de> + */ + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include "byteorder.h" +#include "net/ng_ipv6/addr.h" + +#define DEC "0123456789" +#define HEX_L "0123456789abcdef" +#define HEX_U "0123456789ABCDEF" + +/* XXX: move this to IPv4 when we have it */ +/* based on inet_pton4() by Paul Vixie */ +static network_uint32_t *ipv4_addr_from_str(network_uint32_t *result, + const char *addr) +{ + uint8_t saw_digit, octets, ch; + uint8_t tmp[sizeof(network_uint32_t)], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + + while ((ch = *addr++) != '\0') { + const char *pch; + + if ((pch = strchr(DEC, ch)) != NULL) { + uint16_t new = *tp * 10 + (uint16_t)(pch - DEC); + + if (new > 255) { + return NULL; + } + + *tp = new; + + if (!saw_digit) { + if (++octets > 4) { + return NULL; + } + + saw_digit = 1; + } + } + else if (ch == '.' && saw_digit) { + if (octets == 4) { + return NULL; + } + + *++tp = 0; + saw_digit = 0; + } + else { + return NULL; + } + } + + if (octets < 4) { + return NULL; + } + + memcpy(result, tmp, sizeof(network_uint32_t)); + return result; +} + +/* based on inet_pton6() by Paul Vixie */ +ng_ipv6_addr_t *ng_ipv6_addr_from_str(ng_ipv6_addr_t *result, const char *addr) +{ + uint8_t *colonp = 0; + const char *curtok = addr; + uint32_t val = 0; + char ch; + uint8_t saw_xdigit = 0; + uint8_t i = 0; + + if ((result == NULL) || (addr == NULL)) { + return NULL; + } + + ng_ipv6_addr_set_unspecified(result); + + /* Leading :: requires some special handling. */ + if (*addr == ':') { + if (*++addr != ':') { + return NULL; + } + } + + while ((ch = *addr++) != '\0') { + const char *pch; + const char *xdigits; + + if ((pch = strchr((xdigits = HEX_L), ch)) == NULL) { + pch = strchr((xdigits = HEX_U), ch); + } + + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + + if (val > 0xffff) { + return NULL; + } + + saw_xdigit = 1; + continue; + } + + if (ch == ':') { + curtok = addr; + + if (!saw_xdigit) { + if (colonp != NULL) { + return NULL; + } + + colonp = &(result->u8[i]); + continue; + } + + if (i > sizeof(ng_ipv6_addr_t)) { + return NULL; + } + + result->u8[i++] = (uint8_t)(val >> 8) & 0xff; + result->u8[i++] = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + + if (ch == '.' && (i <= sizeof(ng_ipv6_addr_t)) && + ipv4_addr_from_str((network_uint32_t *)(&(result->u8[i])), + curtok) != NULL) { + i += sizeof(network_uint32_t); + saw_xdigit = 0; + break; /* '\0' was seen by ipv4_addr_from_str(). */ + } + + return NULL; + } + + if (saw_xdigit) { + if (i + sizeof(uint16_t) > sizeof(ng_ipv6_addr_t)) { + return NULL; + } + + result->u8[i++] = (uint8_t)(val >> 8) & 0xff; + result->u8[i++] = (uint8_t) val & 0xff; + } + + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int32_t n = &(result->u8[i++]) - colonp; + + for (int32_t j = 1; j <= n; j++) { + result->u8[sizeof(ng_ipv6_addr_t) - j] = colonp[n - j]; + colonp[n - j] = 0; + } + + i = sizeof(ng_ipv6_addr_t); + } + + if (i != sizeof(ng_ipv6_addr_t)) { + return NULL; + } + + return result; +} + +/** + * @} + */ diff --git a/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_to_str.c b/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_to_str.c new file mode 100644 index 0000000000000000000000000000000000000000..7eaba6c887999be269d3f3ac9cad6f0df496881f --- /dev/null +++ b/sys/net/network_layer/ng_ipv6/addr/ng_ipv6_addr_to_str.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 1996 by Internet Software Consortium. + * Copyright (c) 2015 by Martine Lenders <mlenders@inf.fu-berlin.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/** + * @ingroup net_ipv6_addr + * @{ + * + * @file + * + * @author Paul Vixie + * @author Martine Lenders <mlenders@inf.fu-berlin.de> + */ + +#include <inttypes.h> +#include <string.h> + +#include "byteorder.h" +#include "net/ng_ipv6/addr.h" + +/* Length of an IPv6 address in 16-bit words */ +#define IPV6_ADDR_WORD_LEN (sizeof(ng_ipv6_addr_t) / sizeof(uint16_t)) + +#define IPV4_ADDR_MIN_STR_LEN (sizeof("255.255.255.255")) + +#define HEX_L "0123456789abcdef" + +/* XXX: move this to IPv4 when we have it */ +/* based on inet_ntop4() by Paul Vixie */ +static char *ipv4_addr_to_str(char *result, const network_uint32_t *addr, + uint8_t result_len) +{ + uint8_t n = 0; + char *next = result; + + if (result_len < IPV4_ADDR_MIN_STR_LEN) { + return NULL; + } + + do { + uint8_t u = addr->u8[n]; + + if (u > 99) { + *next++ = '0' + u / 100; + u %= 100; + *next++ = '0' + u / 10; + u %= 10; + } + else if (u > 9) { + *next++ = '0' + u / 10; + u %= 10; + } + + *next++ = '0' + u; + *next++ = '.'; + n++; + } while (n < 4); + + *--next = '\0'; + return result; +} + +/* based on inet_ntop6() by Paul Vixie */ +char *ng_ipv6_addr_to_str(char *result, const ng_ipv6_addr_t *addr, + uint8_t result_len) +{ + char tmp[NG_IPV6_ADDR_MAX_STR_LEN], *tp; + struct { + int16_t base, len; + } best = { -1, 0}, cur = { -1, 0}; + + if ((result == NULL) || (addr == NULL)) { + return NULL; + } + + /* + * Preprocess: + * Find the longest run of 0x0000's in address for :: shorthanding. + */ + for (uint8_t i = 0; i < IPV6_ADDR_WORD_LEN; i++) { + if (addr->u16[i].u16 == 0) { + if (cur.base == -1) { + cur.base = i; + cur.len = 1; + } + else { + cur.len++; + } + } + else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) { + best = cur; + } + + cur.base = -1; + } + } + } + + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) { + best = cur; + } + } + + if (best.base != -1 && best.len < 2) { + best.base = -1; + } + + /* + * Format the result. + */ + tp = tmp; + + for (int i = 0; i < ((int)IPV6_ADDR_WORD_LEN);) { + /* Are we inside the best run of 0x00's? */ + if (i == best.base) { + *tp++ = ':'; + i += best.len; + continue; + } + + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) { + *tp++ = ':'; + } + + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && addr->u16[5].u16 == 0xffff))) { + if (!ipv4_addr_to_str(tp, &addr->u32[3], sizeof(tmp) - (tp - tmp))) { + return (NULL); + } + + tp += strlen(tp); + break; + } + + if ((addr->u16[i].u8[0] & 0xf0) != 0x00) { + *tp++ = HEX_L[addr->u16[i].u8[0] >> 4]; + *tp++ = HEX_L[addr->u16[i].u8[0] & 0x0f]; + *tp++ = HEX_L[addr->u16[i].u8[1] >> 4]; + } + else if ((addr->u16[i].u8[0] & 0x0f) != 0x00) { + *tp++ = HEX_L[addr->u16[i].u8[0] & 0x0f]; + *tp++ = HEX_L[addr->u16[i].u8[1] >> 4]; + } + else if ((addr->u16[i].u8[1] & 0xf0) != 0x00) { + *tp++ = HEX_L[addr->u16[i].u8[1] >> 4]; + } + + *tp++ = HEX_L[addr->u16[i].u8[1] & 0xf]; + + i++; + } + + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == IPV6_ADDR_WORD_LEN) { + *tp++ = ':'; + } + + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > result_len) { + return NULL; + } + + strcpy(result, tmp); + return result; +} + +/** + * @} + */ diff --git a/tests/unittests/tests-ipv6_addr/tests-ipv6_addr.c b/tests/unittests/tests-ipv6_addr/tests-ipv6_addr.c index 03eb6efc80fd97d5d9a1aed2d6124dec4252e991..7345369268b1c477cf197ec01ef1c05120b569c3 100644 --- a/tests/unittests/tests-ipv6_addr/tests-ipv6_addr.c +++ b/tests/unittests/tests-ipv6_addr/tests-ipv6_addr.c @@ -623,6 +623,205 @@ static void test_ipv6_addr_set_solicited_nodes(void) TEST_ASSERT_EQUAL_INT(0xff0d0e0f, byteorder_ntohl(a.u32[3])); } +static void test_ipv6_addr_to_str__string_too_short(void) +{ + ng_ipv6_addr_t a = { { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + } + }; + char result[1]; + + TEST_ASSERT_NULL(ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_to_str__addr_NULL(void) +{ + char result[NG_IPV6_ADDR_MAX_STR_LEN]; + + TEST_ASSERT_NULL(ng_ipv6_addr_to_str(result, NULL, sizeof(result))); +} + +static void test_ipv6_addr_to_str__result_NULL(void) +{ + ng_ipv6_addr_t a; + + TEST_ASSERT_NULL(ng_ipv6_addr_to_str(NULL, &a, NG_IPV6_ADDR_MAX_STR_LEN)); +} + +static void test_ipv6_addr_to_str__success(void) +{ + ng_ipv6_addr_t a = { { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + } + }; + char result[NG_IPV6_ADDR_MAX_STR_LEN]; + + TEST_ASSERT_EQUAL_STRING("1:203:405:607:809:a0b:c0d:e0f", + ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_to_str__success2(void) +{ + ng_ipv6_addr_t a = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + } + }; + char result[NG_IPV6_ADDR_MAX_STR_LEN]; + + TEST_ASSERT_EQUAL_STRING("fe80::f8f9:fafb:fcfd:feff", + ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_to_str__success3(void) +{ + ng_ipv6_addr_t a = NG_IPV6_ADDR_UNSPECIFIED; + char result[sizeof("::")]; + + TEST_ASSERT_EQUAL_STRING("::", ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_to_str__success4(void) +{ + ng_ipv6_addr_t a = NG_IPV6_ADDR_LOOPBACK; + char result[sizeof("::1")]; + + TEST_ASSERT_EQUAL_STRING("::1", ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_to_str__success5(void) +{ + ng_ipv6_addr_t a = { { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 192, 168, 0, 1 + } + }; + char result[NG_IPV6_ADDR_MAX_STR_LEN]; + + TEST_ASSERT_EQUAL_STRING("::ffff:192.168.0.1", + ng_ipv6_addr_to_str(result, &a, sizeof(result))); +} + +static void test_ipv6_addr_from_str__one_colon_start(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, ":ff::1")); +} + +static void test_ipv6_addr_from_str__three_colons(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, "ff02:::1")); +} + +static void test_ipv6_addr_from_str__string_too_long(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, "ffff:ffff:ffff:ffff:" + "ffff:ffff:ffff:ffff:ffff")); +} + +static void test_ipv6_addr_from_str__illegal_chars(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, ":-)")); +} + +static void test_ipv6_addr_from_str__illegal_encapsulated_ipv4(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, "192.168.0.1")); +} + +static void test_ipv6_addr_from_str__addr_NULL(void) +{ + ng_ipv6_addr_t result; + + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(&result, NULL)); +} + +static void test_ipv6_addr_from_str__result_NULL(void) +{ + TEST_ASSERT_NULL(ng_ipv6_addr_from_str(NULL, "::")); +} + +static void test_ipv6_addr_from_str__success(void) +{ + ng_ipv6_addr_t a = { { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + } + }; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "1:203:405:607:809:a0b:c0d:e0f")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + +static void test_ipv6_addr_from_str__success2(void) +{ + ng_ipv6_addr_t a = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + } + }; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "fe80::f8f9:fafb:fcfd:feff")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + +static void test_ipv6_addr_from_str__success3(void) +{ + ng_ipv6_addr_t a = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + } + }; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "FE80::F8F9:FAFB:FCFD:FEFF")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + +static void test_ipv6_addr_from_str__success4(void) +{ + ng_ipv6_addr_t a = NG_IPV6_ADDR_UNSPECIFIED; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "::")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + +static void test_ipv6_addr_from_str__success5(void) +{ + ng_ipv6_addr_t a = NG_IPV6_ADDR_LOOPBACK; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "::1")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + +static void test_ipv6_addr_from_str__success6(void) +{ + ng_ipv6_addr_t a = { { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 192, 168, 0, 1 + } + }; + ng_ipv6_addr_t result; + + TEST_ASSERT_NOT_NULL(ng_ipv6_addr_from_str(&result, "::ffff:192.168.0.1")); + TEST_ASSERT(ng_ipv6_addr_equal(&a, &result)); +} + Test *tests_ipv6_addr_tests(void) { EMB_UNIT_TESTFIXTURES(fixtures) { @@ -673,6 +872,27 @@ Test *tests_ipv6_addr_tests(void) new_TestFixture(test_ipv6_addr_set_all_routers_multicast_site_local), new_TestFixture(test_ipv6_addr_set_all_routers_multicast_unusual), new_TestFixture(test_ipv6_addr_set_solicited_nodes), + new_TestFixture(test_ipv6_addr_to_str__string_too_short), + new_TestFixture(test_ipv6_addr_to_str__addr_NULL), + new_TestFixture(test_ipv6_addr_to_str__result_NULL), + new_TestFixture(test_ipv6_addr_to_str__success), + new_TestFixture(test_ipv6_addr_to_str__success2), + new_TestFixture(test_ipv6_addr_to_str__success3), + new_TestFixture(test_ipv6_addr_to_str__success4), + new_TestFixture(test_ipv6_addr_to_str__success5), + new_TestFixture(test_ipv6_addr_from_str__one_colon_start), + new_TestFixture(test_ipv6_addr_from_str__three_colons), + new_TestFixture(test_ipv6_addr_from_str__string_too_long), + new_TestFixture(test_ipv6_addr_from_str__illegal_chars), + new_TestFixture(test_ipv6_addr_from_str__illegal_encapsulated_ipv4), + new_TestFixture(test_ipv6_addr_from_str__addr_NULL), + new_TestFixture(test_ipv6_addr_from_str__result_NULL), + new_TestFixture(test_ipv6_addr_from_str__success), + new_TestFixture(test_ipv6_addr_from_str__success2), + new_TestFixture(test_ipv6_addr_from_str__success3), + new_TestFixture(test_ipv6_addr_from_str__success4), + new_TestFixture(test_ipv6_addr_from_str__success5), + new_TestFixture(test_ipv6_addr_from_str__success6), }; EMB_UNIT_TESTCALLER(ipv6_addr_tests, NULL, NULL, fixtures);