::iocp::bt::sdrTop, Main

The iocp::bt::sdr namespace contains commands for parsing Bluetooth Service Discovery Records (SDR) as returned by the ::iocp::bt::device services and ::iocp::bt::device service_references commands. These return a list of binary records which must be first parsed with the decode command. Individual service attributes can then be retrieved from the decoded records using the other commands in the namespace.

set dev  [iocp::bt::device_address "APN Phone"]
set recs [iocp::bt::services $dev]
set sdr  [iocp::bt::sdr::decode [lindex $recs 0]]
set service_classes [iocp::bt::sdr::service_classes $sdr]

Commandssdr, Top, Main

attribute exists [::iocp::bt::sdr]sdr, Top, Main

Checks if an attribute exists in a service discovery record

attribute exists sdr attr_id ?varname?
Parameters
sdrA decoded service discovery record in the form returned by decode.
attr_idAttribute integer id or Bluetooth universal attribute name.
varnameOptional. If not the empty string, the raw attribute value is stored in a variable of this name in the caller's context. Optional, default "".
Return value

Returns 1 if the attribute exists, and 0 otherwise.

proc ::iocp::bt::sdr::attribute::exists {sdr attr_id {varname {}}} {

    # Checks if an attribute exists in a service discovery record
    # sdr - a decoded service discovery record in the form returned by
    #       [decode].
    # attr_id - attribute integer id or Bluetooth universal attribute name
    # varname - optional. If not the empty string, the raw attribute value is
    #           stored in a variable of this name in the caller's context.
    #
    # Returns 1 if the attribute exists, and 0 otherwise.

    if {[string is integer -strict $attr_id]} {
        set key [expr {$attr_id + 0}]; # Force decimal rep. Faster than format
    } else {
        set key [names::attribute_id $attr_id]; # name -> id
    }

    if {[dict exists $sdr $key]} {
        if {$varname ne ""} {
            upvar 1 $varname value
            set value [dict get $sdr $attr_id]
        }
        return 1
    }

    return 0
}
# NOTE: showing source of procedure implementing ensemble subcommand.

attribute get [::iocp::bt::sdr]sdr, Top, Main

Get the value of an universal attribute from a service discovery record.

attribute get sdr attr_id ?varname?
Parameters
sdrDecoded service discovery record.
attr_idUniversal attribute name or numeric identifier.
varnameOptional name of variable in caller's context. Optional, default "".
Description

If $varname is not the empty string, it should be the name of a variable in the caller's context.

  • If the attribute is present, the command returns 1 and stores the attribute value in the variable.
  • If the attribute is not present, the command returns 0.

If $varname is an empty string,

  • If the attribute is present, the command returns its value.
  • If the attribute is not present, the command raises an error.

The attribute value is decoded from its raw format into a attribute type-dependent format.

The $attr_id argument must be one of those defined in the Universal Attributes section in the Bluetooth Assigned Numbers specification. These are listed below. Refer to the Bluetooth specification for their exact semantics.

AdditionalProtocolDescriptorListList of protocol stacks. This supplements the ProtocolDescriptorList attribute.
BluetoothProfileDescriptorListList of Bluetooth profile descriptors to which the service conforms. Each element is a dictionary with keys Uuid, Name, MajorVersion and MinorVersion corresponding to the referenced profile.
BrowseGroupListList of browse group descriptors representing the browse groups to which the service record belongs. Each descriptor is dictionary with keys Uuid and Name identifying the a browse group.
ClientExecutableURLURL pointing to the client application that may be used to utilize the service.
DocumentationURLURL for the service documentation.
IconURLURL for icon that may be used to represent the service.
LanguageBaseAttributeIDListNested dictionary of language-specific attribute offsets (see below).
ProtocolDescriptorListList of protocol stacks (see below) that may be used to access the service described by the service record.
ProviderNameName of entity providing the service in the primary language of the service record.
ServiceAvailabilityA value in the range 0-255 that indicates relative availability of the service in terms of the number of clients it can accept. Note this is a scaled estimate.
ServiceClassIDListList with each element being a dictionary corresponding to a service class that the record conforms to. The keys of the dictionary are Name and Uuid.
ServiceDescriptionA description of the service in the primary language of the record.
ServiceIDUUID that uniquely identifies a service instance.
ServiceInfoTimeToLiveThe number of seconds from the time it was generated that the service record information is expected to be valid.
ServiceNameThe human-readable name of the service in the primary language of the record.
ServiceRecordHandleA numeric value that uniquely identifies a service record within a SDP server.
ServiceRecordStateNumeric value that is changed any time the service record is modified by the SDP server.

In the case of ProtocolDescriptorList and AdditionalProtocolDescriptorList, the attribute value is in the form of a list each element of which describes a protocol stack that may be used to access the service. Each such element is itself a list whose elements correspond to layers in that protocol stack starting with the lowest layer first. Each layer is described as a dictionary with the following keys:

UuidThe UUID of the protocol for the layer.
NameThe name of the protocol.
ParamsThe protocol parameters. This is a list of raw data elements of the form {type value} the interpretation of which is protocol dependent.

In the case of LanguageBaseAttributeIDList, the attribute value is a dictionary indexed by language identifiers as defined in ISO639, e.g. en, fr etc. The corresponding value is itself a dictionary with keys BaseOffset and Encoding. See the Bluetooth specification for the former. The latter is either a Tcl encoding name or in case the encoding is not supported by Tcl, an integer value that identifies an encoding as per the Bluetooth specification.

Return value

Returns a boolean or the decoded attribute value.

proc ::iocp::bt::sdr::attribute::get {sdr attr_id {varname {}}} {

    # Get the value of an universal attribute from a service
    # discovery record.
    #  sdr     - Decoded service discovery record.
    #  attr_id - Universal attribute name or numeric identifier.
    #  varname - Optional name of variable in caller's context.
    #
    # If $varname is not the empty string, it should be the name of
    # a variable in the caller's context.
    #  - If the attribute is present, the command returns `1` and
    #    stores the attribute value in the variable.
    #  - If the attribute is not present, the command returns 0.
    #
    # If $varname is an empty string,
    #  - If the attribute is present, the command returns its value.
    #  - If the attribute is not present, the command raises an error.
    #
    # The attribute value is decoded from its raw format into a
    # attribute type-dependent format.
    #
    # The $attr_id argument must be one of those defined in
    # the *Universal Attributes* section in the
    # [Bluetooth Assigned Numbers](https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/)
    # specification. These are listed below. Refer to the Bluetooth
    # specification for their exact semantics.
    #
    # AdditionalProtocolDescriptorList - List of protocol stacks. This
    #    supplements the `ProtocolDescriptorList` attribute.
    # BluetoothProfileDescriptorList - List of Bluetooth profile descriptors
    #    to which the service conforms. Each element is a dictionary
    #    with keys `Uuid`, `Name`, `MajorVersion` and `MinorVersion`
    #    corresponding to the referenced profile.
    # BrowseGroupList - List of browse group descriptors representing
    #    the browse groups to which the service record belongs. Each
    #    descriptor is dictionary with keys `Uuid` and `Name` identifying
    #    the a browse group.
    # ClientExecutableURL - URL pointing to the client application that
    #    may be used to utilize the service.
    # DocumentationURL - URL for the service documentation.
    # IconURL - URL for icon that may be used to represent the service.
    # LanguageBaseAttributeIDList - Nested dictionary of language-specific
    #    attribute offsets (see below).
    # ProtocolDescriptorList - List of protocol stacks (see below) that may
    #    be used to access the service described by the service record.
    # ProviderName - Name of entity providing the service in the primary
    #    language of the service record.
    # ServiceAvailability - A value in the range 0-255 that indicates
    #    relative availability of the service in terms of the number of
    #    clients it can accept. Note this is a scaled estimate.
    # ServiceClassIDList - List with each element being a dictionary
    #    corresponding to a service class that the record conforms to.
    #    The keys of the dictionary are `Name` and `Uuid`.
    # ServiceDescription - A description of the service in the primary
    #    language of the record.
    # ServiceID - UUID that uniquely identifies a service instance.
    # ServiceInfoTimeToLive - The number of seconds from the time it was
    #    generated that the service record information is expected to be valid.
    # ServiceName - The human-readable name of the service in the primary
    #    language of the record.
    # ServiceRecordHandle - A numeric value that uniquely identifies a service
    #    record within a SDP server.
    # ServiceRecordState - Numeric value that is changed any time the service
    #    record is modified by the SDP server.
    #
    # In the case of ProtocolDescriptorList and AdditionalProtocolDescriptorList,
    # the attribute value is in the form of a list each element of
    # which describes a protocol stack that may be used to access the
    # service. Each such element is itself a list whose elements correspond
    # to layers in that protocol stack starting with the lowest layer first.
    # Each layer is described as a dictionary with the following keys:
    #  Uuid - the UUID of the protocol for the layer
    #  Name - the name of the protocol
    #  Params - the protocol parameters. This is a list of raw data elements
    #           of the form `{type value}` the interpretation of which is
    #           protocol dependent.
    #
    # In the case of LanguageBaseAttributeIDList, the attribute value is
    # a dictionary indexed by language
    # identifiers as defined in ISO639, e.g. `en`, `fr` etc.
    # The corresponding value is itself a dictionary with keys `BaseOffset`
    # and `Encoding`. See the Bluetooth specification for the former.
    # The latter is either a Tcl encoding name or in case the encoding
    # is not supported by Tcl, an integer value that identifies an encoding
    # as per the Bluetooth specification.
    #
    # Returns a boolean or the decoded attribute value.

    set attr_name [names::attribute_name $attr_id]
    set attr_id   [names::attribute_id $attr_id]
    switch -exact -- $attr_name {
        BrowseGroupList -
        ServiceClassIDList {
            tailcall Uuids $sdr $attr_id $varname
        }
        BluetoothProfileDescriptorList -
        ProtocolDescriptorList -
        AdditionalProtocolDescriptorList -
        LanguageBaseAttributeIDList {
            tailcall $attr_name $sdr $varname
        }
        DocumentationURL -
        ClientExecutableURL -
        IconURL -
        ServiceAvailability -
        ServiceInfoTimeToLive -
        ServiceID -
        ServiceRecordState -
        ServiceRecordHandle {
            tailcall AttributeValue $sdr $attr_id $varname
        }
        ServiceName -
        ServiceDescription -
        ProviderName {
            # Special case because they need a language argument
            tailcall text $sdr $attr_name primary $varname
        }
        default {
            if {[llength [info commands [namespace current]::universal::$attr_name]]} {
                tailcall [namespace current]::universal::$attr_name $sdr $varname
            }
        }
    }
    error "Unknown universal attribute \"$attr_id\"."
}
# NOTE: showing source of procedure implementing ensemble subcommand.

