/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 * 
 */
package org.apache.directory.server.operations.search;


import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsResponseControl;

import org.apache.directory.api.asn1.EncoderException;
import org.apache.directory.api.ldap.codec.api.LdapApiService;
import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.message.Control;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchResultDone;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.message.controls.PagedResults;
import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.util.JndiUtils;
import org.apache.directory.api.util.Network;
import org.apache.directory.api.util.Strings;
import org.apache.directory.ldap.client.api.EntryCursorImpl;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifs;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.ApacheDSTestExtension;
import org.apache.directory.server.ldap.LdapServer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;


/**
 * Test the PagedSearchControl. The following tables covers all the
 * possible cases for both an admin and a simple user, combining the
 * Server SizeLimit (SL), the requested SizeLimit (RL) and the paged
 * size limit (PL). The 'X' column tells if we are supposed to receive
 * a SizeLimitExceededException.<br>
 * <br>
 * Administrator<br>
 * <pre>
 * +-------+----+----+----+---------------------+----+---+<br>
 * |test   | SL | RL | PL | Nb of responses     | nb | X |<br>
 * +-------+----+----+----+---------------------+----+---+<br>
 * |test1  | 0  | 0  | 3  | 4 ( 3 + 3 + 3 + 1 ) | 10 |   |<br>
 * |test2  | 0  | 0  | 5  | 2 ( 5 + 5 )         | 10 |   |<br>
 * |test3  | 3  | 0  | 5  | 2 ( 5 + 5 )         | 10 |   |<br>
 * |test4  | 0  | 3  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test5  | 5  | 0  | 3  | 4 ( 3 + 3 + 3 + 1 ) | 10 |   |<br>
 * |test6  | 0  | 9  | 5  | 2 ( 5 + 4 )         | 5  | Y |<br>
 * |test7  | 5  | 0  | 5  | 2 ( 5 + 5 )         | 10 |   |<br>
 * |test8  | 0  | 5  | 5  | 1 ( 5 )             | 5  | Y |<br>
 * |test9  | 5  | 4  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test10 | 4  | 5  | 3  | 2 ( 3 + 2 )         | 5  | Y |<br>
 * |test11 | 5  | 3  | 4  | 1 ( 3 )             | 3  | Y |<br>
 * |test12 | 5  | 4  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test13 | 4  | 5  | 3  | 2 ( 3 + 2 )         | 5  | Y |<br>
 * |test14 | 4  | 3  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test15 | 3  | 5  | 4  | 2 ( 4 + 1 )         | 5  | Y |<br>
 * |test16 | 3  | 4  | 5  | 1 ( 4 )             | 4  | Y |<br>
 * |test17 | 5  | 5  | 5  | 1 ( 5 )             | 5  | Y |<br>
 * +-------+----+----+----+---------------------+----+---+<br>
 * <br>
 * Simple user<br>
 * <br>
 * +-------+----+----+----+---------------------+----+---+<br>
 * |test   | SL | RL | PL | Nb of responses     | nb | X |<br>
 * +-------+----+----+----+---------------------+----+---+<br>
 * |test18 | 0  | 0  | 3  | 4 ( 3 + 3 + 3 + 1 ) | 10 |   |<br>
 * |test19 | 0  | 0  | 5  | 2 ( 5 + 5 )         | 10 |   |<br>
 * |test20 | 3  | 0  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test21 | 0  | 3  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test22 | 5  | 0  | 3  | 2 ( 3 + 2 )         | 5  | Y |<br>
 * |test23 | 0  | 9  | 5  | 2 ( 5 + 4 )         | 9  | Y |<br>
 * |test24 | 5  | 0  | 5  | 1 ( 5 )             | 5  | Y |<br>
 * |test25 | 0  | 5  | 5  | 1 ( 5 )             | 5  | Y |<br>
 * |test26 | 5  | 4  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test27 | 4  | 5  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test28 | 5  | 3  | 4  | 1 ( 3 )             | 3  | Y |<br>
 * |test29 | 5  | 4  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test30 | 4  | 5  | 3  | 2 ( 3 + 1 )         | 4  | Y |<br>
 * |test31 | 4  | 3  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test32 | 3  | 5  | 4  | 1 ( 3 )             | 3  | Y |<br>
 * |test33 | 3  | 4  | 5  | 1 ( 3 )             | 3  | Y |<br>
 * |test34 | 5  | 5  | 5  | 1 ( 5 )             | 5  | Y |<br>
 * +-------+----+----+----+---------------------+----+---+<br>
 *</pre>
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
@ExtendWith( { ApacheDSTestExtension.class } )
@CreateLdapServer(
    transports =
        {
            @CreateTransport(protocol = "LDAP")
    })
@ApplyLdifs(
    {
        // Add 10 new entries
        "dn: dc=users,ou=system",
        "objectClass: top",
        "objectClass: domain",
        "dc: users",
        //
        "dn: cn=user0,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user0",
        "sn: user 0",
        //
        "dn: cn=user1,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user1",
        "sn: user 1",
        //
        "dn: cn=user2,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user2",
        "sn: user 2",
        //
        "dn: cn=user3,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user3",
        "sn: user 3",
        //
        "dn: cn=user4,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user4",
        "sn: user 4",
        //
        "dn: cn=user5,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user5",
        "sn: user 5",
        //
        "dn: cn=user6,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user6",
        "sn: user 6",
        //
        "dn: cn=user7,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user7",
        "sn: user 7",
        //
        "dn: cn=user8,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user8",
        "sn: user 8",
        //
        "dn: cn=user9,dc=users,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user9",
        "sn: user 9",
        "",
        // Add another user for non admin tests
        "dn: cn=user,ou=system",
        "objectClass: top",
        "objectClass: person",
        "cn: user",
        "userPassword: secret",
        "sn: user" })
public class PagedSearchIT extends AbstractLdapTestUnit
{
    private LdapApiService codec = LdapApiServiceFactory.getSingleton();


    /**
     * Create the searchControls with a paged size
     * @throws EncoderException on codec failures
     */
    private SearchControls createSearchControls( DirContext ctx, int sizeLimit, int pagedSize )
        throws NamingException, EncoderException
    {
        SearchControls controls = new SearchControls();
        controls.setCountLimit( sizeLimit );
        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        PagedResults pagedSearchControl = new PagedResultsImpl();
        pagedSearchControl.setSize( pagedSize );

        ( ( LdapContext ) ctx ).setRequestControls( JndiUtils.toJndiControls( codec, new Control[]
            { pagedSearchControl } ) );

        return controls;
    }


    /**
     * Create the searchControls with a paged size
     * @throws EncoderException on codec failures
     */
    private void createNextSearchControls( DirContext ctx, byte[] cookie, int pagedSize )
        throws NamingException, EncoderException
    {
        PagedResults pagedSearchControl = new PagedResultsImpl();
        pagedSearchControl.setCookie( cookie );
        pagedSearchControl.setSize( pagedSize );
        ( ( LdapContext ) ctx ).setRequestControls( JndiUtils.toJndiControls( codec, new Control[]
            { pagedSearchControl } ) );
    }


    /**
     * Check that we got the correct result set
     */
    private void checkResults( List<SearchResult> results, int expectedSize ) throws NamingException
    {
        assertEquals( expectedSize, results.size() );
        Set<String> expected = new HashSet<String>();

        for ( int i = 0; i < 10; i++ )
        {
            expected.add( "user" + i );
        }

        // check that we have correctly read all the entries
        for ( int i = 0; i < expectedSize; i++ )
        {
            SearchResult entry = results.get( i );
            String user = ( String ) entry.getAttributes().get( "cn" ).get();
            assertTrue( expected.contains( user ) );

            expected.remove( user );
        }

        assertEquals( 10 - expectedSize, expected.size() );
    }


    /**
     * Do the loop over the entries, until we can't get any more, or until we
     * reach a limit. It will check that we have got all the expected entries.
     * @throws EncoderException  on codec failures
     */
    private void doLoop( DirContext ctx, SearchControls controls, int pagedSizeLimit,
        int expectedLoop, int expectedNbEntries, boolean expectedException ) throws NamingException, EncoderException
    {
        // Loop over all the elements
        int loop = 0;
        boolean hasSizeLimitException = false;
        List<SearchResult> results = new ArrayList<SearchResult>();

        while ( true )
        {
            loop++;
            NamingEnumeration<SearchResult> list = null;

            try
            {
                list = ctx.search( "dc=users,ou=system", "(cn=*)", controls );

                while ( list.hasMore() )
                {
                    SearchResult result = list.next();
                    results.add( result );
                }
            }
            catch ( SizeLimitExceededException e )
            {
                hasSizeLimitException = true;
                break;
            }
            finally
            {
                // Close the NamingEnumeration
                if ( list != null )
                {
                    list.close();
                }
            }

            // Now read the next ones
            javax.naming.ldap.Control[] responseControls = ( ( LdapContext ) ctx ).getResponseControls();

            PagedResultsResponseControl responseControl =
                ( PagedResultsResponseControl ) responseControls[0];
            assertEquals( 0, responseControl.getResultSize() );

            // check if this is over
            byte[] cookie = responseControl.getCookie();

            if ( Strings.isEmpty( cookie ) )
            {
                // If so, exit the loop
                break;
            }

            // Prepare the next iteration
            createNextSearchControls( ctx, responseControl.getCookie(), pagedSizeLimit );
        }

        assertEquals( expectedException, hasSizeLimitException );
        assertEquals( expectedLoop, loop );
        checkResults( results, expectedNbEntries );

        // And close the connection
        closeConnection( ctx );
    }


    /**
     * Close a connection, and wait a bit to be sure it's done
     */
    private void closeConnection( DirContext ctx ) throws NamingException
    {
        if ( ctx != null )
        {
            ctx.close();

            try
            {
                Thread.sleep( 10 );
            }
            catch ( Exception e )
            {
            }
        }
    }


    /**
     * Admin = yes <br>
     * SL = none<br>
     * RL = none<br>
     * PL = 3<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 3 + 3 + 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest1() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 3 );

        doLoop( ctx, controls, 3, 4, 10, false );
    }


    /**
     * Admin = yes <br>
     * SL = none<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 5 + 5 )<br>
     */
    @Test
    public void testPagedSearchtest2() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 2, 10, false );
    }


    /**
     * Admin = yes <br>
     * SL = 3<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 5 + 5 )<br>
     */
    @Test
    public void testPagedSearchTest3() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 2, 10, false );
    }


    /**
     * Admin = yes <br>
     * SL = none<br>
     * RL = 3<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3<br>
     */
    @Test
    public void testPagedSearchTest4() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 3, 5 );

        doLoop( ctx, controls, 5, 1, 3, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = none<br>
     * PL = 3<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 3 + 3 + 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest5() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 3 );

        doLoop( ctx, controls, 3, 4, 10, false );
    }


    /**
     * Admin = yes <br>
     * SL = none<br>
     * RL = 9<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 9 ( 5 + 4 )<br>
     */
    @Test
    public void testPagedSearchTest6() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 9, 5 );

        doLoop( ctx, controls, 5, 2, 9, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 5 + 5 )<br>
     */
    @Test
    public void testPagedSearchtest7() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 2, 10, false );
    }


    /**
     * Admin = yes <br>
     * SL = none<br>
     * RL = 5<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5<br>
     */
    @Test
    public void testPagedSearchTest8() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 5, 5 );

        doLoop( ctx, controls, 5, 1, 5, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = 4<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 2 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchTest9() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 4, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = yes <br>
     * SL = 4<br>
     * RL = 5<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 ( 3 + 2 )<br>
     */
    @Test
    public void testPagedSearchtest10() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 5, 3 );

        doLoop( ctx, controls, 3, 2, 5, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = 3<br>
     * PL = 4<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3<br>
     */
    @Test
    public void testPagedSearchtest11() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 3, 4 );

        doLoop( ctx, controls, 4, 1, 3, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = 4<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 4 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest12() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 4, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = yes <br>
     * SL = 4<br>
     * RL = 5<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 ( 3 + 2 )<br>
     */
    @Test
    public void testPagedSearchtest13() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 5, 3 );

        doLoop( ctx, controls, 3, 2, 5, true );
    }


    /**
     * Admin = yes <br>
     * SL = 4<br>
     * RL = 3<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3 <br>
     */
    @Test
    public void testPagedSearchtest14() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 3, 5 );

        doLoop( ctx, controls, 5, 1, 3, true );
    }


    /**
     * Admin = yes <br>
     * SL = 3<br>
     * RL = 5<br>
     * PL = 4<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 ( 4 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest15() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 5, 4 );

        doLoop( ctx, controls, 4, 2, 5, true );
    }


    /**
     * Admin = yes <br>
     * SL = 3<br>
     * RL = 4<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 4 <br>
     */
    @Test
    public void testPagedSearchtest16() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 4, 5 );

        doLoop( ctx, controls, 5, 1, 4, true );
    }


    /**
     * Admin = yes <br>
     * SL = 5<br>
     * RL = 5<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 <br>
     */
    @Test
    public void testPagedSearchtest17() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer() );
        SearchControls controls = createSearchControls( ctx, 5, 5 );

        doLoop( ctx, controls, 5, 1, 5, true );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = none<br>
     * PL = 3<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 3 + 3 + 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest18() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 3 );

        doLoop( ctx, controls, 3, 4, 10, false );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 ( 5 + 5 )<br>
     */
    @Test
    public void testPagedSearchtest19() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 2, 10, false );
    }


    /**
     * Admin = no <br>
     * SL = 3<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3<br>
     */
    @Test
    public void testPagedSearchTest20() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = 3<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3<br>
     */
    @Test
    public void testPagedSearchTest21() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 3, 5 );

        doLoop( ctx, controls, 5, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = none<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 ( 3 + 2 )<br>
     */
    @Test
    public void testPagedSearchtest22() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 3 );

        doLoop( ctx, controls, 3, 2, 5, true );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = 9<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 9 ( 5 + 4 )<br>
     */
    @Test
    public void testPagedSearchTest23() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 9, 5 );

        doLoop( ctx, controls, 5, 2, 9, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = none<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5<br>
     */
    @Test
    public void testPagedSearchtest24() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 5 );

        doLoop( ctx, controls, 5, 1, 5, true );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = 5<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5<br>
     */
    @Test
    public void testPagedSearchTest25() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 5, 5 );

        doLoop( ctx, controls, 5, 1, 5, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = 4<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 2 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchTest26() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 4, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = no <br>
     * SL = 4<br>
     * RL = 5<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 4 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest27() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 5, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = 3<br>
     * PL = 4<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3<br>
     */
    @Test
    public void testPagedSearchtest28() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 3, 4 );

        doLoop( ctx, controls, 4, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = 4<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 4 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest29() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 4, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = no <br>
     * SL = 4<br>
     * RL = 5<br>
     * PL = 3<br>
     * expected exception : yes<br>
     * expected number of entries returned : 4 ( 3 + 1 )<br>
     */
    @Test
    public void testPagedSearchtest30() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 5, 3 );

        doLoop( ctx, controls, 3, 2, 4, true );
    }


    /**
     * Admin = no <br>
     * SL = 4<br>
     * RL = 3<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3 <br>
     */
    @Test
    public void testPagedSearchtest31() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 4 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 3, 5 );

        doLoop( ctx, controls, 3, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = 3<br>
     * RL = 5<br>
     * PL = 4<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3 <br>
     */
    @Test
    public void testPagedSearchtest32() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 5, 4 );

        doLoop( ctx, controls, 3, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = 3<br>
     * RL = 4<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 3 <br>
     */
    @Test
    public void testPagedSearchtest33() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 3 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 4, 5 );

        doLoop( ctx, controls, 3, 1, 3, true );
    }


    /**
     * Admin = no <br>
     * SL = 5<br>
     * RL = 5<br>
     * PL = 5<br>
     * expected exception : yes<br>
     * expected number of entries returned : 5 <br>
     */
    @Test
    public void testPagedSearchtest34() throws Exception
    {
        getLdapServer().setMaxSizeLimit( 5 );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, 5, 5 );

        doLoop( ctx, controls, 5, 1, 5, true );
    }


    /**
     * Admin = no <br>
     * SL = none<br>
     * RL = none<br>
     * PL = -2<br>
     * expected exception : no<br>
     * expected number of entries returned : 10 <br>
     */
    @Test
    public void testPagedSearchWithNegativePL() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, -2 );

        doLoop( ctx, controls, -2, 1, 10, false );
    }


    /**
     * Do a test with a paged search and send a wrong cookie in the middle
     */
    @Test
    public void testPagedSearchWrongCookie() throws Exception
    {
        LdapNetworkConnection connection = new LdapNetworkConnection( Network.LOOPBACK_HOSTNAME, getLdapServer().getPort() );
        connection.bind( "uid=admin,ou=system", "secret" );

        SearchControls controls = new SearchControls();
        controls.setCountLimit( ( int ) LdapServer.NO_SIZE_LIMIT );
        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        PagedResults pagedSearchControl = new PagedResultsImpl();
        pagedSearchControl.setSize( 3 );

        // Loop over all the elements
        int loop = 0;
        List<Entry> results = new ArrayList<Entry>();
        boolean hasUnwillingToPerform = false;

        while ( true )
        {
            loop++;

            EntryCursor cursor = null;

            try
            {
                SearchRequest searchRequest = new SearchRequestImpl();
                searchRequest.setBase( new Dn( "ou=system" ) );
                searchRequest.setFilter( "(ObjectClass=*)" );
                searchRequest.setScope( SearchScope.SUBTREE );
                searchRequest.addAttributes( "*" );
                searchRequest.addControl( pagedSearchControl );

                cursor = new EntryCursorImpl( connection.search( searchRequest ) );

                int i = 0;

                while ( cursor.next() )
                {
                    Entry result = cursor.get();
                    results.add( result );
                    ++i;
                }

                SearchResultDone result = cursor.getSearchResultDone();
                pagedSearchControl = ( PagedResults ) result.getControl( PagedResults.OID );

                if ( result.getLdapResult().getResultCode() == ResultCodeEnum.UNWILLING_TO_PERFORM )
                {
                    hasUnwillingToPerform = true;
                    break;
                }
            }
            finally
            {
                if ( cursor != null )
                {
                    cursor.close();
                }
            }

            // Now read the next ones
            assertEquals( 0, pagedSearchControl.getSize() );

            // check if this is over
            byte[] cookie = pagedSearchControl.getCookie();

            if ( Strings.isEmpty( cookie ) )
            {
                // If so, exit the loop
                break;
            }

            // Prepare the next iteration, sending a bad cookie
            pagedSearchControl.setCookie( "test".getBytes( "UTF-8" ) );
            pagedSearchControl.setSize( 3 );
        }

        assertTrue( hasUnwillingToPerform );

        // Cleanup the session
        connection.unBind();
        connection.close();
    }


    /**
     * Do a test with a paged search, changing the number of entries to
     * return in the middle of the loop
     */
    @Test
    public void testPagedSearchModifyingPagedLimit() throws Exception
    {
        getLdapServer().setMaxSizeLimit( LdapServer.NO_SIZE_LIMIT );
        DirContext ctx = getWiredContext( getLdapServer(), "cn=user,ou=system", "secret" );
        SearchControls controls = createSearchControls( ctx, ( int ) LdapServer.NO_SIZE_LIMIT, 4 );

        // Loop over all the elements
        int loop = 0;
        List<SearchResult> results = new ArrayList<SearchResult>();

        // The expected size after each loop.
        int[] expectedSize = new int[]
            { 4, 7, 9, 10 };

        while ( true )
        {
            loop++;

            NamingEnumeration<SearchResult> list =
                ctx.search( "dc=users,ou=system", "(cn=*)", controls );

            while ( list.hasMore() )
            {
                SearchResult result = list.next();
                results.add( result );
            }

            list.close();

            // Now read the next ones
            javax.naming.ldap.Control[] responseControls = ( ( LdapContext ) ctx ).getResponseControls();

            PagedResultsResponseControl responseControl =
                ( PagedResultsResponseControl ) responseControls[0];
            assertEquals( 0, responseControl.getResultSize() );

            // check if this is over
            byte[] cookie = responseControl.getCookie();

            if ( Strings.isEmpty( cookie ) )
            {
                // If so, exit the loop
                break;
            }

            // Prepare the next iteration, sending a bad cookie
            createNextSearchControls( ctx, responseControl.getCookie(), 4 - loop );

            assertEquals( expectedSize[loop - 1], results.size() );
        }

        assertEquals( 4, loop );
        checkResults( results, 10 );

        // And close the connection
        closeConnection( ctx );
    }
}