ICP datagrams look like this::

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |     Opcode    |    Version    |         Message Length        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         Request Number                        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            Options                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          Option Data                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                       Sender Host Address                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    |                            Payload                            |
    /                                                               /
    /                                                               /
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The handle_request function tests datagrams for correctness, uses the check_url
function it is passed to determine what response to send, and packs up the
response.

This is the format for the payload of a query::

     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                     Requester Host Address                    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    /                       Null-Terminated URL                     /
    /                                                               /
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

We'll want to set up some stuff for the testig later.

    >>> import struct
    >>> import pprint
    >>> import zc.icp
    >>> query = zc.icp.HEADER_LAYOUT + zc.icp.QUERY_LAYOUT
    >>> response = zc.icp.HEADER_LAYOUT + zc.icp.RESPONSE_LAYOUT
    >>> def checker(url):
    ...     return 'ICP_OP_HIT'
    >>> import logging
    >>> import sys
    >>> stdout_log = logging.StreamHandler(sys.stdout)
    >>> stdout_log.setFormatter(logging.Formatter("LOGGER: %(message)s"))
    >>> logging.getLogger('').addHandler(stdout_log)

If the query is too short to have a proper header, a generic error will be
returned. the length will have the length of the sent datagram.

    >>> truncated_header = '!BBH'
    >>> truncated = struct.pack(
    ...     truncated_header, 1, 2, struct.calcsize(truncated_header))
    >>> struct.unpack(
    ...     response % 1,
    ...     zc.icp.handle_request(truncated, checker))
    LOGGER: Error unpacking ICP header:
        '\x01\x02\x00\x04'
    (4, 2, 4, 0L, 0L, 0L, 0L, '\x00')

The header is 20 bytes long, and the payload has an 4 byte leader for the
requester's ip and is null terminated, so a query datagram that is less than 21
bytes long is malformed.

    >>> no_payload = zc.icp.HEADER_LAYOUT
    >>> header = struct.pack(
    ...     no_payload, 1, 2, struct.calcsize(no_payload), 0xDEADBEEF, 0, 0, 0)
    >>> struct.unpack(
    ...     response % 1,
    ...     zc.icp.handle_request(header, checker))
    LOGGER: Query is not long enough:
        '\x01\x02\x00\x14\xde\xad\xbe\xef...\x00\x00\x00'
    (4, 2, 21, 3735928559L, 0L, 0L, 0L, '\x00')

If the query url is not NULL terminated, this is an error.

    >>> url = 'http://metrowestdailynews.com'
    >>> format = query % len(url)
    >>> datagram = struct.pack(
    ...     format,
    ...     1, 2, struct.calcsize(format), 0xDEADBEEF, 0, 0, 0, 0, url)
    >>> format = response % len(url)
    >>> struct.unpack(
    ...     format,
    ...     zc.icp.handle_request(datagram, checker))
    LOGGER: URL in ICP query is not null-terminated:
        '\x01\x02\x005\xde\xad\xbe\xef\x00\x00...http://metrowestdailynews.com'
    (4, 2, 49, 3735928559L, 0L, 0L, 0L, 'http://metrowestdailynews.com')

If the query is valid, then the determination of whether the request is a
hit or not is delegated to the passed function.  This function should accept
one argument, the url we are looking for.

    >>> url = 'http://metrowestdailynews.com\0'
    >>> format = query % len(url)
    >>> datagram = struct.pack(
    ...     format,
    ...     1, 2, struct.calcsize(format), 0xDEADBEEF, 0, 0, 0, 0, url)
    >>> format = response % len(url)
    >>> struct.unpack(
    ...     format,
    ...     zc.icp.handle_request(datagram, checker))
    (2, 2, 50, 3735928559L, 0L, 0L, 0L, 'http://metrowestdailynews.com\x00')
