mirror of
https://codeberg.org/anoncontributorxmr/monero.git
synced 2024-12-23 13:47:47 +00:00
Commented most of src/serialization/ going to read up more on variant's and finish off the job/add last touchs next
This commit is contained in:
parent
6f65ab1904
commit
b032619a9c
5 changed files with 315 additions and 97 deletions
|
@ -28,7 +28,7 @@
|
|||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
/* binary_archive.h
|
||||
/*! \file binary_archive.h
|
||||
*
|
||||
* Portable (low-endian) binary archive */
|
||||
#pragma once
|
||||
|
@ -41,11 +41,20 @@
|
|||
#include "common/varint.h"
|
||||
#include "warnings.h"
|
||||
|
||||
PUSH_WARNINGS
|
||||
DISABLE_VS_WARNINGS(4244)
|
||||
/* I have no clue what these lines means */
|
||||
PUSH_WARNINGS;
|
||||
DISABLE_VS_WARNINGS(4244);
|
||||
|
||||
//TODO: fix size_t warning in x32 platform
|
||||
|
||||
/*! \struct binary_archive_base
|
||||
*
|
||||
* \brief base for the binary archive type
|
||||
*
|
||||
* \detailed It isn't used outside of this file, which its only
|
||||
* purpse is to define the functions used for the binary_archive. Its
|
||||
* a header, basically. I think it was declared simply to save typing...
|
||||
*/
|
||||
template <class Stream, bool IsSaving>
|
||||
struct binary_archive_base
|
||||
{
|
||||
|
@ -57,22 +66,38 @@ struct binary_archive_base
|
|||
|
||||
explicit binary_archive_base(stream_type &s) : stream_(s) { }
|
||||
|
||||
/* definition of standard API functions */
|
||||
void tag(const char *) { }
|
||||
void begin_object() { }
|
||||
void end_object() { }
|
||||
void begin_variant() { }
|
||||
void end_variant() { }
|
||||
/* I just want to leave a comment saying how this line really shows
|
||||
flaws in the ownership model of many OOP languages, that is all. */
|
||||
stream_type &stream() { return stream_; }
|
||||
|
||||
protected:
|
||||
stream_type &stream_;
|
||||
};
|
||||
|
||||
/* \struct binary_archive
|
||||
*
|
||||
* \brief the actualy binary archive type
|
||||
*
|
||||
* \detailed The boolean template argument /a W is the is_saving
|
||||
* parameter for binary_archive_base.
|
||||
*
|
||||
* The is_saving parameter says whether the archive is being read from
|
||||
* (false) or written to (true)
|
||||
*/
|
||||
template <bool W>
|
||||
struct binary_archive;
|
||||
|
||||
|
||||
template <>
|
||||
struct binary_archive<false> : public binary_archive_base<std::istream, false>
|
||||
{
|
||||
|
||||
explicit binary_archive(stream_type &s) : base_type(s) {
|
||||
stream_type::streampos pos = stream_.tellg();
|
||||
stream_.seekg(0, std::ios_base::end);
|
||||
|
@ -86,6 +111,10 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false>
|
|||
serialize_uint(*(typename boost::make_unsigned<T>::type *)&v);
|
||||
}
|
||||
|
||||
/*! \fn serialize_uint
|
||||
*
|
||||
* \brief serializes an unsigned integer
|
||||
*/
|
||||
template <class T>
|
||||
void serialize_uint(T &v, size_t width = sizeof(T))
|
||||
{
|
||||
|
@ -96,12 +125,16 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false>
|
|||
char c;
|
||||
stream_.get(c);
|
||||
T b = (unsigned char)c;
|
||||
ret += (b << shift);
|
||||
ret += (b << shift); // can this be changed to OR, i think it can.
|
||||
shift += 8;
|
||||
}
|
||||
v = ret;
|
||||
}
|
||||
void serialize_blob(void *buf, size_t len, const char *delimiter="") { stream_.read((char *)buf, len); }
|
||||
|
||||
void serialize_blob(void *buf, size_t len, const char *delimiter="")
|
||||
{
|
||||
stream_.read((char *)buf, len);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void serialize_varint(T &v)
|
||||
|
@ -115,17 +148,18 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false>
|
|||
typedef std::istreambuf_iterator<char> it;
|
||||
tools::read_varint(it(stream_), it(), v); // XXX handle failure
|
||||
}
|
||||
|
||||
void begin_array(size_t &s)
|
||||
{
|
||||
serialize_varint(s);
|
||||
}
|
||||
void begin_array() { }
|
||||
|
||||
void begin_array() { }
|
||||
void delimit_array() { }
|
||||
void end_array() { }
|
||||
|
||||
void begin_string(const char *delimiter="\"") { }
|
||||
void end_string(const char *delimiter="\"") { }
|
||||
void begin_string(const char *delimiter /*="\""*/) { }
|
||||
void end_string(const char *delimiter /*="\""*/) { }
|
||||
|
||||
void read_variant_tag(variant_tag_type &t) {
|
||||
serialize_int(t);
|
||||
|
@ -157,12 +191,14 @@ struct binary_archive<true> : public binary_archive_base<std::ostream, true>
|
|||
{
|
||||
for (size_t i = 0; i < sizeof(T); i++) {
|
||||
stream_.put((char)(v & 0xff));
|
||||
if (1 < sizeof(T)) {
|
||||
v >>= 8;
|
||||
if (1 < sizeof(T)) v >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
void serialize_blob(void *buf, size_t len, const char *delimiter="")
|
||||
{
|
||||
stream_.write((char *)buf, len);
|
||||
}
|
||||
void serialize_blob(void *buf, size_t len, const char *delimiter="") { stream_.write((char *)buf, len); }
|
||||
|
||||
template <class T>
|
||||
void serialize_varint(T &v)
|
||||
|
|
|
@ -34,23 +34,26 @@
|
|||
#include "binary_archive.h"
|
||||
|
||||
namespace serialization {
|
||||
|
||||
template <class T>
|
||||
bool parse_binary(const std::string &blob, T &v)
|
||||
{
|
||||
/*! creates a new archive with the passed blob and serializes it into v
|
||||
*/
|
||||
template <class T>
|
||||
bool parse_binary(const std::string &blob, T &v)
|
||||
{
|
||||
std::istringstream istr(blob);
|
||||
binary_archive<false> iar(istr);
|
||||
return ::serialization::serialize(iar, v);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool dump_binary(T& v, std::string& blob)
|
||||
{
|
||||
/*! dumps the data in v into the blob string
|
||||
*/
|
||||
template<class T>
|
||||
bool dump_binary(T& v, std::string& blob)
|
||||
{
|
||||
std::stringstream ostr;
|
||||
binary_archive<true> oar(ostr);
|
||||
bool success = ::serialization::serialize(oar, v);
|
||||
blob = ostr.str();
|
||||
return success && ostr.good();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace serialization
|
||||
}
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
/* json_archive.h
|
||||
/*! \file json_archive.h
|
||||
*
|
||||
* JSON archive */
|
||||
* \brief JSON archive
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -39,6 +40,12 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
/*! \struct json_archive_base
|
||||
*
|
||||
* \brief the base class of json archive type
|
||||
*
|
||||
* \detailed contains the basic logic for serializing a json archive
|
||||
*/
|
||||
template <class Stream, bool IsSaving>
|
||||
struct json_archive_base
|
||||
{
|
||||
|
@ -48,7 +55,8 @@ struct json_archive_base
|
|||
|
||||
typedef const char *variant_tag_type;
|
||||
|
||||
json_archive_base(stream_type &s, bool indent = false) : stream_(s), indent_(indent), object_begin(false), depth_(0) { }
|
||||
json_archive_base(stream_type &s, bool indent = false)
|
||||
: stream_(s), indent_(indent), object_begin(false), depth_(0) { }
|
||||
|
||||
void tag(const char *tag) {
|
||||
if (!object_begin)
|
||||
|
@ -92,6 +100,13 @@ protected:
|
|||
size_t depth_;
|
||||
};
|
||||
|
||||
|
||||
/*! \struct json_archive
|
||||
*
|
||||
* \brief a archive using the JSON standard
|
||||
*
|
||||
* \detailed only supports being written to
|
||||
*/
|
||||
template <bool W>
|
||||
struct json_archive;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2014, The Monero Project
|
||||
//
|
||||
>//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
|
@ -28,9 +28,16 @@
|
|||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
/* serialization.h
|
||||
/*! \file serialization.h
|
||||
* \breif Simple DSL AAPI based on
|
||||
*
|
||||
* Simple templated serialization API */
|
||||
* \detailed is_blob_type and has_free_serializer are
|
||||
* both descriptors for dispatching on to the serailize function.
|
||||
*
|
||||
* The API itself defines a domain specific language via dirty macro
|
||||
* hacks. Greenspun's tenth rule is very much in action throughout
|
||||
* this entire code base.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
|
@ -38,14 +45,35 @@
|
|||
#include <boost/type_traits/is_integral.hpp>
|
||||
#include <boost/type_traits/integral_constant.hpp>
|
||||
|
||||
/*! \struct is_blob_type
|
||||
*
|
||||
* \brief a descriptor for dispatching serialize
|
||||
*/
|
||||
template <class T>
|
||||
struct is_blob_type { typedef boost::false_type type; };
|
||||
|
||||
/*! \struct has_free_serializer
|
||||
*
|
||||
* \brief a descriptor for dispatching serialize
|
||||
*/
|
||||
template <class T>
|
||||
struct has_free_serializer { typedef boost::true_type type; };
|
||||
|
||||
/*! \struct serializer
|
||||
*
|
||||
* \brief ... wouldn't a class be better?
|
||||
*
|
||||
* \detailed The logic behind serializing data. Places the archive
|
||||
* data into the supplied parameter. This dispatches based on the
|
||||
* supplied \a T template parameter's traits of is_blob_type or it is
|
||||
* an integral (as defined by the is_integral trait). Depends on the
|
||||
* \a Archive parameter to have overloaded the serialize_blob(T v,
|
||||
* size_t size) and serialize_int(T v) base on which trait it
|
||||
* applied. When the class has neither types, it falls to the
|
||||
* overloaded method do_serialize(Archive ar) in T to do the work.
|
||||
*/
|
||||
template <class Archive, class T>
|
||||
struct serializer
|
||||
{
|
||||
struct serializer{
|
||||
static bool serialize(Archive &ar, T &v) {
|
||||
return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type());
|
||||
}
|
||||
|
@ -65,63 +93,151 @@ struct serializer
|
|||
}
|
||||
};
|
||||
|
||||
/*! \fn do_serialize(Archive &ar, T &v)
|
||||
*
|
||||
* \brief just calls the serialize function defined for ar and v...
|
||||
*/
|
||||
template <class Archive, class T>
|
||||
inline bool do_serialize(Archive &ar, T &v)
|
||||
{
|
||||
return ::serializer<Archive, T>::serialize(ar, v);
|
||||
}
|
||||
|
||||
#ifndef __GNUC__
|
||||
#ifndef constexpr
|
||||
#define constexpr
|
||||
#endif
|
||||
#endif
|
||||
// Never used in the code base
|
||||
// #ifndef __GNUC__
|
||||
// #ifndef constexpr
|
||||
// #define constexpr
|
||||
// #endif
|
||||
// #endif
|
||||
|
||||
/* the following add a trait to a set and define the serialization DSL*/
|
||||
|
||||
/*! \macro BLOB_SERIALIZER
|
||||
*
|
||||
* \brief makes the type have a blob serializer trait defined
|
||||
*/
|
||||
#define BLOB_SERIALIZER(T) \
|
||||
template<> struct is_blob_type<T> { typedef boost::true_type type; }
|
||||
template<> \
|
||||
struct is_blob_type<T> { \
|
||||
typedef boost::true_type type; \
|
||||
}
|
||||
|
||||
/*! \macro FREE_SERIALIZER
|
||||
*
|
||||
* \brief adds the has_free_serializer to the type
|
||||
*/
|
||||
#define FREE_SERIALIZER(T) \
|
||||
template<> struct has_free_serializer<T> { typedef boost::true_type type; }
|
||||
#define VARIANT_TAG(A, T, Tg) \
|
||||
template <bool W> struct variant_serialization_traits<A<W>, T> { static inline typename A<W>::variant_tag_type get_tag() { return Tg; } }
|
||||
template<> \
|
||||
struct has_free_serializer<T> { \
|
||||
typedef boost::true_type type; \
|
||||
}
|
||||
|
||||
/*! \macro VARIANT_TAG
|
||||
*
|
||||
* \brief Adds the tag \tag to the \a Archive of \a Type
|
||||
*/
|
||||
#define VARIANT_TAG(Archive, Type, Tag) \
|
||||
template <bool W> \
|
||||
struct variant_serialization_traits<Archive<W>, Type> { \
|
||||
static inline typename Archive<W>::variant_tag_type get_tag() { \
|
||||
return Tag; \
|
||||
} \
|
||||
}
|
||||
|
||||
/*! \macro BEGIN_SERIALIZE
|
||||
*
|
||||
* \brief Begins the environment of the DSL
|
||||
* \detailed for describing how to
|
||||
* serialize an of an archive type
|
||||
*/
|
||||
#define BEGIN_SERIALIZE() \
|
||||
template <bool W, template <bool> class Archive> bool do_serialize(Archive<W> &ar) {
|
||||
template <bool W, template <bool> class Archive> \
|
||||
bool do_serialize(Archive<W> &ar) {
|
||||
|
||||
/*! \macro BEGIN_SERIALIZE_OBJECT
|
||||
*
|
||||
* \brief begins the environment of the DSL
|
||||
* \detailed for described the serialization of an object
|
||||
*/
|
||||
#define BEGIN_SERIALIZE_OBJECT() \
|
||||
template <bool W, template <bool> class Archive> bool do_serialize(Archive<W> &ar) { ar.begin_object(); bool r = do_serialize_object(ar); ar.end_object(); return r; } \
|
||||
template <bool W, template <bool> class Archive> bool do_serialize_object(Archive<W> &ar){
|
||||
#define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) ::serialization::detail::prepare_custom_vector_serialization(size, vec, typename Archive<W>::is_saving())
|
||||
template <bool W, template <bool> class Archive> \
|
||||
bool do_serialize(Archive<W> &ar) { \
|
||||
ar.begin_object(); \
|
||||
bool r = do_serialize_object(ar); \
|
||||
ar.end_object(); \
|
||||
return r; \
|
||||
} \
|
||||
template <bool W, template <bool> class Archive> \
|
||||
bool do_serialize_object(Archive<W> &ar){
|
||||
|
||||
#define END_SERIALIZE() return true;}
|
||||
/*! \macro PREPARE_CUSTON_VECTOR_SERIALIZATION
|
||||
*/
|
||||
#define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) \
|
||||
::serialization::detail::prepare_custom_vector_serialization(size, vec, typename Archive<W>::is_saving())
|
||||
|
||||
/*! \macro END_SERIALIZE
|
||||
* \brief self-explanatory
|
||||
*/
|
||||
#define END_SERIALIZE() \
|
||||
return true; \
|
||||
}
|
||||
|
||||
/*! \macro VALUE(f)
|
||||
* \brief the same as FIELD(f)
|
||||
*/
|
||||
#define VALUE(f) \
|
||||
do { \
|
||||
ar.tag(#f); \
|
||||
bool r = ::do_serialize(ar, f); \
|
||||
if (!r || !ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
/*! \macro FIELD_N(t,f)
|
||||
*
|
||||
* \brief serializes a field \a f tagged \a t
|
||||
*/
|
||||
#define FIELD_N(t, f) \
|
||||
do { \
|
||||
ar.tag(t); \
|
||||
bool r = ::do_serialize(ar, f); \
|
||||
if (!r || !ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
#define FIELDS(f) \
|
||||
do { \
|
||||
bool r = ::do_serialize(ar, f); \
|
||||
if (!r || !ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
/*! \macro FIELD(f)
|
||||
*
|
||||
* \brief tags the field with the variable name and then serializes it
|
||||
*/
|
||||
#define FIELD(f) \
|
||||
do { \
|
||||
ar.tag(#f); \
|
||||
bool r = ::do_serialize(ar, f); \
|
||||
if (!r || !ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
/*! \macro FIELDS(f)
|
||||
*
|
||||
* \brief does not add a tag to the serialized value
|
||||
*/
|
||||
#define FIELDS(f) \
|
||||
do { \
|
||||
bool r = ::do_serialize(ar, f); \
|
||||
if (!r || !ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
/*! \macro VARING_FIELD(f)
|
||||
* \brief tags and serializes the varint \a f
|
||||
*/
|
||||
#define VARINT_FIELD(f) \
|
||||
do { \
|
||||
ar.tag(#f); \
|
||||
ar.serialize_varint(f); \
|
||||
if (!ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
/*! \macro VARING_FIELD_N(t, f)
|
||||
*
|
||||
* \brief tags (as \a t) and serializes the varint \a f
|
||||
*/
|
||||
#define VARINT_FIELD_N(t, f) \
|
||||
do { \
|
||||
ar.tag(t); \
|
||||
|
@ -129,9 +245,19 @@ inline bool do_serialize(Archive &ar, T &v)
|
|||
if (!ar.stream().good()) return false; \
|
||||
} while(0);
|
||||
|
||||
|
||||
namespace serialization {
|
||||
/*! \namespace detail
|
||||
*
|
||||
* \brief declaration and default definition for the functions used the API
|
||||
*
|
||||
*/
|
||||
namespace detail
|
||||
{
|
||||
/*! \fn prepare_custom_vector_serialization
|
||||
*
|
||||
* prepares the vector /vec for serialization
|
||||
*/
|
||||
template <typename T>
|
||||
void prepare_custom_vector_serialization(size_t size, std::vector<T>& vec, const boost::mpl::bool_<true>& /*is_saving*/)
|
||||
{
|
||||
|
@ -143,12 +269,21 @@ namespace serialization {
|
|||
vec.resize(size);
|
||||
}
|
||||
|
||||
/*! \fn do_check_stream_state
|
||||
*
|
||||
* \brief self explanatory
|
||||
*/
|
||||
template<class Stream>
|
||||
bool do_check_stream_state(Stream& s, boost::mpl::bool_<true>)
|
||||
{
|
||||
return s.good();
|
||||
}
|
||||
|
||||
/*! \fn do_check_stream_state
|
||||
*
|
||||
* \brief self explanatory
|
||||
*
|
||||
* \detailed Also checks to make sure that the stream is not at EOF
|
||||
*/
|
||||
template<class Stream>
|
||||
bool do_check_stream_state(Stream& s, boost::mpl::bool_<false>)
|
||||
{
|
||||
|
@ -163,12 +298,20 @@ namespace serialization {
|
|||
}
|
||||
}
|
||||
|
||||
/*! \fn check_stream_state
|
||||
*
|
||||
* \brief calls detail::do_check_stream_state for ar
|
||||
*/
|
||||
template<class Archive>
|
||||
bool check_stream_state(Archive& ar)
|
||||
{
|
||||
return detail::do_check_stream_state(ar.stream(), typename Archive::is_saving());
|
||||
}
|
||||
|
||||
/*! \fn serialize
|
||||
*
|
||||
* \brief serializes \a v into \a ar
|
||||
*/
|
||||
template <class Archive, class T>
|
||||
inline bool serialize(Archive &ar, T &v)
|
||||
{
|
||||
|
|
|
@ -28,6 +28,12 @@
|
|||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
/*! \file variant.h
|
||||
*
|
||||
* \brief for dealing with variants
|
||||
*
|
||||
* \detailed Variant: OOP Union
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
@ -39,11 +45,21 @@
|
|||
#include <boost/mpl/pop_front.hpp>
|
||||
#include "serialization.h"
|
||||
|
||||
/*! \struct variant_serialization_triats
|
||||
*
|
||||
* \brief used internally to contain a variant's traits/possible types
|
||||
*
|
||||
* \detailed see the macro VARIANT_TAG in serialization.h:140
|
||||
*/
|
||||
template <class Archive, class T>
|
||||
struct variant_serialization_traits
|
||||
{
|
||||
};
|
||||
|
||||
/*! \struct variant_reader
|
||||
*
|
||||
* \brief reads a variant
|
||||
*/
|
||||
template <class Archive, class Variant, class TBegin, class TEnd>
|
||||
struct variant_reader
|
||||
{
|
||||
|
@ -51,9 +67,10 @@ struct variant_reader
|
|||
typedef typename boost::mpl::next<TBegin>::type TNext;
|
||||
typedef typename boost::mpl::deref<TBegin>::type current_type;
|
||||
|
||||
// A tail recursive inline function.... okay...
|
||||
static inline bool read(Archive &ar, Variant &v, variant_tag_type t)
|
||||
{
|
||||
if (variant_serialization_traits<Archive, current_type>::get_tag() == t) {
|
||||
if(variant_serialization_traits<Archive, current_type>::get_tag() == t) {
|
||||
current_type x;
|
||||
if(!::do_serialize(ar, x))
|
||||
{
|
||||
|
@ -62,12 +79,15 @@ struct variant_reader
|
|||
}
|
||||
v = x;
|
||||
} else {
|
||||
// Tail recursive.... but no mutation is going on. Why?
|
||||
return variant_reader<Archive, Variant, TNext, TEnd>::read(ar, v, t);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// This one just fails when you call it.... okay
|
||||
// So the TEnd parameter must be specified/differnt from TBegin
|
||||
template <class Archive, class Variant, class TBegin>
|
||||
struct variant_reader<Archive, Variant, TBegin, TBegin>
|
||||
{
|
||||
|
@ -78,7 +98,6 @@ struct variant_reader<Archive, Variant, TBegin, TBegin>
|
|||
ar.stream().setstate(std::ios::failbit);
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -93,7 +112,9 @@ struct serializer<Archive<false>, boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>>
|
|||
variant_tag_type t;
|
||||
ar.begin_variant();
|
||||
ar.read_variant_tag(t);
|
||||
if(!variant_reader<Archive<false>, variant_type, typename boost::mpl::begin<types>::type, typename boost::mpl::end<types>::type>::read(ar, v, t))
|
||||
if(!variant_reader<Archive<false>, variant_type,
|
||||
typename boost::mpl::begin<types>::type,
|
||||
typename boost::mpl::end<types>::type>::read(ar, v, t))
|
||||
{
|
||||
ar.stream().setstate(std::ios::failbit);
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue