2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
22 #include "dbg.h" // dprintf
25 #define SDP_PDU_Error_Response 1
26 #define SDP_PDU_Service_Search_Request 2
27 #define SDP_PDU_Service_Search_Response 3
28 #define SDP_PDU_Service_Attribute_Request 4
29 #define SDP_PDU_Service_Attribute_Response 5
30 #define SDP_PDU_Service_Search_Attribute_Request 6
31 #define SDP_PDU_Service_Search_Attribute_Response 7
33 #define SDP_ERR_Invalid_SDP_Version 0x0001
34 #define SDP_ERR_Invalid_Service_Record_Handle 0x0002
35 #define SDP_ERR_Invalid_Request_Syntax 0x0003
36 #define SDP_ERR_Invalid_PDU_Size 0x0004
37 #define SDP_ERR_Invalid_Continuation_State 0x0005
38 #define SDP_ERR_Insufficient_Resources 0x0006
47 #define MAX_UUIDS_IN_SEARCH 12 //as per spec
48 #define MAX_ATTRS_IN_SEARCH_STRING 8 //as per my opinion
49 #define MAX_SEARCH_RESULTS 16 //no more than this will ever be returned
51 typedef struct SdpService
{
53 struct SdpService
* next
;
56 const uint8_t* descriptor
;
66 uint32_t contDescr
; //which continuation descriptor we expect
69 uint16_t numMatches
; //for servicesearch
74 static SdpService
* knownServices
= NULL
;
75 static uint32_t sdpContVal
= 0x12345678;
76 static uint32_t sdpNextHandle
= 0;
78 static const uint8_t sdpDescrSdp
[] =
80 //define the SDP service itself
81 //service class ID list
82 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0x00, 0x01, SDP_ITEM_DESC(SDP_TYPE_ARRAY
, SDP_SZ_u8
), 3,
83 SDP_ITEM_DESC(SDP_TYPE_UUID
, SDP_SZ_2
), 0x10, 0x00, // ServiceDiscoveryServerServiceClassID
84 //ProtocolDescriptorList
85 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0x00, 0x04, SDP_ITEM_DESC(SDP_TYPE_ARRAY
, SDP_SZ_u8
), 8,
86 SDP_ITEM_DESC(SDP_TYPE_ARRAY
, SDP_SZ_u8
), 6,
87 SDP_ITEM_DESC(SDP_TYPE_UUID
, SDP_SZ_2
), 0x01, 0x00, // L2CAP
88 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), L2CAP_PSM_SDP
>> 8, L2CAP_PSM_SDP
& 0xFF, // L2CAP PSM
91 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0x00, 0x05, SDP_ITEM_DESC(SDP_TYPE_ARRAY
, SDP_SZ_u8
), 3,
92 SDP_ITEM_DESC(SDP_TYPE_UUID
, SDP_SZ_2
), 0x10, 0x02, // Public Browse Group
95 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0xDD, 0xDD, SDP_ITEM_DESC(SDP_TYPE_TEXT
, SDP_SZ_u8
), 19, 0x53, 0x57, 0x3A, 0x20, 0x44, 0x6D, 0x69, 0x74, 0x72, 0x79, 0x20, 0x47, 0x72, 0x69, 0x6e, 0x62, 0x65, 0x72, 0x67,
97 SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0xDD, 0xDE, SDP_ITEM_DESC(SDP_TYPE_TEXT
, SDP_SZ_u8
), 19, 0x48, 0x57, 0x3A, 0x20, 0x45, 0x72, 0x69, 0x63, 0x20, 0x53, 0x63, 0x68, 0x6c, 0x61, 0x65, 0x70, 0x66, 0x65, 0x72
100 static const uuid bt_base_uuid
= {0x0000000000001000ULL
, 0x800000805F9B34FBULL
};
102 static void sdpIntToUUID(uuid
* dst
, uint32_t val
){
105 dst
->hi
+= ((uint64_t)val
) << 32;
108 static char sdpUuidEqual(const uuid
* a
, const uuid
* b
){
110 return a
->lo
== b
->lo
&& a
->hi
== b
->hi
;
113 static uint32_t btSdpGetElemSz(const uint8_t** descr
){
115 const uint8_t* ptr
= *descr
;
116 uint8_t item
= *ptr
++;
119 if((item
>> 3) != SDP_TYPE_NIL
){
129 sz
= 1 << (item
& 7);
140 sz
= (sz
<< 8) | ptr
[1];
147 sz
= (sz
<< 8) | ptr
[1];
148 sz
= (sz
<< 8) | ptr
[2];
149 sz
= (sz
<< 8) | ptr
[3];
158 static uint8_t btSdpGetUUID(const uint8_t** descr
, uuid
* dst
){ //return num bytes consumed
161 const uint8_t* orig
= *descr
;
163 if(((**descr
) >> 3) != SDP_TYPE_UUID
) return 0; //not valid UUID type
164 sz
= btSdpGetElemSz(descr
);
170 sdpIntToUUID(dst
, (((uint32_t)((*descr
)[0])) << 8) | ((*descr
)[1]));
175 sdpIntToUUID(dst
, (((uint32_t)((*descr
)[0])) << 24) | (((uint32_t)((*descr
)[1])) << 16) | (((uint32_t)((*descr
)[2])) << 8) | ((*descr
)[3]));
183 for(i
= 0; i
< 8; i
++){
185 dst
->lo
= (dst
->lo
<< 8) | (*descr
)[i
];
186 dst
->hi
= (dst
->lo
<< 8) | (*descr
)[i
+ 8];
196 return (*descr
) - orig
;
199 //static int recursive =0;
200 static void btStdRecursiveWalk(void* itemList
, uint8_t* listSzP
, sg_buf
** walkResultP
, const uint8_t** ptr
, uint32_t len
){
203 uint8_t typ
, numWantedIDs
;
204 const uint8_t* end
= (*ptr
) + len
;
206 sg_buf
* result
= NULL
;
207 char isID
= 1, skipNext
= 0;
209 uint8_t* numWantedIDsP
;
210 uint32_t* wantedRanges
;
211 uint8_t wantedRangesListSz
;
213 //printf("r%d\n",++recursive);
214 if(walkResultP
){ //copy-traversal
217 if(!result
) {/*printf("r%d\n",--recursive );*/ return;}
220 numWantedIDsP
= NULL
;
221 wantedRanges
= itemList
;
222 wantedRangesListSz
= *listSzP
;
224 else{ //search for UUIDs
226 wantedIDs
= itemList
;
227 numWantedIDsP
= listSzP
;
228 numWantedIDs
= *numWantedIDsP
;
230 wantedRangesListSz
= 0;
237 if(wantedIDs
&& typ
== SDP_TYPE_UUID
){
239 sz
= btSdpGetUUID(ptr
, &id
);
242 dbgPrintf("SDP: UUID size > allowed size (%d, %d)\n", sz
, end
- (*ptr
));
246 for(sz
= 0; sz
< numWantedIDs
; sz
++){
248 if(sdpUuidEqual(wantedIDs
+ sz
, &id
)){
250 wantedIDs
[sz
] = wantedIDs
[numWantedIDs
- 1];
258 const uint8_t* itemStart
= *ptr
;
260 sz
= btSdpGetElemSz(ptr
);
262 if(sz
> (unsigned)(end
- (*ptr
))){
264 dbgPrintf("SDP: element size > allowed size (%d, %d)\n", sz
, end
- (*ptr
));
268 if(typ
== SDP_TYPE_ARRAY
|| typ
== SDP_TYPE_OR_LIST
){
270 btStdRecursiveWalk(wantedIDs
, &numWantedIDs
, NULL
, ptr
, sz
);
283 if(sz
!= 2) dbgPrintf("SDP: attrib ID not 16 bits!\n");
286 attrID
= (attrID
<< 8) | (*ptr
)[-1];
289 for(i
= 0; i
< wantedRangesListSz
&& skipNext
; i
++){
291 if(attrID
>= (wantedRanges
[i
] >> 16) && attrID
<= (wantedRanges
[i
] & 0xFFFF)) skipNext
= 0; //in range
302 if(!sg_add_back(result
, itemStart
, (*ptr
) - itemStart
, SG_FLAG_MAKE_A_COPY
)){
305 //printf("r%d\n",--recursive );
314 //printf("r%d\n",--recursive );
315 if(walkResultP
) *walkResultP
= result
;
316 if(numWantedIDsP
) *numWantedIDsP
= numWantedIDs
;
319 static char btSdpPutIntoGroup(sg_buf
* buf
){
321 uint8_t i
, sizeFieldSz
, sizeFieldName
;
322 uint32_t sz
= sg_length(buf
);
325 //figure out needed header size field
329 sizeFieldName
= SDP_SZ_u8
;
332 else if(sz
< 0x10000){
335 sizeFieldName
= SDP_SZ_u16
;
341 sizeFieldName
= SDP_SZ_u32
;
345 res
[0] = SDP_ITEM_DESC(SDP_TYPE_ARRAY
, sizeFieldName
);
346 for(i
= 0; i
< sizeFieldSz
; i
++, sz
<<= 8) res
[1 + i
] = sz
>> 24;
347 return sg_add_front(buf
, res
, 1 + sizeFieldSz
, SG_FLAG_MAKE_A_COPY
);
350 static sg_buf
* btSdpError(const uint8_t* trans
, uint16_t errNum
){
353 uint8_t data
[] = {SDP_PDU_Error_Response
, trans
[0], trans
[1], errNum
>> 8, errNum
, 0, 0};
358 if(!sg_add_front(buf
, data
, sizeof(data
), SG_FLAG_MAKE_A_COPY
)){
367 static sg_buf
* btSdpProcessRequest(SdpInstance
* inst
, const uint8_t* req
, uint16_t reqSz
){
368 uint8_t trans
[2] ,cmd
, contStateSz
, numIDs
= 0, numAttrs
= 0;
369 uint32_t maxReplSz
= 0, wantedHandle
= 0, sz
;
370 uint32_t attrs
[MAX_ATTRS_IN_SEARCH_STRING
];
371 SdpService
* results
[MAX_SEARCH_RESULTS
];
372 uuid ids
[MAX_UUIDS_IN_SEARCH
];
382 if(reqSz
!= (((uint16_t)req
[0]) << 8) + req
[1]) return btSdpError(trans
, SDP_ERR_Invalid_PDU_Size
);
385 // dbgPrintf("SDP request cmd %d (session %02X%02X) with %d bytes of data\n", cmd, trans[0], trans[1], reqSz);
387 if(cmd
== SDP_PDU_Service_Search_Request
|| cmd
== SDP_PDU_Service_Search_Attribute_Request
){
389 if((*req
) >> 3 != SDP_TYPE_ARRAY
) return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
);
390 sz
= btSdpGetElemSz(&req
);
395 if(numIDs
== MAX_UUIDS_IN_SEARCH
) return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
); //too many requests
396 if(!btSdpGetUUID(&req
, &ids
[numIDs
++])) return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
); //malformed UUID
399 else if(cmd
== SDP_PDU_Service_Attribute_Request
){
401 for(i
= 0; i
< 4; i
++) wantedHandle
= (wantedHandle
<< 8) | *req
++;
405 dbgPrintf("SDP: invalid request: %d\n", cmd
);
406 return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
);
409 for(i
= 0; i
< 2; i
++) maxReplSz
= (maxReplSz
<< 8) | *req
++;
411 if(cmd
== SDP_PDU_Service_Attribute_Request
|| cmd
== SDP_PDU_Service_Search_Attribute_Request
){
413 if((*req
) >> 3 != SDP_TYPE_ARRAY
) return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
);
414 sz
= btSdpGetElemSz(&req
);
419 if(numAttrs
== MAX_UUIDS_IN_SEARCH
) return btSdpError(trans
, SDP_ERR_Insufficient_Resources
); //too many -> unsupported request -> fail
420 sz
= btSdpGetElemSz(&req
);
424 for(i
=0; i
< 2; i
++) sz
= (sz
<< 8) | *req
++;
430 for(i
=0; i
< 4; i
++) sz
= (sz
<< 8) | *req
++;
432 else return btSdpError(trans
, SDP_ERR_Invalid_Request_Syntax
); //fail -> invalid number format
433 attrs
[numAttrs
++] = sz
;
437 contStateSz
= *req
++;
439 if(contStateSz
){ // verify continuation is valid or fail
440 uint32_t contState
= 0;
442 if(contStateSz
!= sizeof(uint32_t)) return btSdpError(trans
, SDP_ERR_Invalid_Continuation_State
);
443 for(i
= 0; i
< 4; i
++) contState
= (contState
<< 8) | *req
++;
445 if(contState
!= inst
->contDescr
|| !inst
->result
){
447 dbgPrintf("SDP: invalid continuation state. Wanted %08X, got %08X\n", inst
->contDescr
, contState
);
453 return btSdpError(trans
, SDP_ERR_Invalid_Continuation_State
);
456 else{ //perform the actual search
458 SdpService
* curSvc
= knownServices
;
459 uint8_t numFound
= 0;
469 if(cmd
== SDP_PDU_Service_Search_Request
|| cmd
== SDP_PDU_Service_Search_Attribute_Request
){
471 for(curSvc
= knownServices
; curSvc
&& numFound
< MAX_SEARCH_RESULTS
; curSvc
= curSvc
->next
){
473 const uint8_t* ptr
= curSvc
->descriptor
;
474 uuid uuids_copy
[MAX_UUIDS_IN_SEARCH
];
477 for(num
= 0; num
< numIDs
; num
++) uuids_copy
[num
] = ids
[num
];
479 btStdRecursiveWalk(uuids_copy
, &num
, NULL
, &ptr
, curSvc
->descrLen
);
480 if(!num
) results
[numFound
++] = curSvc
;
483 else if(cmd
== SDP_PDU_Service_Attribute_Request
){
485 for(curSvc
= knownServices
; curSvc
&& !numFound
; curSvc
= curSvc
->next
){
487 if(curSvc
->handle
== wantedHandle
) results
[numFound
++] = curSvc
;
489 if(!numFound
) return btSdpError(trans
, SDP_ERR_Invalid_Service_Record_Handle
);
492 //gather & prepare results
493 if(cmd
== SDP_PDU_Service_Attribute_Request
|| cmd
== SDP_PDU_Service_Search_Attribute_Request
){
495 //we'll assemble the whole result in this buffer
496 sg_buf
* resultSoFar
= sg_alloc();
497 if(!resultSoFar
) return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
500 for(i
= 0; i
< numFound
; i
++){
502 const uint8_t* ptr
= results
[i
]->descriptor
;
505 //collect wanted attributes
506 btStdRecursiveWalk(attrs
, &numAttrs
, &res
, &ptr
, results
[i
]->descrLen
);
509 //if requested, add the handle attribute
510 for(j
= 0; j
< numAttrs
; j
++) if(SDP_ATTR_HANDLE
>= (attrs
[j
] >> 16) && SDP_ATTR_HANDLE
<= (attrs
[j
] & 0xFFFF)) break;
512 uint8_t buf
[8] = {SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_2
), 0x00, 0x00, SDP_ITEM_DESC(SDP_TYPE_UINT
, SDP_SZ_4
)};
514 buf
[4] = results
[i
]->handle
>> 24;
515 buf
[5] = results
[i
]->handle
>> 16;
516 buf
[6] = results
[i
]->handle
>> 8;
517 buf
[7] = results
[i
]->handle
;
519 if(!sg_add_back(res
, buf
, sizeof(buf
), SG_FLAG_MAKE_A_COPY
)){
527 //wrap and append to the full results list
528 if(btSdpPutIntoGroup(res
)) sg_concat_back(resultSoFar
, res
);
533 //wrap the whole thing if required
534 if((cmd
== SDP_PDU_Service_Search_Attribute_Request
) && !btSdpPutIntoGroup(resultSoFar
)){
535 dbgPrintf("SDP: Failed to put results into a group\n");
536 sg_free(resultSoFar
);
538 return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
541 //flatten to a buffer
542 uint8_t* buf
= malloc(sg_length(resultSoFar
));
545 dbgPrintf("SDP: Failed to allocate flattened result array (%ub)\n", sg_length(resultSoFar
));
546 sg_free(resultSoFar
);
548 return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
550 inst
->resultSz
= sg_length(resultSoFar
);
552 inst
->contDescr
= sdpContVal
;
553 sg_copyto(resultSoFar
, buf
);
554 sg_free(resultSoFar
);
557 else if(cmd
== SDP_PDU_Service_Search_Request
){
560 //inst->resultSz = sizeof(uint32_t[numFound]);
561 inst
->resultSz
= sizeof(uint32_t) * numFound
;
562 uint8_t* buf
= malloc(inst
->resultSz
);
566 //dbgPrintf("SDP: Failed to allocate flattened result array (%ub)\n", sizeof(uint32_t[numFound]));
567 dbgPrintf("SDP: Failed to allocate flattened result array (%ub)\n", sizeof(uint32_t) * numFound
);
568 return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
572 for(i
= 0; i
< numFound
; i
++){
574 buf
[i
* 4 + 0] = results
[i
]->handle
>> 24;
575 buf
[i
* 4 + 1] = results
[i
]->handle
>> 16;
576 buf
[i
* 4 + 2] = results
[i
]->handle
>> 8;
577 buf
[i
* 4 + 3] = results
[i
]->handle
;
580 //put everything in the right place
582 inst
->contDescr
= sdpContVal
;
583 inst
->numMatches
= numFound
;
586 if(++sdpContVal
== 0) sdpContVal
= 0x01234567; //update continuation state to the next value
588 //produce the packet to send
589 uint8_t bufPrepend
[9], bufPostpend
[5] = {0, }, preSz
= 5, postSz
= 1;
592 if(!result
) return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
594 if(cmd
== SDP_PDU_Service_Attribute_Request
|| cmd
== SDP_PDU_Service_Search_Attribute_Request
){
596 if(maxReplSz
> 256) maxReplSz
= 256; //no harm in fragmenting - keep the packets small
598 sendSz
= inst
->resultSz
;
599 if(sendSz
> maxReplSz
) sendSz
= maxReplSz
;
601 bufPrepend
[preSz
++] = sendSz
>> 8;
602 bufPrepend
[preSz
++] = sendSz
& 0xFF;
604 else if(cmd
== SDP_PDU_Service_Search_Request
){
606 if(maxReplSz
> 64) maxReplSz
= 64; //no harm in fragmenting - keep the packets small
608 sendSz
= inst
->resultSz
;
609 if(sendSz
> maxReplSz
* sizeof(uint32_t)) sendSz
= maxReplSz
* sizeof(uint32_t);
611 bufPrepend
[preSz
++] = inst
->numMatches
>> 8;
612 bufPrepend
[preSz
++] = inst
->numMatches
& 0xFF;
613 bufPrepend
[preSz
++] = (sendSz
/ sizeof(uint32_t)) >> 8;
614 bufPrepend
[preSz
++] = (sendSz
/ sizeof(uint32_t)) & 0xFF;
617 if(!sg_add_back(result
, inst
->result
, sendSz
, SG_FLAG_MAKE_A_COPY
)){
619 dbgPrintf("SDP: Failed to attach reply. Droping");
624 return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
628 inst
->resultSz
-= sendSz
;
631 memcpy(inst
->result
, inst
->result
+ sendSz
, inst
->resultSz
);
632 inst
->result
= realloc(inst
->result
, inst
->resultSz
);
640 if(inst
->result
){ //have more
643 for(i
= 0; i
< 4; i
++) bufPostpend
[i
+ 1] = inst
->contDescr
>> ((3 - i
) << 3);
647 bufPrepend
[0] = cmd
+ 1; //response to this request
648 bufPrepend
[1] = trans
[0];
649 bufPrepend
[2] = trans
[1];
650 bufPrepend
[3] = (sendSz
+ preSz
+ postSz
- 5) >> 8;
651 bufPrepend
[4] = (sendSz
+ preSz
+ postSz
- 5) & 0xFF;
653 if(sg_add_front(result
, bufPrepend
, preSz
, SG_FLAG_MAKE_A_COPY
) && sg_add_back(result
, bufPostpend
, postSz
, SG_FLAG_MAKE_A_COPY
)){
659 return btSdpError(trans
, SDP_ERR_Insufficient_Resources
);
662 static void* sdpServiceAlloc(uint16_t conn
, uint16_t chan
, uint16_t remChan
){
664 SdpInstance
* inst
= malloc(sizeof(SdpInstance
));
668 inst
->aclConn
= conn
;
669 inst
->remChan
= remChan
;
674 static void sdpServiceFree(void* service
){
676 SdpInstance
* inst
= (SdpInstance
*)service
;
678 if(inst
->result
) free(inst
->result
);
682 static void sdpServiceDataRx(void* service
, const uint8_t* data
, uint16_t size
){
684 SdpInstance
* inst
= (SdpInstance
*)service
;
685 uint16_t conn
= inst
->aclConn
;
686 uint16_t remChan
= inst
->remChan
;
688 sg_buf
* reply
= btSdpProcessRequest(inst
, data
, size
);
691 /*// -- ugly debugging code --
694 sg_copyto(reply, buf);
696 dbgPrintf("SDP req got (0x%x): ", size);
697 for(i = 0; i < size; i++) dbgPrintf(" %02X", data[i]);
700 dbgPrintf("SDP reply sent (0x%x): ", sg_length(reply));
701 for(i = 0; i < sg_length(reply); i++) dbgPrintf(" %02X", buf[i]);
704 l2capServiceTx(conn
, remChan
, reply
);
708 void btSdpRegisterL2capService(){
710 const L2capService sdp
= {L2CAP_FLAG_SUPPORT_CONNECTIONS
, sdpServiceAlloc
, sdpServiceFree
, sdpServiceDataRx
};
711 if(!l2capServiceRegister(L2CAP_PSM_SDP
, &sdp
)) dbgPrintf("SDP L2CAP registration failed\n");
713 btSdpServiceDescriptorAdd(sdpDescrSdp
, sizeof(sdpDescrSdp
));
716 void btSdpUnregisterL2capService(char sendDiscPacket
){
717 if (!l2capServiceUnregister(L2CAP_PSM_SDP
,sendDiscPacket
)) dbgPrintf("SDP L2CAP unregistration failed\n");
719 btSdpServiceDescriptorDel(sdpDescrSdp
);
722 void btSdpServiceDescriptorAdd(const uint8_t* descriptor
, uint16_t descrLen
) {
724 SdpService
*t
, *s
= malloc(sizeof(SdpService
));
727 s
->handle
= sdpNextHandle
;
729 if(sdpNextHandle
) sdpNextHandle
++;
730 else sdpNextHandle
= SDP_FIRST_USER_HANDLE
; //first add is special - it adds the SDP service itself
732 s
->descriptor
= descriptor
;
733 s
->descrLen
= descrLen
;
736 t
= knownServices
; //add at end
737 while(t
&& t
->next
) t
= t
->next
;
739 else knownServices
= s
;
743 void btSdpServiceDescriptorDel(const uint8_t* descriptor
){
745 SdpService
*s
= knownServices
, *p
= NULL
;
747 while(s
&& s
->descriptor
!= descriptor
){
752 if(p
) p
->next
= s
->next
;
753 else knownServices
= s
->next
;