Commit 485f7966 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰

[1540] Changes after review:

- several parameters are now const
- OS-specific code moved from iface_mgr.cc to iface_mgr_linux.cc
- C-style casts replaced with a casting framework
  (see lib/util/io/pktinfo_utilities.h)
- added lib/util/range_utilities.h for methods that operate on
  container's ranges (isRangeZero and fillRandom)
- many other minor corrections
parent 4fd6efcd
......@@ -59,10 +59,9 @@ Dhcpv4Srv::~Dhcpv4Srv() {
bool
Dhcpv4Srv::run() {
while (!shutdown_) {
// client's message
// client's message and server's response
Pkt4Ptr query = IfaceMgr::instance().receive4();
Pkt4Ptr rsp; // server's response
Pkt4Ptr rsp;
if (query) {
try {
......
......@@ -23,6 +23,7 @@
#include <asiolink/io_address.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
using namespace std;
using namespace isc;
......@@ -73,8 +74,9 @@ Dhcpv6Srv::~Dhcpv6Srv() {
bool Dhcpv6Srv::run() {
while (!shutdown) {
Pkt6Ptr query = IfaceMgr::instance().receive6(); // client's message
Pkt6Ptr rsp; // server's response
// client's message and server's response
Pkt6Ptr query = IfaceMgr::instance().receive6();
Pkt6Ptr rsp;
if (query) {
if (!query->unpack()) {
......@@ -153,7 +155,7 @@ void Dhcpv6Srv::setServerID() {
// let's find suitable interface
for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
// all those conditions could be merged into one multi-condition
// All the following checks could be merged into one multi-condition
// statement, but let's keep them separated as perhaps one day
// we will grow knobs to selectively turn them on or off. Also,
// this code is used only *once* during first start on a new machine
......@@ -162,9 +164,12 @@ void Dhcpv6Srv::setServerID() {
// I wish there was a this_is_a_real_physical_interface flag...
// mac at least 6 bytes. All decent physical interfaces (Ethernet,
// WiFi, Infiniband, etc.) have 6 bytes long MAC address
if (iface->mac_len_ < 6) {
// MAC address should be at least 6 bytes. Although there is no such
// requirement in any RFC, all decent physical interfaces (Ethernet,
// WiFi, Infiniband, etc.) have 6 bytes long MAC address. We want to
// base our DUID on real hardware address, rather than virtual
// interface that pretends that underlying IP address is its MAC.
if (iface->mac_len_ < MIN_MAC_LEN) {
continue;
}
......@@ -213,15 +218,14 @@ void Dhcpv6Srv::setServerID() {
// See Section 9.3 of RFC3315 for details.
OptionBuffer srvid(12);
srand(time(NULL));
writeUint16(DUID_EN, &srvid[0]);
writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);
// length of the identifier is company specific. I hereby declare
// ISC standard of 6 bytes long random numbers.
for (int i = 6; i < 12; i++) {
srvid[i] = static_cast<uint8_t>(rand());
}
// Length of the identifier is company specific. I hereby declare
// ISC "standard" of 6 bytes long pseudo-random numbers.
srand(time(NULL));
fillRandom(&srvid[6],&srvid[12]);
serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
srvid.begin(), srvid.end()));
}
......
......@@ -35,6 +35,10 @@ namespace dhcp {
class Dhcpv6Srv : public boost::noncopyable {
public:
/// @brief Minimum length of a MAC address to be used in DUID generation.
static const size_t MIN_MAC_LEN = 6;
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv6 server.
......
......@@ -24,6 +24,7 @@
#include <dhcp/option6_ia.h>
#include <dhcp6/dhcp6_srv.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
using namespace std;
using namespace isc;
......@@ -136,9 +137,8 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// there's not much we can check. Just simple
// check if it is not all zeros
vector<uint8_t> content(len-2);
vector<uint8_t> zeros(0, len-2);
data.readVector(content, len-2);
EXPECT_NE(content, zeros);
EXPECT_FALSE(isRangeZero(content.begin(), content.end()));
}
case DUID_LL: // not supported yet
case DUID_UUID: // not supported yet
......
......@@ -23,9 +23,11 @@
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
#include <exceptions/exceptions.h>
#include <util/io/pktinfo_utilities.h>
using namespace std;
using namespace isc::asiolink;
using namespace isc::util::io::internal;
namespace isc {
namespace dhcp {
......@@ -154,12 +156,12 @@ IfaceMgr::~IfaceMgr() {
closeSockets();
}
void
IfaceMgr::stubDetectIfaces() {
void IfaceMgr::stubDetectIfaces() {
string ifaceName, linkLocal;
// TODO do the actual detection. Currently interface detection is faked
// by reading a text file.
// This is a stub implementation for interface detection. Actual detection
// is faked by reading a text file. It will eventually be removed once
// we have actual implementations for all supported systems.
cout << "Interface detection is not implemented yet. "
<< "Reading interfaces.txt file instead." << endl;
......@@ -177,7 +179,7 @@ IfaceMgr::stubDetectIfaces() {
cout << "Detected interface " << ifaceName << "/" << linkLocal << endl;
Iface iface(ifaceName, if_nametoindex( ifaceName.c_str() ) );
Iface iface(ifaceName, if_nametoindex(ifaceName.c_str()));
iface.flag_up_ = true;
iface.flag_running_ = true;
iface.flag_loopback_ = false;
......@@ -200,13 +202,16 @@ IfaceMgr::stubDetectIfaces() {
}
}
/// @todo: Remove this once we have OS-specific interface detection
/// routines (or at least OS-specific files, like iface_mgr_solaris.cc)
/// for all OSes.
#if !defined(OS_LINUX) && !defined(OS_BSD)
void IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
#endif
bool IfaceMgr::openSockets4(uint16_t port) {
bool IfaceMgr::openSockets4(const uint16_t port) {
int sock;
int count = 0;
......@@ -246,7 +251,7 @@ bool IfaceMgr::openSockets4(uint16_t port) {
}
bool IfaceMgr::openSockets6(uint16_t port) {
bool IfaceMgr::openSockets6(const uint16_t port) {
int sock;
int count = 0;
......@@ -288,9 +293,12 @@ bool IfaceMgr::openSockets6(uint16_t port) {
}
count++;
/// @todo: Remove this ifdef once we start supporting BSD systems.
#if defined(OS_LINUX)
// To receive multicast traffic, Linux requires binding socket to
// a multicast group. That in turn doesn't work on NetBSD.
int sock2 = openSocket(iface->getName(),
IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
port);
......@@ -357,7 +365,8 @@ IfaceMgr::getIface(const std::string& ifname) {
return (NULL); // not found
}
int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr, uint16_t port) {
int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
const uint16_t port) {
Iface* iface = getIface(ifname);
if (!iface) {
isc_throw(BadValue, "There is no " << ifname << " interface present.");
......@@ -557,6 +566,14 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
// Set the data buffer we're sending. (Using this wacky
// "scatter-gather" stuff... we only have a single chunk
// of data to send, so we declare a single vector entry.)
// As v structure is a C-style is used for both sending and
// receiving data, it is shared between sending and receiving
// (sendmsg and recvmsg). It is also defined in system headers,
// so we have no control over its definition. To set iov_base
// (defined as void*) we must use const cast from void *.
// Otherwise C++ compiler would complain that we are trying
// to assign const void* to void*.
v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
v.iov_len = pkt->getBuffer().getLength();
m.msg_iov = &v;
......@@ -574,7 +591,7 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
pktinfo = convertPktInfo6(CMSG_DATA(cmsg));
memset(pktinfo, 0, sizeof(*pktinfo));
pktinfo->ipi6_ifindex = pkt->getIndex();
m.msg_controllen = cmsg->cmsg_len;
......@@ -635,7 +652,7 @@ IfaceMgr::send(const Pkt4Ptr& pkt)
<< " over socket " << getSocket(*pkt) << " on interface "
<< getIface(pkt->getIface())->getFullName() << endl;
int result = sendmsg(getSocket(*pkt), &m, 0);
int result = sendmsg(getSocket(*pkt), &m, 0);
if (result < 0) {
isc_throw(Unexpected, "Pkt4 send failed.");
}
......@@ -754,15 +771,9 @@ IfaceMgr::receive4() {
}
Pkt6Ptr IfaceMgr::receive6() {
struct msghdr m;
struct iovec v;
int result;
struct cmsghdr* cmsg;
struct in6_pktinfo* pktinfo;
struct sockaddr_in6 from;
struct in6_addr to_addr;
int ifindex = -1;
Pkt6Ptr pkt;
// RFC3315 states that server responses may be
// fragmented if they are over MTU. There is no
......@@ -773,11 +784,10 @@ Pkt6Ptr IfaceMgr::receive6() {
static uint8_t buf[RCVBUFSIZE];
memset(&control_buf_[0], 0, control_buf_len_);
memset(&from, 0, sizeof(from));
memset(&to_addr, 0, sizeof(to_addr));
// Initialize our message header structure.
struct msghdr m;
memset(&m, 0, sizeof(m));
// Point so we can get the from address.
......@@ -787,7 +797,9 @@ Pkt6Ptr IfaceMgr::receive6() {
// Set the data buffer we're receiving. (Using this wacky
// "scatter-gather" stuff... but we that doesn't really make
// sense for us, so we use a single vector entry.)
v.iov_base = (void*)buf;
struct iovec v;
memset(&v, 0, sizeof(v));
v.iov_base = static_cast<void*>(buf);
v.iov_len = RCVBUFSIZE;
m.msg_iov = &v;
m.msg_iovlen = 1;
......@@ -839,7 +851,13 @@ Pkt6Ptr IfaceMgr::receive6() {
<< iface->getFullName() << endl;
result = recvmsg(candidate->sockfd_, &m, 0);
struct in6_addr to_addr;
memset(&to_addr, 0, sizeof(to_addr));
if (result >= 0) {
struct in6_pktinfo* pktinfo = NULL;
// If we did read successfully, then we need to loop
// through the control messages we received and
// find the one with our destination address.
......@@ -847,11 +865,11 @@ Pkt6Ptr IfaceMgr::receive6() {
// We also keep a flag to see if we found it. If we
// didn't, then we consider this to be an error.
int found_pktinfo = 0;
cmsg = CMSG_FIRSTHDR(&m);
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
while (cmsg != NULL) {
if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
(cmsg->cmsg_type == IPV6_PKTINFO)) {
pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg);
pktinfo = convertPktInfo6(CMSG_DATA(cmsg));
to_addr = pktinfo->ipi6_addr;
ifindex = pktinfo->ipi6_ifindex;
found_pktinfo = 1;
......@@ -867,7 +885,8 @@ Pkt6Ptr IfaceMgr::receive6() {
return (Pkt6Ptr()); // NULL
}
// Let's create a packet.
Pkt6Ptr pkt;
try {
pkt = Pkt6Ptr(new Pkt6(buf, result));
} catch (const std::exception& ex) {
......@@ -891,7 +910,7 @@ Pkt6Ptr IfaceMgr::receive6() {
return (boost::shared_ptr<Pkt6>()); // NULL
}
// TODO Move this to LOG_DEBUG
/// @todo: Move this to LOG_DEBUG
cout << "Received " << pkt->getBuffer().getLength() << " bytes over "
<< pkt->getIface() << "/" << pkt->getIndex() << " interface: "
<< " src=" << pkt->getRemoteAddr().toText()
......@@ -914,7 +933,7 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
(!s->addr_.getAddress().to_v6().is_multicast()) ) {
return (s->sockfd_);
}
/// TODO: Add more checks here later. If remote address is
/// @todo: Add more checks here later. If remote address is
/// not link-local, we can't use link local bound socket
/// to send data.
}
......
......@@ -232,6 +232,10 @@ public:
/// @brief Returns container with all interfaces.
///
/// This reference is only valid as long as IfaceMgr is valid. However,
/// since IfaceMgr is a singleton and is expected to be destroyed after
/// main() function completes, you should not worry much about this.
///
/// @return container with all interfaces.
const IfaceCollection& getIfaces() { return ifaces_; }
......@@ -330,7 +334,7 @@ public:
/// @return socket descriptor, if socket creation, binding and multicast
/// group join were all successful.
int openSocket(const std::string& ifname,
const isc::asiolink::IOAddress& addr, uint16_t port);
const isc::asiolink::IOAddress& addr, const uint16_t port);
/// Opens IPv6 sockets on detected interfaces.
///
......@@ -339,7 +343,7 @@ public:
/// @param port specifies port number (usually DHCP6_SERVER_PORT)
///
/// @return true if any sockets were open
bool openSockets6(uint16_t port = DHCP6_SERVER_PORT);
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
/// @brief Closes all open sockets.
/// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
......@@ -351,7 +355,7 @@ public:
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
///
/// @return true if any sockets were open
bool openSockets4(uint16_t port = DHCP4_SERVER_PORT);
bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT);
/// @brief returns number of detected interfaces
///
......
......@@ -40,9 +40,9 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
size_t end = buf.size();
while (offset +4 <= end) {
uint16_t opt_type = buf[offset]*256 + buf[offset+1];
uint16_t opt_type = buf[offset] * 256 + buf[offset + 1];
offset += 2;
uint16_t opt_len = buf[offset]*256 + buf[offset+1];
uint16_t opt_len = buf[offset] * 256 + buf[offset + 1];
offset += 2;
if (offset + opt_len > end) {
......@@ -160,12 +160,12 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
}
case Option::V4:
{
// option 0 is special (a one octet-long, equal 0) PAD option. It is never
// instantiated as Option object, but rather consumer during packet parsing.
// Option 0 is special (a one octet-long, equal 0) PAD option. It is never
// instantiated as an Option object, but rather consumed during packet parsing.
if (opt_type == 0) {
isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
}
// option 255 is never instantiated as an option object. It is special
// Option 255 is never instantiated as an option object. It is special
// (a one-octet equal 255) option that is added at the end of all options
// during packet assembly. It is also silently consumed during packet parsing.
if (opt_type > 254) {
......
......@@ -64,7 +64,7 @@ TEST(LibDhcpTest, packOptions6) {
OutputBuffer assembled(512);
EXPECT_NO_THROW ( LibDHCP::packOptions6(assembled, opts) );
EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
EXPECT_EQ(35, assembled.getLength()); // options should take 35 bytes
EXPECT_EQ(0, memcmp(assembled.getData(), packed, 35) );
}
......@@ -164,7 +164,7 @@ TEST(LibDhcpTest, packOptions4) {
vector<uint8_t> expVect(v4Opts, v4Opts + sizeof(v4Opts));
OutputBuffer buf(100);
EXPECT_NO_THROW ( LibDHCP::packOptions(buf, opts) );
EXPECT_NO_THROW(LibDHCP::packOptions(buf, opts));
ASSERT_EQ(buf.getLength(), sizeof(v4Opts));
EXPECT_EQ(0, memcmp(v4Opts, buf.getData(), sizeof(v4Opts)));
......
......@@ -198,7 +198,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
0xca, 0xfe, // type
0, 0 // len
};
ASSERT_EQ(sizeof(expected), 48);
ASSERT_EQ(48, sizeof(expected));
memcpy(&buf_[0], expected, sizeof(expected));
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
#ifndef __PKTINFO_UTIL_H_
#define __PKTINFO_UTIL_H_ 1
#include <sys/socket.h>
#include <netinet/in.h>
// These definitions in this file are for the convenience of internal
// implementation and test code, and are not intended to be used publicly.
// The namespace "internal" indicates the intent.
namespace isc {
namespace util {
namespace io {
namespace internal {
// Lower level C-APIs require conversion between char* pointers
// (like structures returned by CMSG_DATA macro) and in6_pktinfo,
// which is not friendly with C++. The following templates
// are a shortcut of common workaround conversion in such cases.
inline struct in6_pktinfo*
convertPktInfo6(char* pktinfo) {
return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
}
inline struct in6_pktinfo*
convertPktInfo6(unsigned char* pktinfo) {
return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
}
/// @todo: Do we need const versions as well?
}
}
}
}
#endif // __PKTINFO_UTIL_H_
......@@ -20,7 +20,7 @@
#include <cassert>
// This definitions in this file are for the convenience of internal
// These definitions in this file are for the convenience of internal
// implementation and test code, and are not intended to be used publicly.
// The namespace "internal" indicates the intent.
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
#ifndef __RANGE_UTIL_H_
#define __RANGE_UTIL_H_ 1
#include <algorithm>
// This header contains useful methods for conduction operations on
// a range of container elements. Currently the collection is limited,
// but it is expected to grow.
namespace isc {
namespace util {
/// @brief Checks if specified range in a container contains only zeros
///
/// @param begin beginning of the range
/// @param end end of the range
///
/// @return true if there are only zeroes, false otherwise
template <typename Iterator>
bool
isRangeZero(Iterator begin, Iterator end) {
return (std::find_if(begin, end,
std::bind1st(std::not_equal_to<int>(), 0))
== end);
}
/// @brief Fill in specified range with a random data.
///
/// Make sure that random number generator is initialized properly. Otherwise you
/// will get a the same pseudo-random sequence after every start of your process.
/// Calling srand() is enough. This method uses default rand(), which is usually
/// a LCG pseudo-random number generator, so it is not suitable for security
/// purposes. Please get a decent PRNG implementation, like mersene twister, if
/// you are doing anything related with security.
///
/// PRNG initialization is left out of this function on purpose. It may be
/// initialized to specific value on purpose, e.g. to repeat exactly the same
/// sequence in a test.
///
/// @param begin
/// @param end
template <typename Iterator>
void fillRandom(Iterator begin, Iterator end) {
for (Iterator x=begin; x != end; ++x) {
*x = rand();
}
}
} // end of isc::util namespace
} // end of isc namespace
#endif // __PKTINFO_UTIL_H_
......@@ -34,6 +34,7 @@ run_unittests_SOURCES += sha1_unittest.cc
run_unittests_SOURCES += socketsession_unittest.cc
run_unittests_SOURCES += strutil_unittest.cc
run_unittests_SOURCES += time_utilities_unittest.cc
run_unittests_SOURCES += range_utilities_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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.
#include <config.h>
#include <stdint.h>
#include <gtest/gtest.h>
#include <vector>
#include <util/range_utilities.h>
using namespace std;
using namespace isc::util;
TEST(RangeUtilitiesTest, isZero) {
vector<uint8_t> vec(32,0);
EXPECT_TRUE(isRangeZero(vec.begin(), vec.end()));
EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+1));
vec[5] = 1;
EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+5));
EXPECT_FALSE(isRangeZero(vec.begin(), vec.begin()+6));
}
TEST(RangeUtilitiesTest, randomFill) {
vector<uint8_t> vec1(16,0);
vector<uint8_t> vec2(16,0);
// Testing if returned value is actually random is extraordinary difficult.
// Let's just generate bunch of vectors and see if we get the same
// value. If we manage to do that in 100 tries, pseudo-random generator
// really sucks.
fillRandom(vec1.begin(), vec1.end());
for (int i=0; i<100; i++) {
fillRandom(vec2.begin(), vec2.end());
if (vec1 == vec2)
FAIL();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment