Skip to content

Commit 92e9bfd

Browse files
committed
add SearchAsync
1 parent f61ea45 commit 92e9bfd

File tree

5 files changed

+257
-0
lines changed

5 files changed

+257
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

ldap_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,45 @@ func TestSearch(t *testing.T) {
8989
t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
9090
}
9191

92+
func TestSearchAsync(t *testing.T) {
93+
l, err := DialURL(ldapServer)
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
defer l.Close()
98+
99+
searchRequest := NewSearchRequest(
100+
baseDN,
101+
ScopeWholeSubtree, DerefAlways, 0, 0, false,
102+
filter[0],
103+
attributes,
104+
nil)
105+
106+
var entries []*Entry
107+
responses, err := l.SearchAsync(searchRequest)
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
for res := range responses {
112+
if res.Err() != nil {
113+
t.Error(err)
114+
break
115+
}
116+
if res.Closed() {
117+
break
118+
}
119+
switch res.Type {
120+
case SearchAsyncResponseTypeEntry:
121+
entries = append(entries, res.Entry)
122+
case SearchAsyncResponseTypeReferral:
123+
t.Logf("Received Referral: %s", res.Referral)
124+
case SearchAsyncResponseTypeControl:
125+
t.Logf("Received Control: %s", res.Control)
126+
}
127+
}
128+
t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(entries))
129+
}
130+
92131
func TestSearchStartTLS(t *testing.T) {
93132
l, err := DialURL(ldapServer)
94133
if err != nil {

search.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,42 @@ func (s *SearchResult) PrettyPrint(indent int) {
218218
}
219219
}
220220

221+
// SearchAsyncResponseType describes the SearchAsyncResponse content type
222+
type SearchAsyncResponseType uint8
223+
224+
const (
225+
SearchAsyncResponseTypeNone SearchAsyncResponseType = iota
226+
SearchAsyncResponseTypeEntry
227+
SearchAsyncResponseTypeReferral
228+
SearchAsyncResponseTypeControl
229+
)
230+
231+
// SearchAsyncResponse holds the server's response message to an async search request
232+
type SearchAsyncResponse struct {
233+
// Type indicates the SearchAsyncResponse type
234+
Type SearchAsyncResponseType
235+
// Entry is the received entry, only set if Type is SearchAsyncResponseTypeEntry
236+
Entry *Entry
237+
// Referral is the received referral, only set if Type is SearchAsyncResponseTypeReferral
238+
Referral string
239+
// Control is the received control, only set if Type is SearchAsyncResponseTypeControl
240+
Control Control
241+
// closed indicates that the request is finished
242+
closed bool
243+
// err holds the encountered error while processing server's response, if any
244+
err error
245+
}
246+
247+
// Closed returns true if the request is finished
248+
func (r *SearchAsyncResponse) Closed() bool {
249+
return r.closed
250+
}
251+
252+
// Err returns the encountered error while processing server's response, if any
253+
func (r *SearchAsyncResponse) Err() error {
254+
return r.err
255+
}
256+
221257
// SearchRequest represents a search request to send to the server
222258
type SearchRequest struct {
223259
BaseDN string
@@ -402,6 +438,59 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
402438
}
403439
}
404440

