1 #pragma once 2 #include <fmt/core.h> 3 #include <net/ethernet.h> 4 #include <netinet/in.h> 5 6 #include <algorithm> 7 #include <array> 8 #include <numeric> 9 #include <optional> 10 #include <string> 11 #include <string_view> 12 #include <type_traits> 13 #include <unordered_map> 14 #include <unordered_set> 15 #include <variant> 16 17 constexpr bool operator==(ether_addr lhs, ether_addr rhs) noexcept 18 { 19 return std::equal(lhs.ether_addr_octet, lhs.ether_addr_octet + 6, 20 rhs.ether_addr_octet); 21 } 22 23 constexpr bool operator==(in_addr lhs, in_addr rhs) noexcept 24 { 25 return lhs.s_addr == rhs.s_addr; 26 } 27 28 constexpr bool operator==(in6_addr lhs, in6_addr rhs) noexcept 29 { 30 return std::equal(lhs.s6_addr32, lhs.s6_addr32 + 4, rhs.s6_addr32); 31 } 32 33 namespace phosphor 34 { 35 namespace network 36 { 37 38 // Byte representations for common address types in network byte order 39 using InAddrAny = std::variant<in_addr, in6_addr>; 40 class IfAddr 41 { 42 private: 43 InAddrAny addr; 44 uint8_t pfx; 45 46 static void invalidPfx(uint8_t pfx); 47 48 public: 49 constexpr IfAddr() : addr({}), pfx(0) 50 { 51 } 52 53 constexpr IfAddr(InAddrAny addr, uint8_t pfx) : addr(addr), pfx(pfx) 54 { 55 std::visit( 56 [pfx](auto v) { 57 if (sizeof(v) * 8 < pfx) 58 { 59 invalidPfx(pfx); 60 } 61 }, 62 addr); 63 } 64 65 constexpr auto getAddr() const 66 { 67 return addr; 68 } 69 70 constexpr auto getPfx() const 71 { 72 return pfx; 73 } 74 75 constexpr bool operator==(phosphor::network::IfAddr rhs) const noexcept 76 { 77 return addr == rhs.addr && pfx == rhs.pfx; 78 } 79 }; 80 81 /** @class InterfaceInfo 82 * @brief Information about interfaces from the kernel 83 */ 84 struct InterfaceInfo 85 { 86 unsigned idx; 87 unsigned flags; 88 std::optional<std::string> name = std::nullopt; 89 std::optional<ether_addr> mac = std::nullopt; 90 std::optional<unsigned> mtu = std::nullopt; 91 std::optional<unsigned> parent_idx = std::nullopt; 92 std::optional<std::string> kind = std::nullopt; 93 std::optional<uint16_t> vlan_id = std::nullopt; 94 95 constexpr bool operator==(const InterfaceInfo& rhs) const noexcept 96 { 97 return idx == rhs.idx && flags == rhs.flags && name == rhs.name && 98 mac == rhs.mac && mtu == rhs.mtu && 99 parent_idx == rhs.parent_idx && kind == rhs.kind && 100 vlan_id == rhs.vlan_id; 101 } 102 }; 103 104 /** @class AddressInfo 105 * @brief Information about a addresses from the kernel 106 */ 107 struct AddressInfo 108 { 109 unsigned ifidx; 110 IfAddr ifaddr; 111 uint8_t scope; 112 uint32_t flags; 113 114 constexpr bool operator==(const AddressInfo& rhs) const noexcept 115 { 116 return ifidx == rhs.ifidx && ifaddr == rhs.ifaddr && 117 scope == rhs.scope && flags == rhs.flags; 118 } 119 }; 120 121 /** @class NeighborInfo 122 * @brief Information about a neighbor from the kernel 123 */ 124 struct NeighborInfo 125 { 126 unsigned ifidx; 127 uint16_t state; 128 std::optional<InAddrAny> addr; 129 std::optional<ether_addr> mac; 130 131 constexpr bool operator==(const NeighborInfo& rhs) const noexcept 132 { 133 return ifidx == rhs.ifidx && state == rhs.state && addr == rhs.addr && 134 mac == rhs.mac; 135 } 136 }; 137 138 struct string_hash : public std::hash<std::string_view> 139 { 140 using is_transparent = void; 141 }; 142 template <typename V> 143 using string_umap = 144 std::unordered_map<std::string, V, string_hash, std::equal_to<>>; 145 using string_uset = 146 std::unordered_set<std::string, string_hash, std::equal_to<>>; 147 148 constexpr std::size_t hash_multi() noexcept 149 { 150 return 0; 151 } 152 153 template <typename T, typename... Args> 154 constexpr std::size_t hash_multi(const T& v, const Args&... args) noexcept 155 { 156 const std::size_t seed = hash_multi(args...); 157 return seed ^ (std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2)); 158 } 159 160 namespace detail 161 { 162 163 template <typename T, uint8_t size = sizeof(T)> 164 struct BswapAlign 165 { 166 using type = T; 167 }; 168 169 template <typename T> 170 struct BswapAlign<T, 2> 171 { 172 using type alignas(uint16_t) = T; 173 }; 174 175 template <typename T> 176 struct BswapAlign<T, 4> 177 { 178 using type alignas(uint32_t) = T; 179 }; 180 181 template <typename T> 182 struct BswapAlign<T, 8> 183 { 184 using type alignas(uint64_t) = T; 185 }; 186 187 template <typename T> 188 constexpr T bswapInt(typename BswapAlign<T>::type n) noexcept 189 { 190 static_assert(std::is_trivially_copyable_v<T>); 191 if constexpr (sizeof(T) == 2) 192 { 193 reinterpret_cast<uint16_t&>(n) = 194 __builtin_bswap16(reinterpret_cast<uint16_t&>(n)); 195 } 196 else if constexpr (sizeof(T) == 4) 197 { 198 reinterpret_cast<uint32_t&>(n) = 199 __builtin_bswap32(reinterpret_cast<uint32_t&>(n)); 200 } 201 else if constexpr (sizeof(T) == 8) 202 { 203 reinterpret_cast<uint64_t&>(n) = 204 __builtin_bswap64(reinterpret_cast<uint64_t&>(n)); 205 } 206 else 207 { 208 auto b = reinterpret_cast<std::byte*>(&n); 209 std::reverse(b, b + sizeof(n)); 210 } 211 return n; 212 } 213 214 } // namespace detail 215 216 template <typename T> 217 constexpr T bswap(T n) noexcept 218 { 219 return detail::bswapInt<T>(n); 220 } 221 222 template <typename T> 223 constexpr T hton(T n) noexcept 224 { 225 if constexpr (std::endian::native == std::endian::big) 226 { 227 return n; 228 } 229 else if constexpr (std::endian::native == std::endian::little) 230 { 231 return bswap(n); 232 } 233 else 234 { 235 static_assert(std::is_same_v<T, void>); 236 } 237 } 238 239 template <typename T> 240 constexpr T ntoh(T n) noexcept 241 { 242 return hton(n); 243 } 244 245 namespace detail 246 { 247 inline constexpr auto charLookup = []() { 248 std::array<int8_t, 256> ret; 249 std::fill(ret.begin(), ret.end(), -1); 250 for (int8_t i = 0; i < 10; ++i) 251 { 252 ret[i + '0'] = i; 253 } 254 for (int8_t i = 0; i < 26; ++i) 255 { 256 ret[i + 'A'] = i + 10; 257 ret[i + 'a'] = i + 10; 258 } 259 return ret; 260 }(); 261 inline constexpr auto intLookup = []() { 262 std::array<char, 36> ret; 263 for (int8_t i = 0; i < 10; ++i) 264 { 265 ret[i] = i + '0'; 266 } 267 for (int8_t i = 0; i < 26; ++i) 268 { 269 ret[i + 10] = i + 'a'; 270 } 271 return ret; 272 }(); 273 } // namespace detail 274 275 template <typename T, uint8_t base> 276 struct DecodeInt 277 { 278 static_assert(base > 1 && base <= 36); 279 static_assert(std::is_unsigned_v<T>); 280 281 constexpr T operator()(std::string_view str) const 282 { 283 if (str.empty()) 284 { 285 throw std::invalid_argument("Empty Str"); 286 } 287 constexpr auto max = std::numeric_limits<T>::max(); 288 auto ret = 289 std::accumulate(str.begin(), str.end(), T{}, [&](T r, char c) { 290 auto v = detail::charLookup[c]; 291 if (v < 0 || v >= base) 292 { 293 throw std::invalid_argument("Invalid numeral"); 294 } 295 if constexpr (std::popcount(base) == 1) 296 { 297 constexpr auto shift = std::countr_zero(base); 298 constexpr auto maxshift = max >> shift; 299 if (r > maxshift) 300 { 301 throw std::overflow_error("Integer Decode"); 302 } 303 return (r << shift) | v; 304 } 305 else 306 { 307 constexpr auto maxbase = max / base; 308 if (r > maxbase) 309 { 310 throw std::overflow_error("Integer Decode"); 311 } 312 r *= base; 313 if (max - v < r) 314 { 315 throw std::overflow_error("Integer Decode"); 316 } 317 return r + v; 318 } 319 }); 320 return ret; 321 } 322 }; 323 324 template <typename T, uint8_t base> 325 struct EncodeInt 326 { 327 static_assert(base > 1 && base <= 36); 328 static_assert(std::is_unsigned_v<T>); 329 330 static constexpr uint8_t buf_size = []() { 331 T v = std::numeric_limits<T>::max(); 332 uint8_t i = 0; 333 for (; v != 0; ++i) 334 { 335 v /= base; 336 } 337 return i; 338 }(); 339 using buf_type = std::array<char, buf_size>; 340 341 constexpr uint8_t reverseFill(char* buf, T v) const noexcept 342 { 343 uint8_t i = 0; 344 do 345 { 346 if constexpr (std::popcount(base) == 1) 347 { 348 buf[i++] = detail::intLookup[v & 0xf]; 349 v >>= 4; 350 } 351 else 352 { 353 buf[i++] = detail::intLookup[v % base]; 354 v /= base; 355 } 356 } while (v > 0); 357 return i; 358 } 359 360 constexpr char* operator()(char* buf, T v) const noexcept 361 { 362 uint8_t i = reverseFill(buf, v); 363 std::reverse(buf, buf + i); 364 return buf + i; 365 } 366 367 constexpr char* operator()(char* buf, T v, uint8_t min_width) const noexcept 368 { 369 uint8_t i = reverseFill(buf, v); 370 auto end = buf + std::max(i, min_width); 371 std::fill(buf + i, end, '0'); 372 std::reverse(buf, end); 373 return end; 374 } 375 }; 376 377 template <typename T> 378 struct ToAddr 379 { 380 }; 381 382 template <> 383 struct ToAddr<ether_addr> 384 { 385 constexpr ether_addr operator()(std::string_view str) const 386 { 387 constexpr DecodeInt<uint8_t, 16> di; 388 ether_addr ret; 389 if (str.size() == 12 && str.find(":") == str.npos) 390 { 391 for (size_t i = 0; i < 6; ++i) 392 { 393 ret.ether_addr_octet[i] = di(str.substr(i * 2, 2)); 394 } 395 } 396 else 397 { 398 for (size_t i = 0; i < 5; ++i) 399 { 400 auto loc = str.find(":"); 401 ret.ether_addr_octet[i] = di(str.substr(0, loc)); 402 str.remove_prefix(loc == str.npos ? str.size() : loc + 1); 403 if (str.empty()) 404 { 405 throw std::invalid_argument("Missing mac data"); 406 } 407 } 408 ret.ether_addr_octet[5] = di(str); 409 } 410 return ret; 411 } 412 }; 413 414 template <> 415 struct ToAddr<in_addr> 416 { 417 constexpr in_addr operator()(std::string_view str) const 418 { 419 constexpr DecodeInt<uint8_t, 10> di; 420 uint32_t addr = {}; 421 for (size_t i = 0; i < 3; ++i) 422 { 423 auto loc = str.find("."); 424 addr |= di(str.substr(0, loc)); 425 addr <<= 8; 426 str.remove_prefix(loc == str.npos ? str.size() : loc + 1); 427 if (str.empty()) 428 { 429 throw std::invalid_argument("Missing addr data"); 430 } 431 } 432 addr |= di(str); 433 return {hton(addr)}; 434 } 435 }; 436 437 template <> 438 struct ToAddr<in6_addr> 439 { 440 constexpr in6_addr operator()(std::string_view str) const 441 { 442 constexpr DecodeInt<uint16_t, 16> di; 443 in6_addr ret = {}; 444 size_t i = 0; 445 while (i < 8) 446 { 447 auto loc = str.find(':'); 448 if (i == 6 && loc == str.npos) 449 { 450 ret.s6_addr32[3] = ToAddr<in_addr>{}(str).s_addr; 451 return ret; 452 } 453 if (loc != 0 && !str.empty()) 454 { 455 ret.s6_addr16[i++] = hton(di(str.substr(0, loc))); 456 } 457 if (i < 8 && str.size() > loc + 1 && str[loc + 1] == ':') 458 { 459 str.remove_prefix(loc + 2); 460 break; 461 } 462 else if (str.empty()) 463 { 464 throw std::invalid_argument("IPv6 Data"); 465 } 466 str.remove_prefix(loc == str.npos ? str.size() : loc + 1); 467 } 468 if (str.starts_with(':')) 469 { 470 throw std::invalid_argument("Extra separator"); 471 } 472 size_t j = 7; 473 if (!str.empty() && i < 6 && str.find('.') != str.npos) 474 { 475 auto loc = str.rfind(':'); 476 ret.s6_addr32[3] = 477 ToAddr<in_addr>{}(str.substr(loc == str.npos ? 0 : loc + 1)) 478 .s_addr; 479 str.remove_suffix(loc == str.npos ? str.size() : str.size() - loc); 480 j -= 2; 481 } 482 while (!str.empty() && j > i) 483 { 484 auto loc = str.rfind(':'); 485 ret.s6_addr16[j--] = 486 hton(di(str.substr(loc == str.npos ? 0 : loc + 1))); 487 str.remove_suffix(loc == str.npos ? str.size() : str.size() - loc); 488 } 489 if (!str.empty()) 490 { 491 throw std::invalid_argument("Too much data"); 492 } 493 return ret; 494 } 495 }; 496 497 template <> 498 struct ToAddr<InAddrAny> 499 { 500 constexpr InAddrAny operator()(std::string_view str) const 501 { 502 if (str.find(':') == str.npos) 503 { 504 return ToAddr<in_addr>{}(str); 505 } 506 return ToAddr<in6_addr>{}(str); 507 } 508 }; 509 510 template <> 511 struct ToAddr<IfAddr> 512 { 513 constexpr IfAddr operator()(std::string_view str) const 514 { 515 auto pos = str.rfind('/'); 516 if (pos == str.npos) 517 { 518 throw std::invalid_argument("Invalid IfAddr"); 519 } 520 return {ToAddr<InAddrAny>{}(str.substr(0, pos)), 521 DecodeInt<uint8_t, 10>{}(str.substr(pos + 1))}; 522 } 523 }; 524 525 template <typename T> 526 struct ToStr 527 { 528 }; 529 530 template <> 531 struct ToStr<char> 532 { 533 static constexpr uint8_t buf_size = 1; 534 using buf_type = std::array<char, buf_size>; 535 536 constexpr char* operator()(char* buf, char v) const noexcept 537 { 538 buf[0] = v; 539 return buf + 1; 540 } 541 }; 542 543 template <> 544 struct ToStr<ether_addr> 545 { 546 // 6 octets * 2 hex chars + 5 separators 547 static constexpr uint8_t buf_size = 17; 548 using buf_type = std::array<char, buf_size>; 549 550 constexpr char* operator()(char* buf, ether_addr v) const noexcept 551 { 552 for (char* ptr = buf + 2; ptr < buf + buf_size; ptr += 3) 553 { 554 *ptr = ':'; 555 } 556 for (size_t i = 0; i < 6; ++i) 557 { 558 char* tmp = buf + i * 3; 559 uint8_t byte = v.ether_addr_octet[i]; 560 EncodeInt<uint8_t, 16>{}(tmp, byte, 2); 561 } 562 return buf + buf_size; 563 } 564 }; 565 566 template <> 567 struct ToStr<in_addr> 568 { 569 // 4 octets * 3 dec chars + 3 separators 570 static constexpr uint8_t buf_size = 15; 571 using buf_type = std::array<char, buf_size>; 572 573 constexpr char* operator()(char* buf, in_addr v) const noexcept 574 { 575 auto n = bswap(ntoh(v.s_addr)); 576 for (size_t i = 0; i < 3; ++i) 577 { 578 buf = ToStr<char>{}(EncodeInt<uint8_t, 10>{}(buf, n & 0xff), '.'); 579 n >>= 8; 580 } 581 return EncodeInt<uint8_t, 10>{}(buf, n & 0xff); 582 } 583 }; 584 585 template <> 586 struct ToStr<in6_addr> 587 { 588 // 8 hextets * 4 hex chars + 7 separators 589 static constexpr uint8_t buf_size = 39; 590 using buf_type = std::array<char, buf_size>; 591 592 constexpr char* operator()(char* buf, in6_addr v) const noexcept 593 { 594 // IPv4 in IPv6 Addr 595 if (v.s6_addr32[0] == 0 && v.s6_addr32[1] == 0 && 596 v.s6_addr32[2] == hton(uint32_t(0xffff))) 597 { 598 constexpr auto prefix = std::string_view("::ffff:"); 599 return ToStr<in_addr>{}( 600 std::copy(prefix.begin(), prefix.end(), buf), {v.s6_addr32[3]}); 601 } 602 603 size_t skip_start = 0; 604 size_t skip_size = 0; 605 { 606 size_t new_start = 0; 607 size_t new_size = 0; 608 for (size_t i = 0; i < 9; ++i) 609 { 610 if (i < 8 && v.s6_addr16[i] == 0) 611 { 612 if (new_start + new_size == i) 613 { 614 new_size++; 615 } 616 else 617 { 618 new_start = i; 619 new_size = 1; 620 } 621 } 622 else if (new_start + new_size == i && new_size > skip_size) 623 { 624 skip_start = new_start; 625 skip_size = new_size; 626 } 627 } 628 } 629 for (size_t i = 0; i < 8; ++i) 630 { 631 if (i == skip_start && skip_size > 1) 632 { 633 if (i == 0) 634 { 635 *(buf++) = ':'; 636 } 637 *(buf++) = ':'; 638 i += skip_size - 1; 639 continue; 640 } 641 buf = EncodeInt<uint16_t, 16>{}(buf, ntoh(v.s6_addr16[i])); 642 if (i < 7) 643 { 644 *(buf++) = ':'; 645 } 646 } 647 return buf; 648 } 649 }; 650 651 template <> 652 struct ToStr<InAddrAny> 653 { 654 // IPv6 is the bigger of the addrs 655 static constexpr uint8_t buf_size = ToStr<in6_addr>::buf_size; 656 using buf_type = std::array<char, buf_size>; 657 658 constexpr char* operator()(char* buf, InAddrAny v) const noexcept 659 { 660 return std::visit([=](auto v) { return ToStr<decltype(v)>{}(buf, v); }, 661 v); 662 } 663 }; 664 665 template <> 666 struct ToStr<IfAddr> 667 { 668 // InAddrAny + sep + 3 prefix chars 669 static constexpr uint8_t buf_size = ToStr<InAddrAny>::buf_size + 4; 670 using buf_type = std::array<char, buf_size>; 671 672 constexpr char* operator()(char* buf, IfAddr v) const noexcept 673 { 674 buf = ToStr<InAddrAny>{}(buf, v.getAddr()); 675 buf = ToStr<char>{}(buf, '/'); 676 return EncodeInt<uint8_t, 10>{}(buf, v.getPfx()); 677 } 678 }; 679 680 namespace detail 681 { 682 683 template <typename T> 684 constexpr bool vcontains() noexcept 685 { 686 return false; 687 } 688 689 template <typename T, typename V, typename... Vs> 690 constexpr bool vcontains() noexcept 691 { 692 return vcontains<T, Vs...>() || std::is_same_v<T, V>; 693 } 694 695 template <typename T, typename... Types> 696 constexpr std::enable_if_t<vcontains<T, Types...>(), bool> 697 veq(T t, std::variant<Types...> v) noexcept 698 { 699 return std::visit( 700 [t](auto v) { 701 if constexpr (std::is_same_v<T, decltype(v)>) 702 { 703 return v == t; 704 } 705 else 706 { 707 return false; 708 } 709 }, 710 v); 711 } 712 713 template <typename T> 714 struct ToStrBuf 715 { 716 public: 717 constexpr std::string_view operator()(T v) noexcept 718 { 719 return {buf.data(), ToStr<T>{}(buf.data(), v)}; 720 } 721 722 private: 723 typename ToStr<T>::buf_type buf; 724 }; 725 726 template <typename T> 727 struct Format 728 { 729 private: 730 fmt::formatter<std::string_view> formatter; 731 732 public: 733 template <typename ParseContext> 734 constexpr auto parse(ParseContext& ctx) 735 { 736 return ctx.begin(); 737 } 738 739 template <typename FormatContext> 740 auto format(auto v, FormatContext& ctx) const 741 { 742 return formatter.format(ToStrBuf<T>{}(v), ctx); 743 } 744 }; 745 } // namespace detail 746 } // namespace network 747 } // namespace phosphor 748 749 template <typename... Ts> 750 struct std::hash<std::tuple<Ts...>> 751 { 752 constexpr auto operator()(const std::tuple<Ts...>& t) const noexcept 753 { 754 return std::apply(phosphor::network::hash_multi<Ts...>, t); 755 } 756 }; 757 758 template <> 759 struct std::hash<in_addr> 760 { 761 std::size_t operator()(in_addr addr) const noexcept; 762 }; 763 764 template <> 765 struct std::hash<in6_addr> 766 { 767 std::size_t operator()(in6_addr addr) const noexcept; 768 }; 769 770 template <> 771 struct std::hash<phosphor::network::IfAddr> 772 { 773 std::size_t operator()(phosphor::network::IfAddr addr) const noexcept; 774 }; 775 776 namespace fmt 777 { 778 template <> 779 struct formatter<ether_addr> : phosphor::network::detail::Format<ether_addr> 780 { 781 }; 782 template <> 783 struct formatter<in_addr> : phosphor::network::detail::Format<in_addr> 784 { 785 }; 786 template <> 787 struct formatter<in6_addr> : phosphor::network::detail::Format<in6_addr> 788 { 789 }; 790 template <> 791 struct formatter<phosphor::network::InAddrAny> 792 : phosphor::network::detail::Format<phosphor::network::InAddrAny> 793 { 794 }; 795 template <> 796 struct formatter<phosphor::network::IfAddr> 797 : phosphor::network::detail::Format<phosphor::network::IfAddr> 798 { 799 }; 800 } // namespace fmt 801 802 namespace std 803 { 804 string to_string(ether_addr value); 805 string to_string(in_addr value); 806 string to_string(in6_addr value); 807 string to_string(phosphor::network::InAddrAny value); 808 string to_string(phosphor::network::IfAddr value); 809 } // namespace std 810 811 template <typename T> 812 constexpr std::enable_if_t<!std::is_same_v<phosphor::network::InAddrAny, T>, 813 bool> 814 operator==(phosphor::network::InAddrAny lhs, T rhs) noexcept 815 { 816 return phosphor::network::detail::veq(rhs, lhs); 817 } 818 819 auto& operator<<(auto& os, ether_addr v) 820 { 821 return os << phosphor::network::detail::ToStrBuf<ether_addr>{}(v); 822 } 823 824 auto& operator<<(auto& os, in_addr v) 825 { 826 return os << phosphor::network::detail::ToStrBuf<in_addr>{}(v); 827 } 828 829 auto& operator<<(auto& os, in6_addr v) 830 { 831 return os << phosphor::network::detail::ToStrBuf<in6_addr>{}(v); 832 } 833 834 auto& operator<<(auto& os, phosphor::network::InAddrAny v) 835 { 836 phosphor::network::detail::ToStrBuf<phosphor::network::InAddrAny> tsb; 837 return os << tsb(v); 838 } 839 840 auto& operator<<(auto& os, phosphor::network::IfAddr v) 841 { 842 phosphor::network::detail::ToStrBuf<phosphor::network::IfAddr> tsb; 843 return os << tsb(v); 844 } 845 846 namespace phosphor::network 847 { 848 849 /** @brief Contains all of the object information about the interface */ 850 struct AllIntfInfo 851 { 852 InterfaceInfo intf; 853 std::optional<in_addr> defgw4 = std::nullopt; 854 std::optional<in6_addr> defgw6 = std::nullopt; 855 std::unordered_map<IfAddr, AddressInfo> addrs = {}; 856 std::unordered_map<InAddrAny, NeighborInfo> staticNeighs = {}; 857 }; 858 859 } // namespace phosphor::network 860