attribute raw [::iocp::bt::sdr]sdr, Top, Main

Get an attribute value from an service discovery record.

attribute raw sdr attr_id
Parameters
sdrA decoded service discovery record in the form returned by sdr_decode.
attr_idAttribute integer id.
Description

The command will raise an error if the attribute does not exist in the sdr.

Return value

Returns the attribute value as a pair consisting of the type and the raw data element value.

proc ::iocp::bt::sdr::attribute::raw {sdr attr_id} {

    # Get an attribute value from an service discovery record.
    # sdr - a decoded service discovery record in the form returned by
    #       sdr_decode.
    # attr_id   - attribute integer id
    #
    # The command will raise an error if the attribute does not exist
    # in the sdr.
    #
    # Returns the attribute value as a pair consisting of the type and
    # the raw data element value.

    if {[exists $sdr $attr_id value]} {
        return $value
    } else {
        error "Attribute with id \"$attr_id\" not found."
    }
}
# NOTE: showing source of procedure implementing ensemble subcommand.

attribute text [::iocp::bt::sdr]sdr, Top, Main

Get the value of an text attribute in the specified language from a service discovery record.

attribute text sdr attr_id lang ?varname?
Parameters
sdrDecoded service discovery record.
attr_idUniversal attribute name or numeric identifier.
langLanguage identifier as specified in iso639, e.g. en for english, fr for french etc. or the keyword primary.
varnameOptional name of variable in caller's context. Optional, default "".
Description

If $varname is not the empty string, it should be the name of a variable in the caller's context.

  • If the attribute is present, the command returns 1 and stores the attribute value in the variable.
  • If the attribute is not present, the command returns 0.

If $varname is an empty string,

  • If the attribute is present, the command returns its value.
  • If the attribute is not present, the command raises an error.

If the record does not contain a value for the specified language, the value for the primary language will be retrieved.

Return value

Returns a boolean or the attribute text value.

