Compare commits

..

No commits in common. "master" and "v1.2" have entirely different histories.
master ... v1.2

5 changed files with 134 additions and 167 deletions

View File

@ -1,18 +1,11 @@
cmake_minimum_required(VERSION 3.7.0)
set(UUID_MAIN_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(UUID_MAIN_PROJECT ON)
endif()
project(stduuid CXX)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
option(UUID_BUILD_TESTS "Build the unit tests" ${UUID_MAIN_PROJECT})
option(UUID_BUILD_TESTS "Build the unit tests" ON)
option(UUID_SYSTEM_GENERATOR "Enable operating system uuid generator" OFF)
option(UUID_TIME_GENERATOR "Enable experimental time-based uuid generator" OFF)
option(UUID_USING_CXX20_SPAN "Using span from std instead of gsl" OFF)
option(UUID_ENABLE_INSTALL "Create an install target" ${UUID_MAIN_PROJECT})
# Library target
add_library(${PROJECT_NAME} INTERFACE)
@ -45,38 +38,36 @@ endif()
# Using span from std
if (NOT UUID_USING_CXX20_SPAN)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/gsl>
$<INSTALL_INTERFACE:include/gsl>)
install(DIRECTORY gsl DESTINATION include)
endif ()
if(UUID_ENABLE_INSTALL)
# Install step and imported target
install(FILES include/uuid.h DESTINATION include)
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets)
install(EXPORT ${PROJECT_NAME}-targets
DESTINATION lib/cmake/${PROJECT_NAME})
# Install step and imported target
install(FILES include/uuid.h DESTINATION include)
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets)
install(EXPORT ${PROJECT_NAME}-targets
DESTINATION lib/cmake/${PROJECT_NAME})
# Config files for find_package()
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME})
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake"
VERSION "1.0"
COMPATIBILITY AnyNewerVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindLibuuid.cmake"
DESTINATION lib/cmake/${PROJECT_NAME})
export(EXPORT ${PROJECT_NAME}-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}-targets.cmake")
endif()
# Config files for find_package()
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME})
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake"
VERSION "1.0"
COMPATIBILITY AnyNewerVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindLibuuid.cmake"
DESTINATION lib/cmake/${PROJECT_NAME})
export(EXPORT ${PROJECT_NAME}-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}-targets.cmake")
# Tests
if (UUID_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
endif ()
endif ()

View File

@ -105,7 +105,6 @@ P0959R2 was discussed by LEWGI in Kona with the following feedback:
Based on this feedback and further considerations, the following changes have been done in this version:
* Added detailed explanation of the algorithms for generating uuids.
* `from_string()` and non-member `swap()` declared with `noexcept`.
* `from_string()` and `is_valid_uuid()` declared with `constexpr`.
* The `uuid` type is now defined as a class.
* Added an exposition-only member to hint and the possible internal representation of the uuid data.
* Added `uuid` constructors from arrays.
@ -507,11 +506,21 @@ namespace std {
constexpr std::strong_ordering operator<=>(uuid const&) const noexcept = default;
template<typename StringType>
constexpr static bool is_valid_uuid(StringType const & str) noexcept;
template<typename StringType>
constexpr static uuid from_string(StringType const & str) noexcept;
template<class CharT = char>
static bool is_valid_uuid(CharT const * str) noexcept;
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
static bool is_valid_uuid(std::basic_string<CharT, Traits, Allocator> const & str) noexcept;
template<class CharT = char>
static uuid from_string(CharT const * str) noexcept;
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
static uuid from_string(std::basic_string<CharT, Traits, Allocator> const & str) noexcept;
private:
template <class Elem, class Traits>
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
@ -589,8 +598,13 @@ namespace std {
public:
explicit uuid_name_generator(uuid const& namespace_uuid) noexcept;
template<typename StringType>
uuid operator()(StringType const & name);
template<class CharT = char>
uuid operator()(CharT const * name);
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
uuid operator()(std::basic_string<CharT, Traits, Allocator> const & name);
};
}
```
@ -700,11 +714,21 @@ namespace std {
constexpr std::span<std::byte const, 16> as_bytes() const;
template<typename StringType>
constexpr static bool is_valid_uuid(StringType const & str) noexcept;
template<typename StringType>
constexpr static uuid from_string(StringType const & str) noexcept;
template<class CharT = char>
static bool is_valid_uuid(CharT const * str) noexcept;
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
static bool is_valid_uuid(std::basic_string<CharT, Traits, Allocator> const & str) noexcept;
template<class CharT = char>
static uuid from_string(CharT const * str) noexcept;
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
static uuid from_string(std::basic_string<CharT, Traits, Allocator> const & str) noexcept;
private:
template <class Elem, class Traits>
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
@ -817,12 +841,6 @@ auto id1 = uuid::from_string<char, std::char_traits<char>>(
std::string str{ "47183823-2574-4bfd-b411-99ed177d3e43" };
auto id2 = uuid::from_string<char, std::char_traits<char>>(str); // [2]
```
However, to simplify the definition, these two overloads have been replaced with a single one:
```
template <typename StringType>
constexpr static std::optional<uuid> from_string(StringType const & str);
```
The `is_valid_uuid()` function and the `uuid_name_generator`'s call operator are now defined in the same manner.
#### Need more explanations about the choices of ordering and how the RFC works in that regard.

View File

@ -2,21 +2,12 @@ version: '{build}'
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
CMAKE_GENERATOR: Visual Studio 16 2019
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
CMAKE_GENERATOR: Visual Studio 15 2017
CMAKE_GENERATOR_PLATFORM: Win32
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
CMAKE_GENERATOR: Visual Studio 16 2019
CMAKE_GENERATOR_PLATFORM: x64
CMAKE_CLI_FLAGS: -DUUID_SYSTEM_GENERATOR=ON -DUUID_TIME_GENERATOR=ON
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_GENERATOR_PLATFORM: Win32
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
CMAKE_GENERATOR: Visual Studio 17 2022
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
CMAKE_GENERATOR: Visual Studio 15 2017
CMAKE_GENERATOR_PLATFORM: x64
CMAKE_CLI_FLAGS: -DUUID_SYSTEM_GENERATOR=ON -DUUID_TIME_GENERATOR=ON

View File

@ -1,5 +1,4 @@
#ifndef STDUUID_H
#define STDUUID_H
#pragma once
#include <cstring>
#include <string>
@ -16,31 +15,10 @@
#include <chrono>
#include <numeric>
#include <atomic>
#ifdef __cplusplus
# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
# define LIBUUID_CPP20_OR_GREATER
# endif
#endif
#ifdef LIBUUID_CPP20_OR_GREATER
#include <span>
#else
#include <gsl/span>
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifdef UUID_SYSTEM_GENERATOR
#include <objbase.h>
#endif
@ -77,19 +55,19 @@ namespace uuids
namespace detail
{
template <typename TChar>
[[nodiscard]] constexpr inline unsigned char hex2char(TChar const ch) noexcept
constexpr inline unsigned char hex2char(TChar const ch)
{
if (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9'))
return static_cast<unsigned char>(ch - static_cast<TChar>('0'));
return ch - static_cast<TChar>('0');
if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f'))
return static_cast<unsigned char>(10 + ch - static_cast<TChar>('a'));
return 10 + ch - static_cast<TChar>('a');
if (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F'))
return static_cast<unsigned char>(10 + ch - static_cast<TChar>('A'));
return 10 + ch - static_cast<TChar>('A');
return 0;
}
template <typename TChar>
[[nodiscard]] constexpr inline bool is_hex(TChar const ch) noexcept
constexpr inline bool is_hex(TChar const ch)
{
return
(ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) ||
@ -98,18 +76,17 @@ namespace uuids
}
template <typename TChar>
[[nodiscard]] constexpr std::basic_string_view<TChar> to_string_view(TChar const * str) noexcept
constexpr std::basic_string_view<TChar> to_string_view(TChar const * str)
{
if (str) return str;
return {};
}
template <typename StringType>
[[nodiscard]]
constexpr std::basic_string_view<
typename StringType::value_type,
typename StringType::traits_type>
to_string_view(StringType const & str) noexcept
to_string_view(StringType const & str)
{
return str;
}
@ -122,14 +99,14 @@ namespace uuids
static constexpr unsigned int block_bytes = 64;
[[nodiscard]] inline static uint32_t left_rotate(uint32_t value, size_t const count) noexcept
inline static uint32_t left_rotate(uint32_t value, size_t const count)
{
return (value << count) ^ (value >> (32 - count));
}
sha1() { reset(); }
void reset() noexcept
void reset()
{
m_digest[0] = 0x67452301;
m_digest[1] = 0xEFCDAB89;
@ -295,16 +272,16 @@ namespace uuids
};
template <typename CharT>
inline constexpr CharT empty_guid[37] = "00000000-0000-0000-0000-000000000000";
constexpr CharT empty_guid[37] = "00000000-0000-0000-0000-000000000000";
template <>
inline constexpr wchar_t empty_guid<wchar_t>[37] = L"00000000-0000-0000-0000-000000000000";
constexpr wchar_t empty_guid<wchar_t>[37] = L"00000000-0000-0000-0000-000000000000";
template <typename CharT>
inline constexpr CharT guid_encoder[17] = "0123456789abcdef";
constexpr CharT guid_encoder[17] = "0123456789abcdef";
template <>
inline constexpr wchar_t guid_encoder<wchar_t>[17] = L"0123456789abcdef";
constexpr wchar_t guid_encoder<wchar_t>[17] = L"0123456789abcdef";
}
// --------------------------------------------------------------------------------------------------------------------------
@ -375,13 +352,6 @@ namespace uuids
name_based_sha1 = 5 // The name-based version specified in RFS 4122 with SHA1 hashing
};
// Forward declare uuid & to_string so that we can declare to_string as a friend later.
class uuid;
template <class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
std::basic_string<CharT, Traits, Allocator> to_string(uuid const &id);
// --------------------------------------------------------------------------------------------------------------------------
// uuid class
// --------------------------------------------------------------------------------------------------------------------------
@ -411,7 +381,7 @@ namespace uuids
std::copy(first, last, std::begin(data));
}
[[nodiscard]] constexpr uuid_variant variant() const noexcept
constexpr uuid_variant variant() const noexcept
{
if ((data[8] & 0x80) == 0x00)
return uuid_variant::ncs;
@ -423,7 +393,7 @@ namespace uuids
return uuid_variant::reserved;
}
[[nodiscard]] constexpr uuid_version version() const noexcept
constexpr uuid_version version() const noexcept
{
if ((data[6] & 0xF0) == 0x10)
return uuid_version::time_based;
@ -439,7 +409,7 @@ namespace uuids
return uuid_version::none;
}
[[nodiscard]] constexpr bool is_nil() const noexcept
constexpr bool is_nil() const noexcept
{
for (size_t i = 0; i < data.size(); ++i) if (data[i] != 0) return false;
return true;
@ -450,17 +420,17 @@ namespace uuids
data.swap(other.data);
}
[[nodiscard]] inline span<std::byte const, 16> as_bytes() const
inline span<std::byte const, 16> as_bytes() const
{
return span<std::byte const, 16>(reinterpret_cast<std::byte const*>(data.data()), 16);
}
template <typename StringType>
[[nodiscard]] constexpr static bool is_valid_uuid(StringType const & in_str) noexcept
constexpr static bool is_valid_uuid(StringType const & in_str) noexcept
{
auto str = detail::to_string_view(in_str);
bool firstDigit = true;
size_t hasBraces = 0;
int hasBraces = 0;
size_t index = 0;
if (str.empty())
@ -500,11 +470,11 @@ namespace uuids
}
template <typename StringType>
[[nodiscard]] constexpr static std::optional<uuid> from_string(StringType const & in_str) noexcept
constexpr static std::optional<uuid> from_string(StringType const & in_str) noexcept
{
auto str = detail::to_string_view(in_str);
bool firstDigit = true;
size_t hasBraces = 0;
int hasBraces = 0;
size_t index = 0;
std::array<uint8_t, 16> data{ { 0 } };
@ -527,13 +497,12 @@ namespace uuids
if (firstDigit)
{
data[index] = static_cast<uint8_t>(detail::hex2char(str[i]) << 4);
data[index] = detail::hex2char(str[i]) << 4;
firstDigit = false;
}
else
{
data[index] = static_cast<uint8_t>(data[index] | detail::hex2char(str[i]));
index++;
data[index++] |= detail::hex2char(str[i]);
firstDigit = true;
}
}
@ -553,7 +522,7 @@ namespace uuids
friend bool operator<(uuid const & lhs, uuid const & rhs) noexcept;
template <class Elem, class Traits>
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
template<class CharT, class Traits, class Allocator>
friend std::basic_string<CharT, Traits, Allocator> to_string(uuid const& id);
@ -565,25 +534,32 @@ namespace uuids
// operators and non-member functions
// --------------------------------------------------------------------------------------------------------------------------
[[nodiscard]] inline bool operator== (uuid const& lhs, uuid const& rhs) noexcept
inline bool operator== (uuid const& lhs, uuid const& rhs) noexcept
{
return lhs.data == rhs.data;
}
[[nodiscard]] inline bool operator!= (uuid const& lhs, uuid const& rhs) noexcept
inline bool operator!= (uuid const& lhs, uuid const& rhs) noexcept
{
return !(lhs == rhs);
}
[[nodiscard]] inline bool operator< (uuid const& lhs, uuid const& rhs) noexcept
inline bool operator< (uuid const& lhs, uuid const& rhs) noexcept
{
return lhs.data < rhs.data;
}
template <class CharT,
class Traits,
class Allocator>
[[nodiscard]] inline std::basic_string<CharT, Traits, Allocator> to_string(uuid const & id)
template <class Elem, class Traits>
std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id)
{
s << to_string(id);
return s;
}
template<class CharT = char,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
inline std::basic_string<CharT, Traits, Allocator> to_string(uuid const & id)
{
std::basic_string<CharT, Traits, Allocator> uustr{detail::empty_guid<CharT>};
@ -601,13 +577,6 @@ namespace uuids
return uustr;
}
template <class Elem, class Traits>
std::basic_ostream<Elem, Traits>& operator<<(std::basic_ostream<Elem, Traits>& s, uuid const& id)
{
s << to_string(id);
return s;
}
inline void swap(uuids::uuid & lhs, uuids::uuid & rhs) noexcept
{
lhs.swap(rhs);
@ -736,7 +705,7 @@ namespace uuids
#endif
template <typename UniformRandomNumberGenerator>
class basic_uuid_random_generator
class basic_uuid_random_generator
{
public:
using engine_type = UniformRandomNumberGenerator;
@ -746,9 +715,9 @@ namespace uuids
explicit basic_uuid_random_generator(engine_type* gen) :
generator(gen, [](auto) {}) {}
[[nodiscard]] uuid operator()()
uuid operator()()
{
alignas(uint32_t) uint8_t bytes[16];
uint8_t bytes[16];
for (int i = 0; i < 16; i += 4)
*reinterpret_cast<uint32_t*>(bytes + i) = distribution(*generator);
@ -778,7 +747,7 @@ namespace uuids
{}
template <typename StringType>
[[nodiscard]] uuid operator()(StringType const & name)
uuid operator()(StringType const & name)
{
reset();
process_characters(detail::to_string_view(name));
@ -810,7 +779,7 @@ namespace uuids
}
}
[[nodiscard]] uuid make_uuid()
uuid make_uuid()
{
detail::sha1::digest8_t digest;
hasher.get_digest_bytes(digest);
@ -840,7 +809,7 @@ namespace uuids
std::optional<mac_address> device_address;
[[nodiscard]] bool get_mac_address()
bool get_mac_address()
{
if (device_address.has_value())
{
@ -863,7 +832,7 @@ namespace uuids
return device_address.has_value();
}
[[nodiscard]] long long get_time_intervals()
long long get_time_intervals()
{
auto start = std::chrono::system_clock::from_time_t(time_t(-12219292800));
auto diff = std::chrono::system_clock::now() - start;
@ -871,7 +840,7 @@ namespace uuids
return ns / 100;
}
[[nodiscard]] static unsigned short get_clock_sequence()
static unsigned short get_clock_sequence()
{
static std::mt19937 clock_gen(std::random_device{}());
static std::uniform_int_distribution<unsigned short> clock_dis;
@ -880,7 +849,7 @@ namespace uuids
}
public:
[[nodiscard]] uuid operator()()
uuid operator()()
{
if (get_mac_address())
{
@ -925,32 +894,32 @@ namespace std
using argument_type = uuids::uuid;
using result_type = std::size_t;
[[nodiscard]] result_type operator()(argument_type const &uuid) const
result_type operator()(argument_type const &uuid) const
{
#ifdef UUID_HASH_STRING_BASED
std::hash<std::string> hasher;
return static_cast<result_type>(hasher(uuids::to_string(uuid)));
#else
uint64_t l =
static_cast<uint64_t>(uuid.data[0]) << 56 |
static_cast<uint64_t>(uuid.data[1]) << 48 |
static_cast<uint64_t>(uuid.data[2]) << 40 |
static_cast<uint64_t>(uuid.data[3]) << 32 |
static_cast<uint64_t>(uuid.data[4]) << 24 |
static_cast<uint64_t>(uuid.data[5]) << 16 |
static_cast<uint64_t>(uuid.data[6]) << 8 |
uint64_t l =
static_cast<uint64_t>(uuid.data[0]) << 56 |
static_cast<uint64_t>(uuid.data[1]) << 48 |
static_cast<uint64_t>(uuid.data[2]) << 40 |
static_cast<uint64_t>(uuid.data[3]) << 32 |
static_cast<uint64_t>(uuid.data[4]) << 24 |
static_cast<uint64_t>(uuid.data[5]) << 16 |
static_cast<uint64_t>(uuid.data[6]) << 8 |
static_cast<uint64_t>(uuid.data[7]);
uint64_t h =
static_cast<uint64_t>(uuid.data[8]) << 56 |
static_cast<uint64_t>(uuid.data[9]) << 48 |
static_cast<uint64_t>(uuid.data[10]) << 40 |
static_cast<uint64_t>(uuid.data[11]) << 32 |
static_cast<uint64_t>(uuid.data[12]) << 24 |
static_cast<uint64_t>(uuid.data[13]) << 16 |
static_cast<uint64_t>(uuid.data[14]) << 8 |
uint64_t h =
static_cast<uint64_t>(uuid.data[8]) << 56 |
static_cast<uint64_t>(uuid.data[9]) << 48 |
static_cast<uint64_t>(uuid.data[10]) << 40 |
static_cast<uint64_t>(uuid.data[11]) << 32 |
static_cast<uint64_t>(uuid.data[12]) << 24 |
static_cast<uint64_t>(uuid.data[13]) << 16 |
static_cast<uint64_t>(uuid.data[14]) << 8 |
static_cast<uint64_t>(uuid.data[15]);
if constexpr (sizeof(result_type) > 4)
if (sizeof(result_type) > 4)
{
return result_type(l ^ h);
}
@ -963,5 +932,3 @@ namespace std
}
};
}
#endif /* STDUUID_H */

View File

@ -8,7 +8,7 @@ else ()
set_target_properties(test_${PROJECT_NAME} PROPERTIES CXX_STANDARD 17)
endif ()
if (WIN32)
target_compile_options(test_${PROJECT_NAME} PRIVATE /EHc /Zc:hiddenFriend)
target_compile_options(test_${PROJECT_NAME} PRIVATE /EHc)
target_compile_definitions(test_${PROJECT_NAME} PRIVATE _SCL_SECURE_NO_WARNINGS)
elseif (APPLE)
target_compile_options(test_${PROJECT_NAME} PRIVATE -fexceptions -g -Wall)
@ -28,4 +28,4 @@ set_tests_properties("test_${PROJECT_NAME}"
FAIL_REGULAR_EXPRESSION "Failed \\d+ test cases")
set_tests_properties("test_${PROJECT_NAME}"
PROPERTIES
TIMEOUT 120)
TIMEOUT 120)