BIND 10 trac1154, updated. 82348d8d9d266d91e570c4ae8d8f1afd3315178a Merge branch 'master' into trac1154
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Aug 5 10:45:24 UTC 2011
The branch, trac1154 has been updated
via 82348d8d9d266d91e570c4ae8d8f1afd3315178a (commit)
via ee2a86bd4c1472e606b3d59ef5c4392b61d7ab48 (commit)
via 0b98878ed8a185cbc3b78c860019416bfed317bb (commit)
via 747d2952c78ee32acc485946d3922cfe899a4b48 (commit)
via f26298e3ae274ccea3d4bcef37f5ac85da383461 (commit)
via 7489fa475c3f5963323a6b660e4544e48f45d37c (commit)
via dae8a2aabc0cc9c9f3794276676872014c5a58fa (commit)
via 3cebb4e77088feb357b485aeeda26429f98dce9b (commit)
via 19912ea4537e669f9c9ad1108b6f5453025738ef (commit)
via e62e50f3143aa67bd60c2351ad61d7544f28d4ca (commit)
via d299036c6ac281d1d6c119c5fdbe603bed404851 (commit)
via e5d9f259dce621201a2c52b56b260f8de776ecc0 (commit)
via f773f9ac21221663bd093806374cab83abd2288d (commit)
from 54ef8963e504e22dcf29405412a95100a210efe5 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 82348d8d9d266d91e570c4ae8d8f1afd3315178a
Merge: 54ef8963e504e22dcf29405412a95100a210efe5 ee2a86bd4c1472e606b3d59ef5c4392b61d7ab48
Author: Stephen Morris <stephen at isc.org>
Date: Fri Aug 5 10:58:25 2011 +0100
Merge branch 'master' into trac1154
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 3 +-
src/bin/auth/Makefile.am | 1 +
src/bin/auth/benchmarks/Makefile.am | 1 +
src/bin/auth/tests/Makefile.am | 1 +
src/bin/bind10/Makefile.am | 6 +-
src/bin/bind10/bind10_src.py.in | 6 +-
src/bin/bind10/run_bind10.sh.in | 2 +-
src/bin/bind10/tests/Makefile.am | 2 +-
src/bin/dhcp6/Makefile.am | 1 +
src/bin/host/Makefile.am | 1 +
src/lib/bench/tests/Makefile.am | 1 +
src/lib/cache/tests/Makefile.am | 1 +
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/dns/benchmarks/Makefile.am | 1 +
src/lib/dns/rdata/any_255/tsig_250.cc | 126 +++++++------------
src/lib/dns/rdata/in_1/srv_33.cc | 58 +++-------
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/bind10/Makefile.am | 4 +
src/{bin => lib/python/isc}/bind10/__init__.py | 0
src/{bin => lib/python/isc}/bind10/sockcreator.py | 0
.../python/isc}/bind10/tests/Makefile.am | 7 +-
.../python/isc/bind10/tests/sockcreator_test.py} | 2 +-
src/lib/python/isc/config/ccsession.py | 4 +-
src/lib/python/isc/config/tests/ccsession_test.py | 2 +
src/lib/resolve/tests/Makefile.am | 1 +
src/lib/util/strutil.cc | 11 ++
src/lib/util/strutil.h | 62 ++++++++++
src/lib/util/tests/strutil_unittest.cc | 80 +++++++++++--
28 files changed, 236 insertions(+), 151 deletions(-)
create mode 100644 src/lib/python/isc/bind10/Makefile.am
rename src/{bin => lib/python/isc}/bind10/__init__.py (100%)
rename src/{bin => lib/python/isc}/bind10/sockcreator.py (100%)
copy src/{bin => lib/python/isc}/bind10/tests/Makefile.am (91%)
rename src/{bin/bind10/tests/sockcreator_test.py.in => lib/python/isc/bind10/tests/sockcreator_test.py} (99%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 0ede949..4ff0d07 100644
--- a/configure.ac
+++ b/configure.ac
@@ -839,6 +839,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/notify/Makefile
src/lib/python/isc/notify/tests/Makefile
src/lib/python/isc/testutils/Makefile
+ src/lib/python/isc/bind10/Makefile
+ src/lib/python/isc/bind10/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
@@ -907,7 +909,6 @@ AC_OUTPUT([doc/version.ent
src/bin/bind10/bind10_src.py
src/bin/bind10/run_bind10.sh
src/bin/bind10/tests/bind10_test.py
- src/bin/bind10/tests/sockcreator_test.py
src/bin/bindctl/run_bindctl.sh
src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index 64136c1..e3128b5 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -56,6 +56,7 @@ EXTRA_DIST += auth_messages.mes
b10_auth_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+b10_auth_LDADD += $(top_builddir)/src/lib/util/libutil.la
b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index cf3fe4a..d51495b 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -17,6 +17,7 @@ query_bench_SOURCES += ../auth_log.h ../auth_log.cc
nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+query_bench_LDADD += $(top_builddir)/src/lib/util/libutil.la
query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
query_bench_LDADD += $(top_builddir)/src/lib/bench/libbench.la
query_bench_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 71520c2..5cd2f5a 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -47,6 +47,7 @@ run_unittests_LDADD += $(SQLITE_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am
index 1a5ce64..6ab88d8 100644
--- a/src/bin/bind10/Makefile.am
+++ b/src/bin/bind10/Makefile.am
@@ -1,11 +1,7 @@
SUBDIRS = . tests
sbin_SCRIPTS = bind10
-CLEANFILES = bind10 bind10_src.pyc bind10_messages.py bind10_messages.pyc \
- sockcreator.pyc
-
-python_PYTHON = __init__.py sockcreator.py
-pythondir = $(pyexecdir)/bind10
+CLEANFILES = bind10 bind10_src.pyc bind10_messages.py bind10_messages.pyc
pkglibexecdir = $(libexecdir)/@PACKAGE@
pyexec_DATA = bind10_messages.py
diff --git a/src/bin/bind10/__init__.py b/src/bin/bind10/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index bbb17a2..b497f7c 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -67,7 +67,7 @@ import isc.util.process
import isc.net.parse
import isc.log
from bind10_messages import *
-import bind10.sockcreator
+import isc.bind10.sockcreator
isc.log.init("b10-boss")
logger = isc.log.Logger("boss")
@@ -337,8 +337,8 @@ class BoB:
def start_creator(self):
self.curproc = 'b10-sockcreator'
- self.sockcreator = bind10.sockcreator.Creator("@@LIBEXECDIR@@:" +
- os.environ['PATH'])
+ self.sockcreator = isc.bind10.sockcreator.Creator("@@LIBEXECDIR@@:" +
+ os.environ['PATH'])
def stop_creator(self, kill=False):
if self.sockcreator is None:
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index bb44ca0..b5b9721 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/dhcp6:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:
diff --git a/src/bin/bind10/sockcreator.py b/src/bin/bind10/sockcreator.py
deleted file mode 100644
index 9fcc74e..0000000
--- a/src/bin/bind10/sockcreator.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# 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 SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS 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.
-
-import socket
-import struct
-import os
-import subprocess
-from bind10_messages import *
-from libutil_io_python import recv_fd
-
-logger = isc.log.Logger("boss")
-
-"""
-Module that comunicates with the privileged socket creator (b10-sockcreator).
-"""
-
-class CreatorError(Exception):
- """
- Exception for socket creator related errors.
-
- It has two members: fatal and errno and they are just holding the values
- passed to the __init__ function.
- """
-
- def __init__(self, message, fatal, errno=None):
- """
- Creates the exception. The message argument is the usual string.
- The fatal one tells if the error is fatal (eg. the creator crashed)
- and errno is the errno value returned from socket creator, if
- applicable.
- """
- Exception.__init__(self, message)
- self.fatal = fatal
- self.errno = errno
-
-class Parser:
- """
- This class knows the sockcreator language. It creates commands, sends them
- and receives the answers and parses them.
-
- It does not start it, the communication channel must be provided.
-
- In theory, anything here can throw a fatal CreatorError exception, but it
- happens only in case something like the creator process crashes. Any other
- occasions are mentioned explicitly.
- """
-
- def __init__(self, creator_socket):
- """
- Creates the parser. The creator_socket is socket to the socket creator
- process that will be used for communication. However, the object must
- have a read_fd() method to read the file descriptor. This slightly
- unusual trick with modifying an object is used to easy up testing.
-
- You can use WrappedSocket in production code to add the method to any
- ordinary socket.
- """
- self.__socket = creator_socket
- logger.info(BIND10_SOCKCREATOR_INIT)
-
- def terminate(self):
- """
- Asks the creator process to terminate and waits for it to close the
- socket. Does not return anything. Raises a CreatorError if there is
- still data on the socket, if there is an error closing the socket,
- or if the socket had already been closed.
- """
- if self.__socket is None:
- raise CreatorError('Terminated already', True)
- logger.info(BIND10_SOCKCREATOR_TERMINATE)
- try:
- self.__socket.sendall(b'T')
- # Wait for an EOF - it will return empty data
- eof = self.__socket.recv(1)
- if len(eof) != 0:
- raise CreatorError('Protocol error - data after terminated',
- True)
- self.__socket = None
- except socket.error as se:
- self.__socket = None
- raise CreatorError(str(se), True)
-
- def get_socket(self, address, port, socktype):
- """
- Asks the socket creator process to create a socket. Pass an address
- (the isc.net.IPaddr object), port number and socket type (either
- string "UDP", "TCP" or constant socket.SOCK_DGRAM or
- socket.SOCK_STREAM.
-
- Blocks until it is provided by the socket creator process (which
- should be fast, as it is on localhost) and returns the file descriptor
- number. It raises a CreatorError exception if the creation fails.
- """
- if self.__socket is None:
- raise CreatorError('Socket requested on terminated creator', True)
- # First, assemble the request from parts
- logger.info(BIND10_SOCKET_GET, address, port, socktype)
- data = b'S'
- if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
- data += b'U'
- elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
- data += b'T'
- else:
- raise ValueError('Unknown socket type: ' + str(socktype))
- if address.family == socket.AF_INET:
- data += b'4'
- elif address.family == socket.AF_INET6:
- data += b'6'
- else:
- raise ValueError('Unknown address family in address')
- data += struct.pack('!H', port)
- data += address.addr
- try:
- # Send the request
- self.__socket.sendall(data)
- answer = self.__socket.recv(1)
- if answer == b'S':
- # Success!
- result = self.__socket.read_fd()
- logger.info(BIND10_SOCKET_CREATED, result)
- return result
- elif answer == b'E':
- # There was an error, read the error as well
- error = self.__socket.recv(1)
- errno = struct.unpack('i',
- self.__read_all(len(struct.pack('i',
- 0))))
- if error == b'S':
- cause = 'socket'
- elif error == b'B':
- cause = 'bind'
- else:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
- raise CreatorError('Unknown error cause' + str(answer), True)
- logger.error(BIND10_SOCKET_ERROR, cause, errno[0],
- os.strerror(errno[0]))
- raise CreatorError('Error creating socket on ' + cause, False,
- errno[0])
- else:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
- raise CreatorError('Unknown response ' + str(answer), True)
- except socket.error as se:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se))
- raise CreatorError(str(se), True)
-
- def __read_all(self, length):
- """
- Keeps reading until length data is read or EOF or error happens.
-
- EOF is considered error as well and throws a CreatorError.
- """
- result = b''
- while len(result) < length:
- data = self.__socket.recv(length - len(result))
- if len(data) == 0:
- self.__socket = None
- logger.fatal(BIND10_SOCKCREATOR_EOF)
- raise CreatorError('Unexpected EOF', True)
- result += data
- return result
-
-class WrappedSocket:
- """
- This class wraps a socket and adds a read_fd method, so it can be used
- for the Parser class conveniently. It simply copies all its guts into
- itself and implements the method.
- """
- def __init__(self, socket):
- # Copy whatever can be copied from the socket
- for name in dir(socket):
- if name not in ['__class__', '__weakref__']:
- setattr(self, name, getattr(socket, name))
- # Keep the socket, so we can prevent it from being garbage-collected
- # and closed before we are removed ourself
- self.__orig_socket = socket
-
- def read_fd(self):
- """
- Read the file descriptor from the socket.
- """
- return recv_fd(self.fileno())
-
-# FIXME: Any idea how to test this? Starting an external process doesn't sound
-# OK
-class Creator(Parser):
- """
- This starts the socket creator and allows asking for the sockets.
- """
- def __init__(self, path):
- (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- # Popen does not like, for some reason, having the same socket for
- # stdin as well as stdout, so we dup it before passing it there.
- remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX,
- socket.SOCK_STREAM)
- env = os.environ
- env['PATH'] = path
- self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
- stdin=remote.fileno(),
- stdout=remote2.fileno())
- remote.close()
- remote2.close()
- Parser.__init__(self, WrappedSocket(local))
-
- def pid(self):
- return self.__process.pid
-
- def kill(self):
- logger.warn(BIND10_SOCKCREATOR_KILL)
- if self.__process is not None:
- self.__process.kill()
- self.__process = None
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 6d758b3..d9e012f 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -1,7 +1,7 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
#PYTESTS = args_test.py bind10_test.py
# NOTE: this has a generated test found in the builddir
-PYTESTS = bind10_test.py sockcreator_test.py
+PYTESTS = bind10_test.py
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
diff --git a/src/bin/bind10/tests/sockcreator_test.py.in b/src/bin/bind10/tests/sockcreator_test.py.in
deleted file mode 100644
index 53e7035..0000000
--- a/src/bin/bind10/tests/sockcreator_test.py.in
+++ /dev/null
@@ -1,327 +0,0 @@
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# 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 SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS 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.
-
-# This test file is generated .py.in -> .py just to be in the build dir,
-# same as the rest of the tests. Saves a lot of stuff in makefile.
-
-"""
-Tests for the bind10.sockcreator module.
-"""
-
-import unittest
-import struct
-import socket
-from isc.net.addr import IPAddr
-import isc.log
-from libutil_io_python import send_fd
-from bind10.sockcreator import Parser, CreatorError, WrappedSocket
-
-class FakeCreator:
- """
- Class emulating the socket to the socket creator. It can be given expected
- data to receive (and check) and responses to give to the Parser class
- during testing.
- """
-
- class InvalidPlan(Exception):
- """
- Raised when someone wants to recv when sending is planned or vice
- versa.
- """
- pass
-
- class InvalidData(Exception):
- """
- Raises when the data passed to sendall are not the same as expected.
- """
- pass
-
- def __init__(self, plan):
- """
- Create the object. The plan variable contains list of expected actions,
- in form:
-
- [('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
- , ('d', 'File descriptor number to return from read_sock'), ('e',
- None), ...]
-
- It modifies the array as it goes.
- """
- self.__plan = plan
-
- def __get_plan(self, expected):
- if len(self.__plan) == 0:
- raise InvalidPlan('Nothing more planned')
- (kind, data) = self.__plan[0]
- if kind == 'e':
- self.__plan.pop(0)
- raise socket.error('False socket error')
- if kind != expected:
- raise InvalidPlan('Planned ' + kind + ', but ' + expected +
- 'requested')
- return data
-
- def recv(self, maxsize):
- """
- Emulate recv. Returs maxsize bytes from the current recv plan. If
- there are data left from previous recv call, it is used first.
-
- If no recv is planned, raises InvalidPlan.
- """
- data = self.__get_plan('r')
- result, rest = data[:maxsize], data[maxsize:]
- if len(rest) > 0:
- self.__plan[0] = ('r', rest)
- else:
- self.__plan.pop(0)
- return result
-
- def read_fd(self):
- """
- Emulate the reading of file descriptor. Returns one from a plan.
-
- It raises InvalidPlan if no socket is planned now.
- """
- fd = self.__get_plan('f')
- self.__plan.pop(0)
- return fd
-
- def sendall(self, data):
- """
- Checks that the data passed are correct according to plan. It raises
- InvalidData if the data differs or InvalidPlan when sendall is not
- expected.
- """
- planned = self.__get_plan('s')
- dlen = len(data)
- prefix, rest = planned[:dlen], planned[dlen:]
- if prefix != data:
- raise InvalidData('Expected "' + str(prefix)+ '", got "' +
- str(data) + '"')
- if len(rest) > 0:
- self.__plan[0] = ('s', rest)
- else:
- self.__plan.pop(0)
-
- def all_used(self):
- """
- Returns if the whole plan was consumed.
- """
- return len(self.__plan) == 0
-
-class ParserTests(unittest.TestCase):
- """
- Testcases for the Parser class.
-
- A lot of these test could be done by
- `with self.assertRaises(CreatorError) as cm`. But some versions of python
- take the scope wrong and don't work, so we use the primitive way of
- try-except.
- """
- def __terminate(self):
- creator = FakeCreator([('s', b'T'), ('r', b'')])
- parser = Parser(creator)
- self.assertEqual(None, parser.terminate())
- self.assertTrue(creator.all_used())
- return parser
-
- def test_terminate(self):
- """
- Test if the command to terminate is correct and it waits for reading the
- EOF.
- """
- self.__terminate()
-
- def __terminate_raises(self, parser):
- """
- Check that terminate() raises a fatal exception.
- """
- try:
- parser.terminate()
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_terminate_error1(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one raises an error when receiving the EOF.
- """
- creator = FakeCreator([('s', b'T'), ('e', None)])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_error2(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one raises an error when sending data.
- """
- creator = FakeCreator([('e', None)])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_error3(self):
- """
- Test it reports an exception when there's error terminating the creator.
- This one sends data when it should have terminated.
- """
- creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
- parser = Parser(creator)
- self.__terminate_raises(parser)
-
- def test_terminate_twice(self):
- """
- Test we can't terminate twice.
- """
- parser = self.__terminate()
- self.__terminate_raises(parser)
-
- def test_crash(self):
- """
- Tests that the parser correctly raises exception when it crashes
- unexpectedly.
- """
- creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- # Is the exception correct?
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_error(self):
- """
- Tests that the parser correctly raises non-fatal exception when
- the socket can not be created.
- """
- # We split the int to see if it can cope with data coming in
- # different packets
- intpart = struct.pack('@i', 42)
- creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
- intpart[:1]), ('r', intpart[1:])])
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- # Is the exception correct?
- self.assertFalse(ce.fatal)
- self.assertEqual(42, ce.errno)
-
- def __error(self, plan):
- creator = FakeCreator(plan)
- parser = Parser(creator)
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(creator.all_used())
- self.assertTrue(ce.fatal)
-
- def test_error_send(self):
- self.__error([('e', None)])
-
- def test_error_recv(self):
- self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
-
- def test_error_read_fd(self):
- self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
-
- def __create(self, addr, socktype, encoded):
- creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
- parser = Parser(creator)
- self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
-
- def test_create1(self):
- self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
-
- def test_create2(self):
- self.__create('2001:db8::', socket.SOCK_STREAM,
- b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
-
- def test_create_terminated(self):
- """
- Test we can't request sockets after it was terminated.
- """
- parser = self.__terminate()
- try:
- parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
- self.fail("Not raised")
- except CreatorError as ce:
- self.assertTrue(ce.fatal)
- self.assertEqual(None, ce.errno)
-
- def test_invalid_socktype(self):
- """
- Test invalid socket type is rejected
- """
- self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
- IPAddr('0.0.0.0'), 42, 'RAW')
-
- def test_invalid_family(self):
- """
- Test it rejects invalid address family.
- """
- # Note: this produces a bad logger output, since this address
- # can not be converted to string, so the original message with
- # placeholders is output. This should not happen in practice, so
- # it is harmless.
- addr = IPAddr('0.0.0.0')
- addr.family = 42
- self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
- addr, 42, socket.SOCK_DGRAM)
-
-class WrapTests(unittest.TestCase):
- """
- Tests for the wrap_socket function.
- """
- def test_wrap(self):
- # We construct two pairs of socket. The receiving side of one pair will
- # be wrapped. Then we send one of the other pair through this pair and
- # check the received one can be used as a socket
-
- # The transport socket
- (t1, t2) = socket.socketpair()
- # The payload socket
- (p1, p2) = socket.socketpair()
-
- t2 = WrappedSocket(t2)
-
- # Transfer the descriptor
- send_fd(t1.fileno(), p1.fileno())
- p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
-
- # Now, pass some data trough the socket
- p1.send(b'A')
- data = p2.recv(1)
- self.assertEqual(b'A', data)
-
- # Test the wrapping didn't hurt the socket's usual methods
- t1.send(b'B')
- data = t2.recv(1)
- self.assertEqual(b'B', data)
- t2.send(b'C')
- data = t1.recv(1)
- self.assertEqual(b'C', data)
-
-if __name__ == '__main__':
- isc.log.init("bind10") # FIXME Should this be needed?
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 8d341cb..824e8a8 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -35,6 +35,7 @@ b10_dhcp6_SOURCES = main.cc
b10_dhcp6_SOURCES += dhcp6.h
b10_dhcp6_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libutil.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/bin/host/Makefile.am b/src/bin/host/Makefile.am
index ec34ce7..a8f96c2 100644
--- a/src/bin/host/Makefile.am
+++ b/src/bin/host/Makefile.am
@@ -13,6 +13,7 @@ CLEANFILES = *.gcno *.gcda
bin_PROGRAMS = b10-host
b10_host_SOURCES = host.cc
b10_host_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+b10_host_LDADD += $(top_builddir)/src/lib/util/libutil.la
b10_host_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
man_MANS = b10-host.1
diff --git a/src/lib/bench/tests/Makefile.am b/src/lib/bench/tests/Makefile.am
index 3ebdf29..3f8a678 100644
--- a/src/lib/bench/tests/Makefile.am
+++ b/src/lib/bench/tests/Makefile.am
@@ -16,6 +16,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(top_builddir)/src/lib/bench/libbench.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(GTEST_LDADD)
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index f9237af..a215c56 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -56,6 +56,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index fbcf9c9..ffedb75 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -36,6 +36,7 @@ run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
diff --git a/src/lib/dns/benchmarks/Makefile.am b/src/lib/dns/benchmarks/Makefile.am
index 8645385..0d7856f 100644
--- a/src/lib/dns/benchmarks/Makefile.am
+++ b/src/lib/dns/benchmarks/Makefile.am
@@ -13,5 +13,6 @@ noinst_PROGRAMS = rdatarender_bench
rdatarender_bench_SOURCES = rdatarender_bench.cc
rdatarender_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+rdatarender_bench_LDADD += $(top_builddir)/src/lib/util/libutil.la
rdatarender_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
rdatarender_bench_LDADD += $(SQLITE_LIBS)
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index 2557965..04a4dc4 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -19,6 +19,7 @@
#include <boost/lexical_cast.hpp>
#include <util/buffer.h>
+#include <util/strutil.h>
#include <util/encode/base64.h>
#include <dns/messagerenderer.h>
@@ -30,6 +31,7 @@ using namespace std;
using namespace boost;
using namespace isc::util;
using namespace isc::util::encode;
+using namespace isc::util::str;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -65,45 +67,6 @@ struct TSIG::TSIGImpl {
const vector<uint8_t> other_data_;
};
-namespace {
-string
-getToken(istringstream& iss, const string& full_input) {
- string token;
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid TSIG text: parse error " <<
- full_input);
- }
- return (token);
-}
-
-// This helper function converts a string token to an *unsigned* integer.
-// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
-// wide to store resulting integers.
-// BitSize is the maximum number of bits that the resulting integer can take.
-// This function first checks whether the given token can be converted to
-// an integer of NumType type. It then confirms the conversion result is
-// within the valid range, i.e., [0, 2^NumType - 1]. The second check is
-// necessary because lexical_cast<T> where T is an unsigned integer type
-// doesn't correctly reject negative numbers when compiled with SunStudio.
-template <typename NumType, int BitSize>
-NumType
-tokenToNum(const string& num_token) {
- NumType num;
- try {
- num = lexical_cast<NumType>(num_token);
- } catch (const boost::bad_lexical_cast& ex) {
- isc_throw(InvalidRdataText, "Invalid TSIG numeric parameter: " <<
- num_token);
- }
- if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
- isc_throw(InvalidRdataText, "Numeric TSIG parameter out of range: " <<
- num);
- }
- return (num);
-}
-}
-
/// \brief Constructor from string.
///
/// \c tsig_str must be formatted as follows:
@@ -148,47 +111,52 @@ tokenToNum(const string& num_token) {
TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
istringstream iss(tsig_str);
- const Name algorithm(getToken(iss, tsig_str));
- const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss,
- tsig_str));
- const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
- const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
-
- const string mac_txt = (macsize > 0) ? getToken(iss, tsig_str) : "";
- vector<uint8_t> mac;
- decodeBase64(mac_txt, mac);
- if (mac.size() != macsize) {
- isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
- }
-
- const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
-
- const string error_txt = getToken(iss, tsig_str);
- int32_t error = 0;
- // XXX: In the initial implementation we hardcode the mnemonics.
- // We'll soon generalize this.
- if (error_txt == "BADSIG") {
- error = 16;
- } else if (error_txt == "BADKEY") {
- error = 17;
- } else if (error_txt == "BADTIME") {
- error = 18;
- } else {
- error = tokenToNum<int32_t, 16>(error_txt);
- }
-
- const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
- const string otherdata_txt = (otherlen > 0) ? getToken(iss, tsig_str) : "";
- vector<uint8_t> other_data;
- decodeBase64(otherdata_txt, other_data);
-
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
- tsig_str);
+ try {
+ const Name algorithm(getToken(iss));
+ const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss));
+ const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss));
+ const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss));
+
+ const string mac_txt = (macsize > 0) ? getToken(iss) : "";
+ vector<uint8_t> mac;
+ decodeBase64(mac_txt, mac);
+ if (mac.size() != macsize) {
+ isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
+ }
+
+ const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss));
+
+ const string error_txt = getToken(iss);
+ int32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "BADSIG") {
+ error = 16;
+ } else if (error_txt == "BADKEY") {
+ error = 17;
+ } else if (error_txt == "BADTIME") {
+ error = 18;
+ } else {
+ error = tokenToNum<int32_t, 16>(error_txt);
+ }
+
+ const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss));
+ const string otherdata_txt = (otherlen > 0) ? getToken(iss) : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+
+ if (!iss.eof()) {
+ isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
+ tsig_str);
+ }
+
+ impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
+ error, other_data);
+
+ } catch (const StringTokenError& ste) {
+ isc_throw(InvalidRdataText, "Invalid TSIG text: " << ste.what() <<
+ ": " << tsig_str);
}
-
- impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
- error, other_data);
}
/// \brief Constructor from wire-format data.
diff --git a/src/lib/dns/rdata/in_1/srv_33.cc b/src/lib/dns/rdata/in_1/srv_33.cc
index c28c435..93b5d4d 100644
--- a/src/lib/dns/rdata/in_1/srv_33.cc
+++ b/src/lib/dns/rdata/in_1/srv_33.cc
@@ -18,6 +18,7 @@
#include <boost/lexical_cast.hpp>
#include <util/buffer.h>
+#include <util/strutil.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
@@ -26,6 +27,7 @@
using namespace std;
using namespace isc::util;
+using namespace isc::util::str;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -44,39 +46,6 @@ struct SRVImpl {
Name target_;
};
-namespace {
-string
-getToken(istringstream& iss, const string& full_input) {
- string token;
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SRV text: parse error " <<
- full_input);
- }
- return (token);
-}
-
-// This helper function converts a string token to an *unsigned* integer.
-// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
-// wide to store resulting integers.
-template <typename NumType, int BitSize>
-NumType
-tokenToNum(const string& num_token) {
- NumType num;
- try {
- num = boost::lexical_cast<NumType>(num_token);
- } catch (const boost::bad_lexical_cast& ex) {
- isc_throw(InvalidRdataText, "Invalid SRV numeric parameter: " <<
- num_token);
- }
- if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
- isc_throw(InvalidRdataText, "Numeric SRV parameter out of range: " <<
- num);
- }
- return (num);
-}
-}
-
/// \brief Constructor from string.
///
/// \c srv_str must be formatted as follows:
@@ -103,17 +72,22 @@ SRV::SRV(const string& srv_str) :
{
istringstream iss(srv_str);
- const int32_t priority = tokenToNum<int32_t, 16>(getToken(iss, srv_str));
- const int32_t weight = tokenToNum<int32_t, 16>(getToken(iss, srv_str));
- const int32_t port = tokenToNum<int32_t, 16>(getToken(iss, srv_str));
- const Name targetname(getToken(iss, srv_str));
-
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for SRV RDATA: " <<
- srv_str);
+ try {
+ const int32_t priority = tokenToNum<int32_t, 16>(getToken(iss));
+ const int32_t weight = tokenToNum<int32_t, 16>(getToken(iss));
+ const int32_t port = tokenToNum<int32_t, 16>(getToken(iss));
+ const Name targetname(getToken(iss));
+
+ if (!iss.eof()) {
+ isc_throw(InvalidRdataText, "Unexpected input for SRV RDATA: " <<
+ srv_str);
+ }
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+ } catch (const StringTokenError& ste) {
+ isc_throw(InvalidRdataText, "Invalid SRV text: " <<
+ ste.what() << ": " << srv_str);
}
-
- impl_ = new SRVImpl(priority, weight, port, targetname);
}
/// \brief Constructor from wire-format data.
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index b391c1e..d94100b 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = datasrc cc config log net notify util testutils acl
+SUBDIRS = datasrc cc config log net notify util testutils acl bind10
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/bind10/Makefile.am b/src/lib/python/isc/bind10/Makefile.am
new file mode 100644
index 0000000..43a7605
--- /dev/null
+++ b/src/lib/python/isc/bind10/Makefile.am
@@ -0,0 +1,4 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py sockcreator.py
+pythondir = $(pyexecdir)/isc/bind10
diff --git a/src/lib/python/isc/bind10/__init__.py b/src/lib/python/isc/bind10/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/bind10/sockcreator.py b/src/lib/python/isc/bind10/sockcreator.py
new file mode 100644
index 0000000..9fcc74e
--- /dev/null
+++ b/src/lib/python/isc/bind10/sockcreator.py
@@ -0,0 +1,226 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS 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.
+
+import socket
+import struct
+import os
+import subprocess
+from bind10_messages import *
+from libutil_io_python import recv_fd
+
+logger = isc.log.Logger("boss")
+
+"""
+Module that comunicates with the privileged socket creator (b10-sockcreator).
+"""
+
+class CreatorError(Exception):
+ """
+ Exception for socket creator related errors.
+
+ It has two members: fatal and errno and they are just holding the values
+ passed to the __init__ function.
+ """
+
+ def __init__(self, message, fatal, errno=None):
+ """
+ Creates the exception. The message argument is the usual string.
+ The fatal one tells if the error is fatal (eg. the creator crashed)
+ and errno is the errno value returned from socket creator, if
+ applicable.
+ """
+ Exception.__init__(self, message)
+ self.fatal = fatal
+ self.errno = errno
+
+class Parser:
+ """
+ This class knows the sockcreator language. It creates commands, sends them
+ and receives the answers and parses them.
+
+ It does not start it, the communication channel must be provided.
+
+ In theory, anything here can throw a fatal CreatorError exception, but it
+ happens only in case something like the creator process crashes. Any other
+ occasions are mentioned explicitly.
+ """
+
+ def __init__(self, creator_socket):
+ """
+ Creates the parser. The creator_socket is socket to the socket creator
+ process that will be used for communication. However, the object must
+ have a read_fd() method to read the file descriptor. This slightly
+ unusual trick with modifying an object is used to easy up testing.
+
+ You can use WrappedSocket in production code to add the method to any
+ ordinary socket.
+ """
+ self.__socket = creator_socket
+ logger.info(BIND10_SOCKCREATOR_INIT)
+
+ def terminate(self):
+ """
+ Asks the creator process to terminate and waits for it to close the
+ socket. Does not return anything. Raises a CreatorError if there is
+ still data on the socket, if there is an error closing the socket,
+ or if the socket had already been closed.
+ """
+ if self.__socket is None:
+ raise CreatorError('Terminated already', True)
+ logger.info(BIND10_SOCKCREATOR_TERMINATE)
+ try:
+ self.__socket.sendall(b'T')
+ # Wait for an EOF - it will return empty data
+ eof = self.__socket.recv(1)
+ if len(eof) != 0:
+ raise CreatorError('Protocol error - data after terminated',
+ True)
+ self.__socket = None
+ except socket.error as se:
+ self.__socket = None
+ raise CreatorError(str(se), True)
+
+ def get_socket(self, address, port, socktype):
+ """
+ Asks the socket creator process to create a socket. Pass an address
+ (the isc.net.IPaddr object), port number and socket type (either
+ string "UDP", "TCP" or constant socket.SOCK_DGRAM or
+ socket.SOCK_STREAM.
+
+ Blocks until it is provided by the socket creator process (which
+ should be fast, as it is on localhost) and returns the file descriptor
+ number. It raises a CreatorError exception if the creation fails.
+ """
+ if self.__socket is None:
+ raise CreatorError('Socket requested on terminated creator', True)
+ # First, assemble the request from parts
+ logger.info(BIND10_SOCKET_GET, address, port, socktype)
+ data = b'S'
+ if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
+ data += b'U'
+ elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
+ data += b'T'
+ else:
+ raise ValueError('Unknown socket type: ' + str(socktype))
+ if address.family == socket.AF_INET:
+ data += b'4'
+ elif address.family == socket.AF_INET6:
+ data += b'6'
+ else:
+ raise ValueError('Unknown address family in address')
+ data += struct.pack('!H', port)
+ data += address.addr
+ try:
+ # Send the request
+ self.__socket.sendall(data)
+ answer = self.__socket.recv(1)
+ if answer == b'S':
+ # Success!
+ result = self.__socket.read_fd()
+ logger.info(BIND10_SOCKET_CREATED, result)
+ return result
+ elif answer == b'E':
+ # There was an error, read the error as well
+ error = self.__socket.recv(1)
+ errno = struct.unpack('i',
+ self.__read_all(len(struct.pack('i',
+ 0))))
+ if error == b'S':
+ cause = 'socket'
+ elif error == b'B':
+ cause = 'bind'
+ else:
+ self.__socket = None
+ logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
+ raise CreatorError('Unknown error cause' + str(answer), True)
+ logger.error(BIND10_SOCKET_ERROR, cause, errno[0],
+ os.strerror(errno[0]))
+ raise CreatorError('Error creating socket on ' + cause, False,
+ errno[0])
+ else:
+ self.__socket = None
+ logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
+ raise CreatorError('Unknown response ' + str(answer), True)
+ except socket.error as se:
+ self.__socket = None
+ logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se))
+ raise CreatorError(str(se), True)
+
+ def __read_all(self, length):
+ """
+ Keeps reading until length data is read or EOF or error happens.
+
+ EOF is considered error as well and throws a CreatorError.
+ """
+ result = b''
+ while len(result) < length:
+ data = self.__socket.recv(length - len(result))
+ if len(data) == 0:
+ self.__socket = None
+ logger.fatal(BIND10_SOCKCREATOR_EOF)
+ raise CreatorError('Unexpected EOF', True)
+ result += data
+ return result
+
+class WrappedSocket:
+ """
+ This class wraps a socket and adds a read_fd method, so it can be used
+ for the Parser class conveniently. It simply copies all its guts into
+ itself and implements the method.
+ """
+ def __init__(self, socket):
+ # Copy whatever can be copied from the socket
+ for name in dir(socket):
+ if name not in ['__class__', '__weakref__']:
+ setattr(self, name, getattr(socket, name))
+ # Keep the socket, so we can prevent it from being garbage-collected
+ # and closed before we are removed ourself
+ self.__orig_socket = socket
+
+ def read_fd(self):
+ """
+ Read the file descriptor from the socket.
+ """
+ return recv_fd(self.fileno())
+
+# FIXME: Any idea how to test this? Starting an external process doesn't sound
+# OK
+class Creator(Parser):
+ """
+ This starts the socket creator and allows asking for the sockets.
+ """
+ def __init__(self, path):
+ (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ # Popen does not like, for some reason, having the same socket for
+ # stdin as well as stdout, so we dup it before passing it there.
+ remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX,
+ socket.SOCK_STREAM)
+ env = os.environ
+ env['PATH'] = path
+ self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
+ stdin=remote.fileno(),
+ stdout=remote2.fileno())
+ remote.close()
+ remote2.close()
+ Parser.__init__(self, WrappedSocket(local))
+
+ def pid(self):
+ return self.__process.pid
+
+ def kill(self):
+ logger.warn(BIND10_SOCKCREATOR_KILL)
+ if self.__process is not None:
+ self.__process.kill()
+ self.__process = None
diff --git a/src/lib/python/isc/bind10/tests/Makefile.am b/src/lib/python/isc/bind10/tests/Makefile.am
new file mode 100644
index 0000000..f498b86
--- /dev/null
+++ b/src/lib/python/isc/bind10/tests/Makefile.am
@@ -0,0 +1,29 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+#PYTESTS = args_test.py bind10_test.py
+# NOTE: this has a generated test found in the builddir
+PYTESTS = sockcreator_test.py
+
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+ BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
diff --git a/src/lib/python/isc/bind10/tests/sockcreator_test.py b/src/lib/python/isc/bind10/tests/sockcreator_test.py
new file mode 100644
index 0000000..4453184
--- /dev/null
+++ b/src/lib/python/isc/bind10/tests/sockcreator_test.py
@@ -0,0 +1,327 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS 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.
+
+# This test file is generated .py.in -> .py just to be in the build dir,
+# same as the rest of the tests. Saves a lot of stuff in makefile.
+
+"""
+Tests for the bind10.sockcreator module.
+"""
+
+import unittest
+import struct
+import socket
+from isc.net.addr import IPAddr
+import isc.log
+from libutil_io_python import send_fd
+from isc.bind10.sockcreator import Parser, CreatorError, WrappedSocket
+
+class FakeCreator:
+ """
+ Class emulating the socket to the socket creator. It can be given expected
+ data to receive (and check) and responses to give to the Parser class
+ during testing.
+ """
+
+ class InvalidPlan(Exception):
+ """
+ Raised when someone wants to recv when sending is planned or vice
+ versa.
+ """
+ pass
+
+ class InvalidData(Exception):
+ """
+ Raises when the data passed to sendall are not the same as expected.
+ """
+ pass
+
+ def __init__(self, plan):
+ """
+ Create the object. The plan variable contains list of expected actions,
+ in form:
+
+ [('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
+ , ('d', 'File descriptor number to return from read_sock'), ('e',
+ None), ...]
+
+ It modifies the array as it goes.
+ """
+ self.__plan = plan
+
+ def __get_plan(self, expected):
+ if len(self.__plan) == 0:
+ raise InvalidPlan('Nothing more planned')
+ (kind, data) = self.__plan[0]
+ if kind == 'e':
+ self.__plan.pop(0)
+ raise socket.error('False socket error')
+ if kind != expected:
+ raise InvalidPlan('Planned ' + kind + ', but ' + expected +
+ 'requested')
+ return data
+
+ def recv(self, maxsize):
+ """
+ Emulate recv. Returs maxsize bytes from the current recv plan. If
+ there are data left from previous recv call, it is used first.
+
+ If no recv is planned, raises InvalidPlan.
+ """
+ data = self.__get_plan('r')
+ result, rest = data[:maxsize], data[maxsize:]
+ if len(rest) > 0:
+ self.__plan[0] = ('r', rest)
+ else:
+ self.__plan.pop(0)
+ return result
+
+ def read_fd(self):
+ """
+ Emulate the reading of file descriptor. Returns one from a plan.
+
+ It raises InvalidPlan if no socket is planned now.
+ """
+ fd = self.__get_plan('f')
+ self.__plan.pop(0)
+ return fd
+
+ def sendall(self, data):
+ """
+ Checks that the data passed are correct according to plan. It raises
+ InvalidData if the data differs or InvalidPlan when sendall is not
+ expected.
+ """
+ planned = self.__get_plan('s')
+ dlen = len(data)
+ prefix, rest = planned[:dlen], planned[dlen:]
+ if prefix != data:
+ raise InvalidData('Expected "' + str(prefix)+ '", got "' +
+ str(data) + '"')
+ if len(rest) > 0:
+ self.__plan[0] = ('s', rest)
+ else:
+ self.__plan.pop(0)
+
+ def all_used(self):
+ """
+ Returns if the whole plan was consumed.
+ """
+ return len(self.__plan) == 0
+
+class ParserTests(unittest.TestCase):
+ """
+ Testcases for the Parser class.
+
+ A lot of these test could be done by
+ `with self.assertRaises(CreatorError) as cm`. But some versions of python
+ take the scope wrong and don't work, so we use the primitive way of
+ try-except.
+ """
+ def __terminate(self):
+ creator = FakeCreator([('s', b'T'), ('r', b'')])
+ parser = Parser(creator)
+ self.assertEqual(None, parser.terminate())
+ self.assertTrue(creator.all_used())
+ return parser
+
+ def test_terminate(self):
+ """
+ Test if the command to terminate is correct and it waits for reading the
+ EOF.
+ """
+ self.__terminate()
+
+ def __terminate_raises(self, parser):
+ """
+ Check that terminate() raises a fatal exception.
+ """
+ try:
+ parser.terminate()
+ self.fail("Not raised")
+ except CreatorError as ce:
+ self.assertTrue(ce.fatal)
+ self.assertEqual(None, ce.errno)
+
+ def test_terminate_error1(self):
+ """
+ Test it reports an exception when there's error terminating the creator.
+ This one raises an error when receiving the EOF.
+ """
+ creator = FakeCreator([('s', b'T'), ('e', None)])
+ parser = Parser(creator)
+ self.__terminate_raises(parser)
+
+ def test_terminate_error2(self):
+ """
+ Test it reports an exception when there's error terminating the creator.
+ This one raises an error when sending data.
+ """
+ creator = FakeCreator([('e', None)])
+ parser = Parser(creator)
+ self.__terminate_raises(parser)
+
+ def test_terminate_error3(self):
+ """
+ Test it reports an exception when there's error terminating the creator.
+ This one sends data when it should have terminated.
+ """
+ creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
+ parser = Parser(creator)
+ self.__terminate_raises(parser)
+
+ def test_terminate_twice(self):
+ """
+ Test we can't terminate twice.
+ """
+ parser = self.__terminate()
+ self.__terminate_raises(parser)
+
+ def test_crash(self):
+ """
+ Tests that the parser correctly raises exception when it crashes
+ unexpectedly.
+ """
+ creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
+ parser = Parser(creator)
+ try:
+ parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+ self.fail("Not raised")
+ except CreatorError as ce:
+ self.assertTrue(creator.all_used())
+ # Is the exception correct?
+ self.assertTrue(ce.fatal)
+ self.assertEqual(None, ce.errno)
+
+ def test_error(self):
+ """
+ Tests that the parser correctly raises non-fatal exception when
+ the socket can not be created.
+ """
+ # We split the int to see if it can cope with data coming in
+ # different packets
+ intpart = struct.pack('@i', 42)
+ creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
+ intpart[:1]), ('r', intpart[1:])])
+ parser = Parser(creator)
+ try:
+ parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+ self.fail("Not raised")
+ except CreatorError as ce:
+ self.assertTrue(creator.all_used())
+ # Is the exception correct?
+ self.assertFalse(ce.fatal)
+ self.assertEqual(42, ce.errno)
+
+ def __error(self, plan):
+ creator = FakeCreator(plan)
+ parser = Parser(creator)
+ try:
+ parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
+ self.fail("Not raised")
+ except CreatorError as ce:
+ self.assertTrue(creator.all_used())
+ self.assertTrue(ce.fatal)
+
+ def test_error_send(self):
+ self.__error([('e', None)])
+
+ def test_error_recv(self):
+ self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
+
+ def test_error_read_fd(self):
+ self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
+
+ def __create(self, addr, socktype, encoded):
+ creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
+ parser = Parser(creator)
+ self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
+
+ def test_create1(self):
+ self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
+
+ def test_create2(self):
+ self.__create('2001:db8::', socket.SOCK_STREAM,
+ b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
+
+ def test_create_terminated(self):
+ """
+ Test we can't request sockets after it was terminated.
+ """
+ parser = self.__terminate()
+ try:
+ parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+ self.fail("Not raised")
+ except CreatorError as ce:
+ self.assertTrue(ce.fatal)
+ self.assertEqual(None, ce.errno)
+
+ def test_invalid_socktype(self):
+ """
+ Test invalid socket type is rejected
+ """
+ self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
+ IPAddr('0.0.0.0'), 42, 'RAW')
+
+ def test_invalid_family(self):
+ """
+ Test it rejects invalid address family.
+ """
+ # Note: this produces a bad logger output, since this address
+ # can not be converted to string, so the original message with
+ # placeholders is output. This should not happen in practice, so
+ # it is harmless.
+ addr = IPAddr('0.0.0.0')
+ addr.family = 42
+ self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
+ addr, 42, socket.SOCK_DGRAM)
+
+class WrapTests(unittest.TestCase):
+ """
+ Tests for the wrap_socket function.
+ """
+ def test_wrap(self):
+ # We construct two pairs of socket. The receiving side of one pair will
+ # be wrapped. Then we send one of the other pair through this pair and
+ # check the received one can be used as a socket
+
+ # The transport socket
+ (t1, t2) = socket.socketpair()
+ # The payload socket
+ (p1, p2) = socket.socketpair()
+
+ t2 = WrappedSocket(t2)
+
+ # Transfer the descriptor
+ send_fd(t1.fileno(), p1.fileno())
+ p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
+
+ # Now, pass some data trough the socket
+ p1.send(b'A')
+ data = p2.recv(1)
+ self.assertEqual(b'A', data)
+
+ # Test the wrapping didn't hurt the socket's usual methods
+ t1.send(b'B')
+ data = t2.recv(1)
+ self.assertEqual(b'B', data)
+ t2.send(b'C')
+ data = t1.recv(1)
+ self.assertEqual(b'C', data)
+
+if __name__ == '__main__':
+ isc.log.init("bind10") # FIXME Should this be needed?
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 8150729..4fa9d58 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -425,7 +425,7 @@ class UIModuleCCSession(MultiConfigData):
raise ModuleCCSessionError("Bad config version")
self._set_current_config(config)
- def _add_value_to_list(self, identifier, value):
+ def _add_value_to_list(self, identifier, value, module_spec):
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
@@ -491,7 +491,7 @@ class UIModuleCCSession(MultiConfigData):
if set_value_str is not None:
value_str += set_value_str
value = isc.cc.data.parse_value_str(value_str)
- self._add_value_to_list(identifier, value)
+ self._add_value_to_list(identifier, value, module_spec)
elif 'named_set_item_spec' in module_spec:
item_name = None
item_value = None
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index c820ad9..351c8e6 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -745,6 +745,8 @@ class TestUIModuleCCSession(unittest.TestCase):
uccs.remove_value, "Spec2/item5[123]", None)
uccs.remove_value("Spec2/item5[0]", None)
self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
+ uccs.add_value("Spec2/item5", None);
+ self.assertEqual({'Spec2': {'item5': ['']}}, uccs._local_changes)
def test_add_remove_value_named_set(self):
fake_conn = fakeUIConn()
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index ee311a6..cf05d9b 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/tests/Makefile.am
@@ -31,6 +31,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc
index 161f9ac..ed7fc9b 100644
--- a/src/lib/util/strutil.cc
+++ b/src/lib/util/strutil.cc
@@ -132,6 +132,17 @@ format(const std::string& format, const std::vector<std::string>& args) {
return (result);
}
+std::string
+getToken(std::istringstream& iss) {
+ string token;
+ iss >> token;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(StringTokenError, "could not read token from string");
+ }
+ return (token);
+}
+
+
} // namespace str
} // namespace util
} // namespace isc
diff --git a/src/lib/util/strutil.h b/src/lib/util/strutil.h
index e044c15..021c236 100644
--- a/src/lib/util/strutil.h
+++ b/src/lib/util/strutil.h
@@ -18,7 +18,10 @@
#include <algorithm>
#include <cctype>
#include <string>
+#include <sstream>
#include <vector>
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
namespace isc {
namespace util {
@@ -26,6 +29,16 @@ namespace str {
/// \brief A Set of C++ Utilities for Manipulating Strings
+///
+/// \brief A standard string util exception that is thrown if getToken or
+/// numToToken are called with bad input data
+///
+class StringTokenError : public Exception {
+public:
+ StringTokenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// \brief Normalize Backslash
///
/// Only relevant to Windows, this replaces all "\" in a string with "/" and
@@ -140,6 +153,55 @@ std::string format(const std::string& format,
const std::vector<std::string>& args);
+/// \brief Returns one token from the given stringstream
+///
+/// Using the >> operator, with basic error checking
+///
+/// \exception StringTokenError if the token cannot be read from the stream
+///
+/// \param iss stringstream to read one token from
+///
+/// \return the first token read from the stringstream
+std::string getToken(std::istringstream& iss);
+
+/// \brief Converts a string token to an *unsigned* integer.
+///
+/// The value is converted using a lexical cast, with error and bounds
+/// checking.
+///
+/// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
+/// wide to store resulting integers.
+///
+/// BitSize is the maximum number of bits that the resulting integer can take.
+/// This function first checks whether the given token can be converted to
+/// an integer of NumType type. It then confirms the conversion result is
+/// within the valid range, i.e., [0, 2^BitSize - 1]. The second check is
+/// necessary because lexical_cast<T> where T is an unsigned integer type
+/// doesn't correctly reject negative numbers when compiled with SunStudio.
+///
+/// \exception StringTokenError if the value is out of range, or if it
+/// could not be converted
+///
+/// \param num_token the string token to convert
+///
+/// \return the converted value, of type NumType
+template <typename NumType, int BitSize>
+NumType
+tokenToNum(const std::string& num_token) {
+ NumType num;
+ try {
+ num = boost::lexical_cast<NumType>(num_token);
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(StringTokenError, "Invalid SRV numeric parameter: " <<
+ num_token);
+ }
+ if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
+ isc_throw(StringTokenError, "Numeric SRV parameter out of range: " <<
+ num);
+ }
+ return (num);
+}
+
} // namespace str
} // namespace util
} // namespace isc
diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc
index cd3a9ca..74bc17d 100644
--- a/src/lib/util/tests/strutil_unittest.cc
+++ b/src/lib/util/tests/strutil_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <stdint.h>
+
#include <string>
#include <gtest/gtest.h>
@@ -22,17 +24,9 @@ using namespace isc;
using namespace isc::util;
using namespace std;
-class StringUtilTest : public ::testing::Test {
-protected:
- StringUtilTest()
- {
- }
-};
-
-
// Check for slash replacement
-TEST_F(StringUtilTest, Slash) {
+TEST(StringUtilTest, Slash) {
string instring = "";
isc::util::str::normalizeSlash(instring);
@@ -49,7 +43,7 @@ TEST_F(StringUtilTest, Slash) {
// Check that leading and trailing space trimming works
-TEST_F(StringUtilTest, Trim) {
+TEST(StringUtilTest, Trim) {
// Empty and full string.
EXPECT_EQ("", isc::util::str::trim(""));
@@ -71,7 +65,7 @@ TEST_F(StringUtilTest, Trim) {
// returned vector; if not as expected, the following references may be invalid
// so should not be used.
-TEST_F(StringUtilTest, Tokens) {
+TEST(StringUtilTest, Tokens) {
vector<string> result;
// Default delimiters
@@ -157,7 +151,7 @@ TEST_F(StringUtilTest, Tokens) {
// Changing case
-TEST_F(StringUtilTest, ChangeCase) {
+TEST(StringUtilTest, ChangeCase) {
string mixed("abcDEFghiJKLmno123[]{=+--+]}");
string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
string lower("abcdefghijklmno123[]{=+--+]}");
@@ -173,7 +167,7 @@ TEST_F(StringUtilTest, ChangeCase) {
// Formatting
-TEST_F(StringUtilTest, Formatting) {
+TEST(StringUtilTest, Formatting) {
vector<string> args;
args.push_back("arg1");
@@ -213,3 +207,63 @@ TEST_F(StringUtilTest, Formatting) {
string format9 = "%s %s";
EXPECT_EQ(format9, isc::util::str::format(format9, args));
}
+
+TEST(StringUtilTest, getToken) {
+ string s("a b c");
+ istringstream ss(s);
+ EXPECT_EQ("a", isc::util::str::getToken(ss));
+ EXPECT_EQ("b", isc::util::str::getToken(ss));
+ EXPECT_EQ("c", isc::util::str::getToken(ss));
+ EXPECT_THROW(isc::util::str::getToken(ss), isc::util::str::StringTokenError);
+}
+
+int32_t tokenToNumCall_32_16(const string& token) {
+ return isc::util::str::tokenToNum<int32_t, 16>(token);
+}
+
+int16_t tokenToNumCall_16_8(const string& token) {
+ return isc::util::str::tokenToNum<int16_t, 8>(token);
+}
+
+TEST(StringUtilTest, tokenToNum) {
+ uint32_t num32 = tokenToNumCall_32_16("0");
+ EXPECT_EQ(0, num32);
+ num32 = tokenToNumCall_32_16("123");
+ EXPECT_EQ(123, num32);
+ num32 = tokenToNumCall_32_16("65535");
+ EXPECT_EQ(65535, num32);
+
+ EXPECT_THROW(tokenToNumCall_32_16(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("65536"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1234567890"),
+ isc::util::str::StringTokenError);
+
+ uint16_t num16 = tokenToNumCall_16_8("123");
+ EXPECT_EQ(123, num16);
+ num16 = tokenToNumCall_16_8("0");
+ EXPECT_EQ(0, num16);
+ num16 = tokenToNumCall_16_8("255");
+ EXPECT_EQ(255, num16);
+
+ EXPECT_THROW(tokenToNumCall_16_8(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("256"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1234567890"),
+ isc::util::str::StringTokenError);
+
+}
More information about the bind10-changes
mailing list