proc ::iocp::bt::sdr::attribute::text {sdr attr_id lang {varname {}}} {

    # Get the value of an text attribute in the specified language
    # from a service discovery record.
    #  sdr     - Decoded service discovery record.
    #  attr_id - Universal attribute name or numeric identifier.
    #  lang - language identifier as specified in iso639, e.g. `en` for
    #         english, `fr` for french etc. or the keyword `primary`.
    #  varname - Optional name of variable in caller's context.
    #
    # If $varname is not the empty string, it should be the name of
    # a variable in the caller's context.
    #  - If the attribute is present, the command returns `1` and
    #    stores the attribute value in the variable.
    #  - If the attribute is not present, the command returns 0.
    #
    # If $varname is an empty string,
    #  - If the attribute is present, the command returns its value.
    #  - If the attribute is not present, the command raises an error.
    #
    # If the record does not contain a value for the specified language,
    # the value for the primary language will be retrieved.
    #
    # Returns a boolean or the attribute text value.
    #
    set lang_offset [names::attribute_id $attr_id]
    if {$lang_offset < 256 || $lang_offset >= 512} {
        error "Invalid text attribute id \"$attr_id\"."
    }
    incr lang_offset -256
    tailcall TextAttribute $sdr $lang_offset $lang $varname
}
# NOTE: showing source of procedure implementing ensemble subcommand.

attributes [::iocp::bt::sdr]sdr, Top, Main

Get the list of attributes in a service discovery record

attributes sdr
Parameters
sdrA decoded service discovery record in the form returned by decode.
Return value

Returns a list of numeric attribute ids.

proc ::iocp::bt::sdr::attributes {sdr} {

    # Get the list of attributes in a service discovery record
    # sdr - a decoded service discovery record in the form returned by
    #       [decode].
    # Returns a list of numeric attribute ids.
    return [lmap {attr val} $sdr {set attr}]
}

decode [::iocp::bt::sdr]sdr, Top, Main

Decodes a binary service discovery record

decode binsdr
Parameters
binsdrA raw Bluetooth service discovery record in binary form.
Description

The returned value should be treated as opaque. The attributes stored in the record should be accessed with the commands in the sdr namespace.

Return value

Returns a container of attributes stored in the record.

proc ::iocp::bt::sdr::decode {binsdr} {

    # Decodes a binary service discovery record
    # binsdr - a raw Bluetooth service discovery record in binary
    #       form.
    #
    # The returned value should be treated as opaque. The attributes
    # stored in the record should be accessed with the commands in
    # the `sdr` namespace.
    #
    # Returns a container of attributes stored in the record.

    # A SDR record is a single data element which is a sequence
    # containing nested data elements.
    # {{sequence {nested data elements}}}
    set rec [lindex [DecodeElements $binsdr] 0 1]

    # $rec contains alternating attribute value pairs. The result
    # is built as a list as its faster to do that and shimmer
    # to a dict on access than to build a dictionary to begin with.
    set sdr {}
    foreach {attr val} $rec {
        lappend sdr [lindex $attr 1] $val
    }
    return $sdr
}

print [::iocp::bt::sdr]sdr, Top, Main

Prints a SDP record to a more human readable form.