441+
// SearchAsync performs the given search request asynchronously, it returns a SearchAsyncResponse channel which will be
442+
// closed when the request finished and an error, not nil if the request to the server failed
443+
func (l *Conn) SearchAsync(searchRequest *SearchRequest) (<-chan *SearchAsyncResponse, error) {
444+
msgCtx, err := l.doRequest(searchRequest)
445+
if err != nil {
446+
return nil, err
447+
}
448+
responses := make(chan *SearchAsyncResponse)
449+
go func() {
450+
defer l.finishMessage(msgCtx)
451+
defer close(responses)
452+
for {
453+
packet, err := l.readPacket(msgCtx)
454+
if err != nil {
455+
responses <- &SearchAsyncResponse{closed: true, err: err}
456+
return
457+
}
458+
459+
switch packet.Children[1].Tag {
460+
case 4:
461+
entry := &Entry{
462+
DN: packet.Children[1].Children[0].Value.(string),
463+
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
464+
}
465+
responses <- &SearchAsyncResponse{Type: SearchAsyncResponseTypeEntry, Entry: entry}
466+
case 5:
467+
err := GetLDAPError(packet)
468+
if err != nil {
469+
responses <- &SearchAsyncResponse{closed: true, err: err}
470+
return
471+
}
472+
var response SearchAsyncResponse
473+
if len(packet.Children) == 3 {
474+
for _, child := range packet.Children[2].Children {
475+
decodedChild, err := DecodeControl(child)
476+
if err != nil {
477+
responses <- &SearchAsyncResponse{closed: true, err: fmt.Errorf("failed to decode child control: %s", err)}
478+
return
479+
}
480+
response = SearchAsyncResponse{Type: SearchAsyncResponseTypeControl, Control: decodedChild}
481+
}
482+
}
483+
response.closed = true
484+
responses <- &response
485+
return
486+
case 19:
487+
responses <- &SearchAsyncResponse{Type: SearchAsyncResponseTypeReferral, Referral: packet.Children[1].Children[0].Value.(string)}
488+
}
489+
}
490+
}()
491+
return responses, nil
492+
}
493+
405494
// unpackAttributes will extract all given LDAP attributes and it's values
406495
// from the ber.Packet
407496
func unpackAttributes(children []*ber.Packet) []*EntryAttribute {

v3/ldap_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,45 @@ func TestSearch(t *testing.T) {
8989
t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
9090
}
9191

92+
func TestSearchAsync(t *testing.T) {
93+
l, err := DialURL(ldapServer)
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
defer l.Close()
98+
99+
searchRequest := NewSearchRequest(
100+
baseDN,
101+
ScopeWholeSubtree, DerefAlways, 0, 0, false,
102+
filter[0],
103+
attributes,
104+
nil)
105+
106+
var entries []*Entry
107+
responses, err := l.SearchAsync(searchRequest)
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
for res := range responses {
112+
if res.Err() != nil {
113+
t.Error(err)
114+
break
115+
}
116+
if res.Closed() {
117+
break
118+
}
119+
switch res.Type {
120+
case SearchAsyncResponseTypeEntry:
121+
entries = append(entries, res.Entry)
122+
case SearchAsyncResponseTypeReferral:
123+
t.Logf("Received Referral: %s", res.Referral)
124+
case SearchAsyncResponseTypeControl:
125+
t.Logf("Received Control: %s", res.Control)
126+
}
127+
}
128+
t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(entries))
129+
}
130+
92131
func TestSearchStartTLS(t *testing.T) {
93132
l, err := DialURL(ldapServer)
94133
if err != nil {

v3/search.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,42 @@ func (s *SearchResult) PrettyPrint(indent int) {
218218
}
219219
}
220220

221+
// SearchAsyncResponseType describes the SearchAsyncResponse content type
222+
type SearchAsyncResponseType uint8
223+
224+
const (
225+
SearchAsyncResponseTypeNone SearchAsyncResponseType = iota
226+
SearchAsyncResponseTypeEntry
227+
SearchAsyncResponseTypeReferral
228+
SearchAsyncResponseTypeControl
229+
)
230+
231+
// SearchAsyncResponse holds the server's response message to an async search request
232+
type SearchAsyncResponse struct {
233+
// Type indicates the SearchAsyncResponse type
234+
Type SearchAsyncResponseType
235+
// Entry is the received entry, only set if Type is SearchAsyncResponseTypeEntry
236+
Entry *Entry
237+
// Referral is the received referral, only set if Type is SearchAsyncResponseTypeReferral
238+
Referral string
239+
// Control is the received control, only set if Type is SearchAsyncResponseTypeControl
240+
Control Control
241+
// closed indicates that the request is finished
242+
closed bool
243+
// err holds the encountered error while processing server's response, if any
244+
err error
245+
}
246+
247+
// Closed returns true if the request is finished
248+
func (r *SearchAsyncResponse) Closed() bool {
249+
return r.closed
250+
}
251+
252+
// Err returns the encountered error while processing server's response, if any
253+
func (r *SearchAsyncResponse) Err() error {
254+
return r.err
255+
}
256+
221257
// SearchRequest represents a search request to send to the server
222258
type SearchRequest struct {
223259
BaseDN string
@@ -402,6 +438,59 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
402438
}
403439
}
404440

441+
// SearchAsync performs the given search request asynchronously, it returns a SearchAsyncResponse channel which will be
442+
// closed when the request finished and an error, not nil if the request to the server failed
443+
func (l *Conn) SearchAsync(searchRequest *SearchRequest) (<-chan *SearchAsyncResponse, error) {
444+
msgCtx, err := l.doRequest(searchRequest)
445+
if err != nil {
446+
return nil, err
447+
}
448+
responses := make(chan *SearchAsyncResponse)
449+
go func() {
450+
defer l.finishMessage(msgCtx)
451+
defer close(responses)
452+
for {
453+
packet, err := l.readPacket(msgCtx)
454+
if err != nil {
455+
responses <- &SearchAsyncResponse{closed: true, err: err}
456+
return
457+
}
458+
459+
switch packet.Children[1].Tag {
460+
case 4:
461+
entry := &Entry{
462+
DN: packet.Children[1].Children[0].Value.(string),
463+
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
464+
}
465+
responses <- &SearchAsyncResponse{Type: SearchAsyncResponseTypeEntry, Entry: entry}
466+
case 5:
467+
err := GetLDAPError(packet)
468+
if err != nil {
469+
responses <- &SearchAsyncResponse{closed: true, err: err}
470+
return
471+
}
472+
var response SearchAsyncResponse
473+
if len(packet.Children) == 3 {
474+
for _, child := range packet.Children[2].Children {
475+
decodedChild, err := DecodeControl(child)
476+
if err != nil {
477+
responses <- &SearchAsyncResponse{closed: true, err: fmt.Errorf("failed to decode child control: %s", err)}
478+
return
479+
}
480+
response = SearchAsyncResponse{Type: SearchAsyncResponseTypeControl, Control: decodedChild}
481+
}
482+
}
483+
response.closed = true
484+
responses <- &response
485+
return
486+
case 19:
487+
responses <- &SearchAsyncResponse{Type: SearchAsyncResponseTypeReferral, Referral: packet.Children[1].Children[0].Value.(string)}
488+
}
489+
}
490+
}()
491+
return responses, nil
492+
}
493+
405494
// unpackAttributes will extract all given LDAP attributes and it's values
406495
// from the ber.Packet
407496
func unpackAttributes(children []*ber.Packet) []*EntryAttribute {

0 commit comments

Comments
 (0)