diff --git a/client.go b/client.go index c1e93d0..ed96e84 100644 --- a/client.go +++ b/client.go @@ -36,5 +36,6 @@ type Client interface { SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error) + DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response } diff --git a/control.go b/control.go index fa74da4..331ec38 100644 --- a/control.go +++ b/control.go @@ -523,29 +523,7 @@ func DecodeControl(packet *ber.Packet) (Control, error) { return NewControlSubtreeDelete(), nil case ControlTypeDirSync: value.Description += " (DirSync)" - c := new(ControlDirSync) - if value.Value != nil { - valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) - if err != nil { - return nil, err - } - value.Data.Truncate(0) - value.Value = nil - value.AppendChild(valueChildren) - } - value = value.Children[0] - if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string - return nil, fmt.Errorf("invalid number of children in dirSync control") - } - value.Description = "DirSync Control Value" - value.Children[0].Description = "Flags" - value.Children[1].Description = "MaxAttrCnt" - value.Children[2].Description = "Cookie" - c.Flags = value.Children[0].Value.(int64) - c.MaxAttrCnt = value.Children[1].Value.(int64) - c.Cookie = value.Children[2].Data.Bytes() - value.Children[2].Value = c.Cookie - return c, nil + return NewControlDirSyncForDecode(value) case ControlTypeSyncState: value.Description += " (Sync State)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) @@ -640,18 +618,52 @@ func encodeControls(controls []Control) *ber.Packet { // ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx type ControlDirSync struct { - Flags int64 - MaxAttrCnt int64 - Cookie []byte + Criticality bool + Flags int64 + MaxAttrCount int64 + Cookie []byte } -// NewControlDirSync returns a dir sync control -func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync { +// NewControlDirSyncForEncode returns a dir sync control +func NewControlDirSyncForEncode( + flags int64, maxAttrCount int64, cookie []byte, +) *ControlDirSync { return &ControlDirSync{ - Flags: flags, - MaxAttrCnt: maxAttrCount, - Cookie: cookie, + Criticality: true, + Flags: flags, + MaxAttrCount: maxAttrCount, + Cookie: cookie, + } +} + +// NewControlDirSyncForDecode returns a dir sync control +func NewControlDirSyncForDecode(value *ber.Packet) (*ControlDirSync, error) { + if value.Value != nil { + valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to decode data bytes: %s", err) + } + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + child := value.Children[0] + if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string + return nil, fmt.Errorf("invalid number of children in dirSync control") } + child.Description = "DirSync Control Value" + child.Children[0].Description = "Flags" + child.Children[1].Description = "MaxAttrCount" + child.Children[2].Description = "Cookie" + + cookie := child.Children[2].Data.Bytes() + child.Children[2].Value = cookie + return &ControlDirSync{ + Criticality: true, + Flags: child.Children[0].Value.(int64), + MaxAttrCount: child.Children[1].Value.(int64), + Cookie: cookie, + }, nil } // GetControlType returns the OID @@ -661,28 +673,33 @@ func (c *ControlDirSync) GetControlType() string { // String returns a human-readable description func (c *ControlDirSync) String() string { - return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt) + return fmt.Sprintf( + "ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d", + ControlTypeMap[ControlTypeDirSync], + ControlTypeDirSync, + c.Criticality, + c.Flags, + c.MaxAttrCount, + ) } // Encode returns the ber packet representation func (c *ControlDirSync) Encode() *ber.Packet { + cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") + if len(c.Cookie) != 0 { + cookie.Value = c.Cookie + cookie.Data.Write(c.Cookie) + } + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")")) - packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always + packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)") - seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags")) - seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount")) - - cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") - if len(c.Cookie) != 0 { - cookie.Value = c.Cookie - cookie.Data.Write(c.Cookie) - } + seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount")) seq.AppendChild(cookie) - val.AppendChild(seq) packet.AppendChild(val) diff --git a/control_test.go b/control_test.go index 7bd4a8a..90b2ab0 100644 --- a/control_test.go +++ b/control_test.go @@ -44,8 +44,8 @@ func TestControlString(t *testing.T) { } func TestControlDirSync(t *testing.T) { - runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil)) - runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) + runControlTest(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, nil)) + runControlTest(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) } func runControlTest(t *testing.T, originalControl Control) { @@ -122,7 +122,7 @@ func TestDescribeControlString(t *testing.T) { } func TestDescribeControlDirSync(t *testing.T) { - runAddControlDescriptions(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") + runAddControlDescriptions(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") } func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) { diff --git a/examples_test.go b/examples_test.go index 86b23a3..817016b 100644 --- a/examples_test.go +++ b/examples_test.go @@ -466,6 +466,49 @@ func ExampleConn_DirSync() { } } +// This example demonstrates how to use DirSync search asynchronously +func ExampleConn_DirSyncAsync() { + conn, err := Dial("tcp", "ad.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&SimpleBindRequest{ + Username: "cn=Some User,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + if err != nil { + log.Fatalf("failed to bind: %s", err) + } + + req := &SearchRequest{ + BaseDN: `DC=example,DC=org`, + Filter: `(&(objectClass=person)(!(objectClass=computer)))`, + Attributes: []string{"*"}, + Scope: ScopeWholeSubtree, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var cookie []byte = nil + r := conn.DirSyncAsync(ctx, req, 64, DirSyncObjectSecurity, 1000, cookie) + for r.Next() { + entry := r.Entry() + if entry != nil { + entry.Print() + } + controls := r.Controls() + if len(controls) != 0 { + fmt.Printf("%s", controls) + } + } + if err := r.Err(); err != nil { + log.Fatal(err) + } +} + // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { ldapCert := "/path/to/cert.pem" diff --git a/search.go b/search.go index 0d353b9..665428e 100644 --- a/search.go +++ b/search.go @@ -633,55 +633,56 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute { } // DirSync does a Search with dirSync Control. -func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte) (*SearchResult, error) { - var dirSyncControl *ControlDirSync - +func (l *Conn) DirSync( + searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte, +) (*SearchResult, error) { control := FindControl(searchRequest.Controls, ControlTypeDirSync) if control == nil { - dirSyncControl = NewControlDirSync(flags, maxAttrCount, cookie) - searchRequest.Controls = append(searchRequest.Controls, dirSyncControl) + c := NewControlDirSyncForEncode(flags, maxAttrCount, cookie) + searchRequest.Controls = append(searchRequest.Controls, c) } else { - castControl, ok := control.(*ControlDirSync) - if !ok { - return nil, fmt.Errorf("Expected DirSync control to be of type *ControlDirSync, got %v", control) + c := control.(*ControlDirSync) + if c.Flags != flags { + return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags) } - if castControl.Flags != flags { - return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", castControl.Flags, flags) + if c.MaxAttrCount != maxAttrCount { + return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount) } - if castControl.MaxAttrCnt != maxAttrCount { - return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", castControl.MaxAttrCnt, maxAttrCount) - } - dirSyncControl = castControl } - searchResult := new(SearchResult) - result, err := l.Search(searchRequest) + searchResult, err := l.Search(searchRequest) l.Debug.Printf("Looking for result...") if err != nil { - return searchResult, err + return nil, err } - if result == nil { - return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) + if searchResult == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } - searchResult.Entries = append(searchResult.Entries, result.Entries...) - searchResult.Referrals = append(searchResult.Referrals, result.Referrals...) - searchResult.Controls = append(searchResult.Controls, result.Controls...) - l.Debug.Printf("Looking for DirSync Control...") - dirSyncResult := FindControl(result.Controls, ControlTypeDirSync) - if dirSyncResult == nil { - dirSyncControl = nil + resultControl := FindControl(searchResult.Controls, ControlTypeDirSync) + if resultControl == nil { l.Debug.Printf("Could not find dirSyncControl control. Breaking...") return searchResult, nil } - cookie = dirSyncResult.(*ControlDirSync).Cookie + cookie = resultControl.(*ControlDirSync).Cookie if len(cookie) == 0 { - dirSyncControl = nil l.Debug.Printf("Could not find cookie. Breaking...") return searchResult, nil } - dirSyncControl.SetCookie(cookie) return searchResult, nil } + +// DirSyncDirSyncAsync performs a search request and returns all search results +// asynchronously. This is efficient when the server returns lots of entries. +func (l *Conn) DirSyncAsync( + ctx context.Context, searchRequest *SearchRequest, bufferSize int, + flags, maxAttrCount int64, cookie []byte, +) Response { + control := NewControlDirSyncForEncode(flags, maxAttrCount, cookie) + searchRequest.Controls = append(searchRequest.Controls, control) + r := newSearchResponse(l, bufferSize) + r.start(ctx, searchRequest) + return r +} diff --git a/v3/client.go b/v3/client.go index c1e93d0..ed96e84 100644 --- a/v3/client.go +++ b/v3/client.go @@ -36,5 +36,6 @@ type Client interface { SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error) + DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response } diff --git a/v3/control.go b/v3/control.go index c2c3f87..b33d555 100644 --- a/v3/control.go +++ b/v3/control.go @@ -534,29 +534,7 @@ func DecodeControl(packet *ber.Packet) (Control, error) { return NewControlServerSideSortingResult(value) case ControlTypeDirSync: value.Description += " (DirSync)" - c := new(ControlDirSync) - if value.Value != nil { - valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) - if err != nil { - return nil, err - } - value.Data.Truncate(0) - value.Value = nil - value.AppendChild(valueChildren) - } - value = value.Children[0] - if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string - return nil, fmt.Errorf("invalid number of children in dirSync control") - } - value.Description = "DirSync Control Value" - value.Children[0].Description = "Flags" - value.Children[1].Description = "MaxAttrCnt" - value.Children[2].Description = "Cookie" - c.Flags = value.Children[0].Value.(int64) - c.MaxAttrCnt = value.Children[1].Value.(int64) - c.Cookie = value.Children[2].Data.Bytes() - value.Children[2].Value = c.Cookie - return c, nil + return NewControlDirSyncForDecode(value) case ControlTypeSyncState: value.Description += " (Sync State)" valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) @@ -651,18 +629,52 @@ func encodeControls(controls []Control) *ber.Packet { // ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx type ControlDirSync struct { - Flags int64 - MaxAttrCnt int64 - Cookie []byte + Criticality bool + Flags int64 + MaxAttrCount int64 + Cookie []byte } -// NewControlDirSync returns a dir sync control -func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync { +// NewControlDirSyncForEncode returns a dir sync control +func NewControlDirSyncForEncode( + flags int64, maxAttrCount int64, cookie []byte, +) *ControlDirSync { return &ControlDirSync{ - Flags: flags, - MaxAttrCnt: maxAttrCount, - Cookie: cookie, + Criticality: true, + Flags: flags, + MaxAttrCount: maxAttrCount, + Cookie: cookie, + } +} + +// NewControlDirSyncForDecode returns a dir sync control +func NewControlDirSyncForDecode(value *ber.Packet) (*ControlDirSync, error) { + if value.Value != nil { + valueChildren, err := ber.DecodePacketErr(value.Data.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to decode data bytes: %s", err) + } + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + child := value.Children[0] + if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string + return nil, fmt.Errorf("invalid number of children in dirSync control") } + child.Description = "DirSync Control Value" + child.Children[0].Description = "Flags" + child.Children[1].Description = "MaxAttrCount" + child.Children[2].Description = "Cookie" + + cookie := child.Children[2].Data.Bytes() + child.Children[2].Value = cookie + return &ControlDirSync{ + Criticality: true, + Flags: child.Children[0].Value.(int64), + MaxAttrCount: child.Children[1].Value.(int64), + Cookie: cookie, + }, nil } // GetControlType returns the OID @@ -672,28 +684,33 @@ func (c *ControlDirSync) GetControlType() string { // String returns a human-readable description func (c *ControlDirSync) String() string { - return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt) + return fmt.Sprintf( + "ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d", + ControlTypeMap[ControlTypeDirSync], + ControlTypeDirSync, + c.Criticality, + c.Flags, + c.MaxAttrCount, + ) } // Encode returns the ber packet representation func (c *ControlDirSync) Encode() *ber.Packet { + cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") + if len(c.Cookie) != 0 { + cookie.Value = c.Cookie + cookie.Data.Write(c.Cookie) + } + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")")) - packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always + packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)") - seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value") seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags")) - seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount")) - - cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie") - if len(c.Cookie) != 0 { - cookie.Value = c.Cookie - cookie.Data.Write(c.Cookie) - } + seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount")) seq.AppendChild(cookie) - val.AppendChild(seq) packet.AppendChild(val) diff --git a/v3/control_test.go b/v3/control_test.go index 0a88878..14a9bd4 100644 --- a/v3/control_test.go +++ b/v3/control_test.go @@ -44,8 +44,8 @@ func TestControlString(t *testing.T) { } func TestControlDirSync(t *testing.T) { - runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil)) - runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) + runControlTest(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, nil)) + runControlTest(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!"))) } func runControlTest(t *testing.T, originalControl Control) { @@ -122,7 +122,7 @@ func TestDescribeControlString(t *testing.T) { } func TestDescribeControlDirSync(t *testing.T) { - runAddControlDescriptions(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") + runAddControlDescriptions(t, NewControlDirSyncForEncode(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value") } func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) { diff --git a/v3/examples_test.go b/v3/examples_test.go index 86b23a3..817016b 100644 --- a/v3/examples_test.go +++ b/v3/examples_test.go @@ -466,6 +466,49 @@ func ExampleConn_DirSync() { } } +// This example demonstrates how to use DirSync search asynchronously +func ExampleConn_DirSyncAsync() { + conn, err := Dial("tcp", "ad.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&SimpleBindRequest{ + Username: "cn=Some User,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + if err != nil { + log.Fatalf("failed to bind: %s", err) + } + + req := &SearchRequest{ + BaseDN: `DC=example,DC=org`, + Filter: `(&(objectClass=person)(!(objectClass=computer)))`, + Attributes: []string{"*"}, + Scope: ScopeWholeSubtree, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var cookie []byte = nil + r := conn.DirSyncAsync(ctx, req, 64, DirSyncObjectSecurity, 1000, cookie) + for r.Next() { + entry := r.Entry() + if entry != nil { + entry.Print() + } + controls := r.Controls() + if len(controls) != 0 { + fmt.Printf("%s", controls) + } + } + if err := r.Err(); err != nil { + log.Fatal(err) + } +} + // This example demonstrates how to use EXTERNAL SASL with TLS client certificates. func ExampleConn_ExternalBind() { ldapCert := "/path/to/cert.pem" diff --git a/v3/search.go b/v3/search.go index e6bd78f..22e1c72 100644 --- a/v3/search.go +++ b/v3/search.go @@ -635,55 +635,56 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute { } // DirSync does a Search with dirSync Control. -func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte) (*SearchResult, error) { - var dirSyncControl *ControlDirSync - +func (l *Conn) DirSync( + searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte, +) (*SearchResult, error) { control := FindControl(searchRequest.Controls, ControlTypeDirSync) if control == nil { - dirSyncControl = NewControlDirSync(flags, maxAttrCount, cookie) - searchRequest.Controls = append(searchRequest.Controls, dirSyncControl) + c := NewControlDirSyncForEncode(flags, maxAttrCount, cookie) + searchRequest.Controls = append(searchRequest.Controls, c) } else { - castControl, ok := control.(*ControlDirSync) - if !ok { - return nil, fmt.Errorf("Expected DirSync control to be of type *ControlDirSync, got %v", control) + c := control.(*ControlDirSync) + if c.Flags != flags { + return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags) } - if castControl.Flags != flags { - return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", castControl.Flags, flags) + if c.MaxAttrCount != maxAttrCount { + return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount) } - if castControl.MaxAttrCnt != maxAttrCount { - return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", castControl.MaxAttrCnt, maxAttrCount) - } - dirSyncControl = castControl } - searchResult := new(SearchResult) - result, err := l.Search(searchRequest) + searchResult, err := l.Search(searchRequest) l.Debug.Printf("Looking for result...") if err != nil { - return searchResult, err + return nil, err } - if result == nil { - return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) + if searchResult == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received")) } - searchResult.Entries = append(searchResult.Entries, result.Entries...) - searchResult.Referrals = append(searchResult.Referrals, result.Referrals...) - searchResult.Controls = append(searchResult.Controls, result.Controls...) - l.Debug.Printf("Looking for DirSync Control...") - dirSyncResult := FindControl(result.Controls, ControlTypeDirSync) - if dirSyncResult == nil { - dirSyncControl = nil + resultControl := FindControl(searchResult.Controls, ControlTypeDirSync) + if resultControl == nil { l.Debug.Printf("Could not find dirSyncControl control. Breaking...") return searchResult, nil } - cookie = dirSyncResult.(*ControlDirSync).Cookie + cookie = resultControl.(*ControlDirSync).Cookie if len(cookie) == 0 { - dirSyncControl = nil l.Debug.Printf("Could not find cookie. Breaking...") return searchResult, nil } - dirSyncControl.SetCookie(cookie) return searchResult, nil } + +// DirSyncDirSyncAsync performs a search request and returns all search results +// asynchronously. This is efficient when the server returns lots of entries. +func (l *Conn) DirSyncAsync( + ctx context.Context, searchRequest *SearchRequest, bufferSize int, + flags, maxAttrCount int64, cookie []byte, +) Response { + control := NewControlDirSyncForEncode(flags, maxAttrCount, cookie) + searchRequest.Controls = append(searchRequest.Controls, control) + r := newSearchResponse(l, bufferSize) + r.start(ctx, searchRequest) + return r +}