print rec ?attrfilter?
Parameters
recA binary SDP record in the form returned by ::iocp::bt::device services or ::iocp::bt::device service_references.
attrfilterIf specified, only attribute names matching the filter using string match are printed. Optional, default *.
proc ::iocp::bt::sdr::print {rec {attrfilter *}} {

    # Prints a SDP record to a more human readable form.
    # rec - a binary SDP record in the form returned by [::iocp::bt::device services] or
    #       [::iocp::bt::device service_references].
    # attrfilter - If specified, only attribute names matching the filter
    #       using `string match` are printed.
    #

    # Alternating attribute value pairs
    set rec [decode $rec]
    foreach attr [attributes $rec] {
        set attrname [names::attribute_name $attr]
        if {[string is integer -strict $attrname]} {
            # For easier matching with Bluetooth specs
            set attrname [format 0x%x $attrname]
        }
        if {![string match -nocase $attrfilter $attrname]} {
            continue
        }
        switch -exact -- $attrname {
            ServiceID -
            ServiceRecordState -
            ServiceInfoTimeToLive -
            ServiceAvailability -
            DocumentationURL -
            ClientExecutableURL -
            IconURL -
            ServiceName -
            ServiceDescription -
            ProviderName -
            ServiceRecordHandle {
                puts "$attrname: [attribute get $rec $attrname]"
            }
            ServiceClassIDList {
                puts "$attrname: sequence"
                foreach elem [attribute get $rec $attrname] {
                    dict with elem {
                        # We use this for printing service-specific attributes
                        lappend service_class_uuids $Uuid
                        if {$Name eq $Uuid} {
                            puts "    $Uuid"
                        } else {
                            puts "    $Uuid $Name"
                        }
                    }
                }
            }
            ProtocolDescriptorList {
                puts "$attrname:"
                foreach protocol [attribute get $rec $attrname] {
                    puts "    ProtocolStack:"
                    foreach layer $protocol {
                        set name [dict get $layer ProtocolName]
                        set uuid [dict get $layer Protocol]
                        set params [lmap param [dict get $layer ProtocolParams] {
                            PrintableElement $param
                        }]
                        if {$name eq ""} {
                            puts "        $uuid ([join $params {, }])"
                        } else {
                            puts "        $uuid $name ([join $params {, }])"
                        }
                    }
                }
            }
            AdditionalProtocolDescriptorList {
                # Like ProtocolDescriptorList but an additional level
                # of nesting.
                puts "$attrname:"
                foreach additional_protocol [attribute get $rec $attrname] {
                    foreach protocol $additional_protocol {
                        puts "    ProtocolStack:"
                        foreach layer $protocol {
                            set name [dict get $layer ProtocolName]
                            set uuid [dict get $layer Protocol]
                            set params [lmap param [dict get $layer ProtocolParams] {
                                PrintableElement $param
                            }]
                            if {$name eq ""} {
                                puts "        $uuid ([join $params {, }])"
                            } else {
                                puts "        $uuid $name ([join $params {, }])"
                            }
                        }
                    }
                }
            }
            BrowseGroupList {
                puts "$attrname: sequence"
                foreach elem [attribute get $rec $attrname] {
                    dict with elem {
                        if {$Name eq $Uuid} {
                            puts "    $Uuid"
                        } else {
                            puts "    $Uuid $Name"
                        }
                    }
                }
            }
            LanguageBaseAttributeIDList {
                puts "$attrname: sequence"
                dict for {lang val} [attribute get $rec $attrname] {
                    puts "    $lang: [dict get $val Encoding], [dict get $val BaseOffset]"
                }
            }
            BluetoothProfileDescriptorList {
                puts "$attrname: sequence"
                foreach profile [attribute get $rec $attrname] {
                    # Sequence of profiles. Each profile is a sequence of
                    # profile uuid and version.
                    # puts profile:$profile
                    dict with profile {
                        if {$Uuid eq $Name} {
                            puts "    $Uuid v$MajorVersion.$MinorVersion"
                        } else {
                            puts "    $Uuid $Name v$MajorVersion.$MinorVersion"
                        }
                    }
                }
            }
            default {
                if {$attr >= 0x100 && $attr <= 0x1ff} {
                    # TBD - special handling for 0x100-0x1ff attributes by
                    # looking up languages table
                    puts "$attrname: [PrintableElement [attribute raw $rec $attr]]"
                } else {
                    lassign [PrintableServiceSpecificAttribute  $service_class_uuids $rec $attr] attrname attrval
                    puts "$attrname: $attrval"
                }
            }
        }
    }
}

printn [::iocp::bt::sdr]sdr, Top, Main

Prints a SDP record to a more human readable form.

printn recs ?attrfilter?
Parameters
recsA list of binary SDP records in the form returned by ::iocp::bt::device services or ::iocp::bt::device service_references.
attrfilterIf specified, only attribute names matching the filter using string match are printed. Optional, default *.
proc ::iocp::bt::sdr::printn {recs {attrfilter *}} {

    # Prints a SDP record to a more human readable form.
    # recs - a list of binary SDP records in the form returned by
    #        [::iocp::bt::device services] or [::iocp::bt::device service_references].
    # attrfilter - If specified, only attribute names matching the filter
    #       using `string match` are printed.
    #
    set sep ""
    foreach rec $recs {
        puts $sep
        set sep "--------------------------------------------"
        print $rec $attrfilter
    }
}
Document generated by Ruff!