Obex (v0.1)

::obexTop, Main, Index

The obex namespace contains the Client and Server classes which implement the Generic Object Exchange Profile (GOEP) on which all other OBEX profiles are based. These classes may be used to access or provide any OBEX based service but require the application to have more knowledge of the profile with which that service is compliant. The profile-specific classes are easier to use in that regard.

ClientImplements GOEP client functionality.
ServerImplements GOEP server functionality.

OBEX operationsobex, Top, Main, Index

An OBEX operation consists of the client making one of the requests in the table below by calling the method of the same name. This is the request phase during which multiple packets may be exchanged with the server. The exchange then enters the response phase in which the server responds to the request via another multiple packet exchange. A single request may be in progress on a single transport connection at a time.

connectInitiate a conversation and establish context.
disconnectTerminate a conversation.
putSend an object to the server.
getRetrieve an object from the server.
setpathSets the object directory location on the server.
sessionUsed for reliable session support over unreliable transports. Not supported by the obex package.
abortSpecial request sent to abort an ongoing request.

The normal mode of operation consists of a sequence of requests starting with a connect, ending with a disconnect, and one or more of the other requests in between. Note that the connect and disconnect are optional for some some servers which will accept the put and get requests without a preceding connect.

Packet transfer modelobex, Top, Main, Index

The OBEX session protocol allows for one request at a time. This one request may result in multiple packets in both directions that need to be processed for the request to be completed. Rather than carrying out this communications itself, the Client and Server objects depend on the application itself to do the actual packet transfer. This makes the implementation independent of the channels, whether synchronous or event-driven I/O is used and so on. For all it knows, the data is transferred by encapsulating in E-mail.

Generating requestsobex, Top, Main, Index

From the client side, a request to connect looks as below:

obex::Client create Client
lassign [client connect] step data
while {$step eq "continue"} {
    if {[string length $data]} {
        ...send $data to server...
    }
    set reply [...get data from server..]
    lassign [client input $reply]
}
if {$step eq "done"} {
    # Operation completed. Check if successful.
    if {[client status] eq "success"} {
        ... handle success ...
    } else {
        ... handle error ...
    }
} else {
    # assert $step == "failed". Operation could not be completed.
    ... Handle bad news ...
}

Although this fragment used the connect operation, the model is exactly the same for other operations such as get, put etc. All the methods that implement these operations return a pair consisting of the next step to take and optionally data to send to the server. The application then sends data, if any, to the server. Then if the step value was continue, application needs to read additional data and feed whatever it gets (at least one byte) to the Client.input method. This step is repeated as long as the input method returns continue. At any state, a method may return done indicating all communication is over and the request completed or failed indicated the request could not be completed. Note that done only indicates the operation was completed, not that it was successful. More on this in Request completion status.

The above illustrates the conceptual model but of course the application may choose to do the equivalent non-sequentially via the event loop and non-blocking I/O.

Request completion statusobex, Top, Main, Index

The completion of a request is indicated by a return value of done, writable or failed from the operation methods.

The value writable is only returned in a PUT streaming operation to indicate the next chunk of the data stream may be sent. See Client.put_stream for details.

The value failed indicates a complete response was not received from the server. The cause may be protocol version incompatibility, protocol errors, loss of connectivity and so on.

The value done indicates a full and valid response was received from the server. However, this does not mean that the request itself was successful as the server response may indicate failure or some other status. This status can be checked with the Client.status method which returns one of the following values: success, informational, redirect, clienterror, servererror, databaseerror or protocolerror.

Each request completion status value corresponds to one of several OBEX response codes from the server. The actual response code may be obtained with the Client.status_detail method. The ResponseCode and ResponseCodeName dictionary keys returned by the method contain the numeric and mnemonic values.

A status of success includes the following response codes (mnemonic values shown):

okSuccess.
createdObject was created.
acceptedRequest accepted.
nonauthoritativeNon-authoritative information.
nocontentNo content.
resetcontentReset content.
partialcontentPartial content.

A status of informational includes the following response codes:

continueClient should send next packet in the request. This is internally handled by the package.

A status of redirect includes the following response codes and indicates the resource or object is available elsewhere or by some other means.

multiplechoicesMultiple choices.
movedpermanentlyMoved permanently.
movedtemporarilyMoved temporarily.
seeotherSee other.
notmodifiedNot modified.
useproxyUse proxy.

A status of protocolerror includes the following response codes:

protocolerrorGenerated internally by the obex package if a protocol error occured. It does not actually map to a OBEX response.

A status of clienterror indicates an error by the client in its request. It includes the following response codes:

badrequestBad request. Server could not understand request.
unauthorizedUnauthorized.
paymentrequiredPayment required.
forbiddenForbidden. Request understood but denied.
notfoundNot found.
methodnotallowedMethod not allowed.
notacceptableRequest not acceptable.
proxyauthenticationrequiredProxy authentication required.
requesttimeoutRequest timed out.
conflictConflict.
goneGone.
lengthrequiredLength required.
preconditionfailedPrecondition failed.
requestedentitytoolargeRequested entity too large.
requesturltoolargeRequest URL too large.
unsupportedmediatypeUnsupported media.

A status of servererror indicates an error on the server in responding to a request and includes the following response codes:

internalservererrorInternal server error.
notimplementedNot implemented.
badgatewayBad gateway.
serviceunavailableService unavailable.
gatewaytimeoutGateway timed out.
httpversionnotsupportedVersion not supported.

A status of databaseerror includes the following response codes:

databasefullDatabase full.
databaselockedDatabase locked.

Synchronous completionobex, Top, Main, Index

As a convenience most suitable for interactive use, the Client.await method can be used instead of the above idiom to synchronously wait for a request to complete. The equivalent of the above example would be

set status [client await $chan [client connect]]
if {$status eq "done"} {
    ...
} else {
    ...
}

This runs the "continue" loop shown previously internally until the request succeeds or fails. The disadvantage of this method is that it will block the event loop until completion and offers no protection against timeouts, a non-responsive server and other such errors.

Channel configurationobex, Top, Main, Index

OBEX is a binary protocol. Any channels used to pass data should therefore be configured to be in binary mode. Moreover, because OBEX packets are small and never have more than one outstanding, buffering should be turned off.

chan configure $chan -translation binary -buffering none

Generating responsesobex, Top, Main, Index

[TBD] Server not implemented

OBEX headersobex, Top, Main, Index

The actual object itself, and any related metadata about it, is transferred in OBEX packets as a sequence of headers. For example, in a file transfer using get operations, the request may contain a Name header specifying the requested file while the response would include Body and Timestamp headers containing the file content and time of creation respectively.

The headers that allowed a OBEX conversation and the context in which they are used are defined by the profile followed by the application.

A header consists of two parts:

Header values may be a string, a binary (sequence of bytes), a 8-bit value or a 32-bit value. When passing header values into obex commands, the caller has to ensure the value is formatted appropriately. For strings and integers, this is straightforward. For byte sequences, caller must ensure the value is generated as a binary using the binary format or encoding convertto commands, read from a binary channel and so on.

The table below shows the header identifiers.

AppParametersByte sequence. Used by layered applications to include additional information in a request or response. The value is a byte sequence of (tag,length,value) triples where tag and length are one byte each. Tags and semantics are defined by the application.
AuthChallengeByte sequence. Authentication challenge.
AuthResponseByte sequence. Authentication response.
BodyByte sequence. A chunk of the object content.
ConnectionId32-bit. The connection id used when multiplexing multiple OBEX connections over one transport connection.
Count32-bit. Number of objects involved in the operation.
CreatorId32-bit. Unsigned integer that identifies the creator of an object.
DescriptionString. Describes the object or provides additional information about the operation, errors etc.
EndOfBodyByte sequence. The last chunk of the object content.
HttpByte sequence. This has the same format as HTTP 1.x headers and should be parsed as HTTP headers with the same semantics.
Length32-bit. Length of object in bytes.
NameString. Name of the object, e.g. a file name.
ObjectClassByte sequence. Similar in function to the Type header except the scope of the semantics are specific to the layered application.
SessionParametersByte sequence. Parameters in session commands.
SessionSequenceNumber8-bit. Used for sequencing packets in a session.
TargetByte sequence. Specifies the service to process a request. Must be the first header in a request packet if present and cannot be used together with the ConnectionId header within a request.
TimestampByte sequence. Represents time of last modification of the object. This should be in ISO 8601 format as YYYYMMDDTHHMMSS for local time and YYYYMMDDTHHMMSSZ for UTC. Note this is a byte sequence and not a string.
Timestamp432-bit. Represents time of last modification as number of seconds since January 1, 1970.
TypeByte sequence. Describes the type of the object in the same manner as HTTP's Content-Header header. The value is a byte sequence of ASCII characters terminated by a null, not a string.
WanUuidByte sequence. Only used in stateless networks environments where the OBEX server resides on network client with the OBEX client residing on the network server. The OBEX server (the network client) then includes this in all responses.
WhoByte sequence. Similar to the Target header except that while Target in a request identifies the desired service, Who in a response identifies the service generating the response.

Classesobex, Top, Main, Index

Client [::obex]obex, Top, Main, Index

Method summary
constructorConstructor for the class.
abortGenerates a Obex ABORT request.
awaitSynchronously completes an ongoing operation.
bodiesSee Helper.bodies
clearClears error state if any.
connectGenerates a Obex connect request.
connectedReturns 1 if the client has an OBEX connection active.
disconnectGenerates a Obex disconnect request.
getGenerates a Obex GET request.
headersSee Helper.headers
idleReturns 1 if another request can be issued, otherwise 0.
inputProcess data from the remote server.
putGenerates a Obex PUT request.
put_deleteGenerates a Obex PUT request to delete an object.
put_streamGenerates a Obex PUT request with content provided in chunks.
resetResets state of the object.
responseReturns the last response received from the server.
sessionGenerate a OBEX session request.
setpathGenerates a Obex SETPATH request.
stateReturns the state of the client.
statusReturns the status of the last response received.
status_detailReturns the detailed status of the last response received.
Mixins

Helper

Subclasses

::obex::opp::Client

constructor [::obex::Client]Client, Top, Main, Index

Client create OBJNAME ?args?
Client new ?args?
Parameters
method constructor {args} {

    namespace path [linsert [namespace path] end ::obex ::obex::core]
    my reset
    if {[llength [self next]]} {
        next {*}$args
    }
}

abort [::obex::Client]Client, Top, Main, Index

Generates a Obex ABORT request.

OBJECT abort ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if valid for ABORT requests. The ConnectionId header is automatically generated as needed and should not be included by the caller.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method abort {{headers {}}} {

    # Generates a Obex `ABORT` request.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if valid for `ABORT` requests.
    # The `ConnectionId` header is automatically generated as needed
    # and should not be included by the caller.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    my BeginRequest abort
    set state(headers_out) [header encoden $headers]
    set packet [my OutgoingPacket 0xff 0]
    # Check if all headers were accomodated
    if {[llength $state(headers_out)]} {
        # Not all headers fit. Abort request must be a single packet
        my RaiseError "Headers too long for abort request."
    }
    return [list continue $packet]
}

await [::obex::Client]Client, Top, Main, Index

Synchronously completes an ongoing operation.

OBJECT await chan action_state
Parameters
chanA Tcl channel for communicating with the server. This must not be multiplexed with other requests.
action_stateThe return value from a method that returns an action state such as connect, disconnect, get, put, put_delete, put_stream, setpath, abort or input.
Description

The method places the passed channel into binary blocking mode and uses it to communicate with the server to complete all remaining steps required for the operation to complete. The original modes are restored before returning.

Return value

Returns done, writable or failed. See input. Note the method will not return continue.

method await {chan action_state} {

    # Synchronously completes an ongoing operation.
    #  chan - A Tcl channel for communicating with the server. This
    #    must not be multiplexed with other requests.
    #  action_state - The return value from a method that returns
    #    an action state such as [connect], [disconnect], [get],
    #    [put], [put_delete], [put_stream], [setpath], [abort] or [input].
    #
    # The method places the passed channel into binary blocking mode and
    # uses it to communicate with the server to complete all remaining
    # steps required for the operation to complete. The original modes are
    # restored before returning.
    #
    # Returns `done`, `writable` or `failed`. See [input]. Note the
    # method will not return `continue`.

    set chan_config [chan configure $chan]
    chan configure $chan -blocking 1 -buffering none  -translation binary
    try {
        return [my Await $chan $action_state]
    } finally {
        # Restore original config. Note -encoding and -eofchar
        # need explicitly set as -translation binary above
        # changes them but not changed back by -translation below.
        chan configure $chan  -blocking [dict get $chan_config -blocking]  -buffering [dict get $chan_config -buffering]  -encoding [dict get $chan_config -encoding]  -translation [dict get $chan_config -translation]  -eofchar [dict get $chan_config -eofchar]
    }
}

clear [::obex::Client]Client, Top, Main, Index

Clears error state if any.

OBJECT clear
Description

The object is restored to an idle state readying it for another request. The command will raise an error if called while a request is in progress.

method clear {} {

    # Clears error state if any.
    #
    # The object is restored to an idle state readying it for another
    # request. The command will raise an error if called while a request
    # is in progress.
    my ResetRequest
    set state(state) IDLE
    return
}

connect [::obex::Client]Client, Top, Main, Index

Generates a Obex connect request.

OBJECT connect ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if any are acceptable in connect request. The following headers are commonly used in connects: Target, Who, Count, Length and Description.

The method should not be called multiple times without an intervening call to disconnect.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method connect {{headers {}}} {

    # Generates a Obex connect request.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if any are acceptable in `connect` request.
    # The following headers are commonly used in connects:
    # `Target`, `Who`, `Count`, `Length` and `Description`.
    #
    # The method should not be called multiple times without an
    # intervening call to [disconnect].
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    if {$state(connected)} {
        error "Already connected."
    }

    my BeginRequest connect
    set state(headers_out) [header encoden $headers]
    # Packet is opcode 0x80, 2 bytes length, version (1.0->0x10),
    # flags (0), 2 bytes max len (proposed)
    set extra [binary format cucuSu 0x10 0 65535]
    set packet [my OutgoingPacket 0x80 0 $extra]
    if {[llength $state(headers_out)]} {
        # Not all headers fit. Connect request must be a single packet
        my RaiseError "Headers too long for connect request."
    }
    return [list continue $packet]
}

connected [::obex::Client]Client, Top, Main, Index

Returns 1 if the client has an OBEX connection active.

OBJECT connected
Return value

Returns 1 if the client has an OBEX connection active.

method connected {} {

    # Returns 1 if the client has an OBEX connection active.
    return $state(connected)
}

disconnect [::obex::Client]Client, Top, Main, Index

Generates a Obex disconnect request.

OBJECT disconnect ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if valid for disconnect requests. The ConnectionId header is automatically generated as needed and shoould not be included by the caller.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method disconnect {{headers {}}} {

    # Generates a Obex disconnect request.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if valid for `disconnect` requests.
    # The `ConnectionId` header is automatically generated as needed
    # and shoould not be included by the caller.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    if {!$state(connected)} {
        error "Not connected."
    }
    my BeginRequest disconnect
    set state(headers_out) [header encoden $headers]
    set packet [my OutgoingPacket 0x81 0]
    # Check if all headers were accomodated
    if {[llength $state(headers_out)]} {
        # Not all headers fit. Disconnect request must be a single packet
        my RaiseError "Headers too long for disconnect request."
    }
    set state(connected) false
    return [list continue $packet]
}

get [::obex::Client]Client, Top, Main, Index

Generates a Obex GET request.

OBJECT get ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if any are acceptable in put request. The following headers are commonly used in put operations: Name, Type, Http, Timestamp and Description.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method get {{headers {}}} {

    # Generates a Obex `GET` request.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if any are acceptable in `put` request.
    # The following headers are commonly used in put operations:
    # `Name`, `Type`, `Http`, `Timestamp` and `Description`.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    my BeginRequest get
    set state(headers_out) [header encoden $headers]
    return [list continue [my OutgoingPacket 0x03 0]]
}

idle [::obex::Client]Client, Top, Main, Index

Returns 1 if another request can be issued, otherwise 0.

OBJECT idle
Return value

Returns 1 if another request can be issued, otherwise 0.

method idle {} {

    # Returns 1 if another request can be issued, otherwise 0.
    return [expr {$state eq "IDLE"}]
}

input [::obex::Client]Client, Top, Main, Index

Process data from the remote server.

OBJECT input data
Parameters
dataBinary data as received from remote server.
Description

The method takes as input data received from the server as part of the response to a request. The return value from the method is a list of one or two elements. The first element is one of the following:

doneThe full response has been received. The application can then call any of the retrieval methods or initiate another request. If the second element is present and and not empty, it is data to be sent to the server. The application can call other methods to retrieve the result of the request. The application may also call methods to initiate the next request.
continueThe response has only been partially received. If the second element is present and not empty, it is data to be sent to the server. In either case, the application should read more data from the server and invoke the input method again passing it the read data.
writableThis value is only returned if the current operation was a streaming put operation initiated with put_stream. It indicates that put_stream should be called again to send the next chunk of data.
failedThe request failed. See Request completion status for dealing with errors and failures. If the second element is present and not empty, it is data to be sent to the server. In either case, the application must not invoke additional requests without first calling the reset method.

This method will raise an exception if no request is currently outstanding.

Return value

Returns a list of one or two elements, the first being one of done, failed, continue or writable and the second optional element being data to send to the server.

method input {data} {

    # Process data from the remote server.
    #   data - Binary data as received from remote server.
    # The method takes as input data received from the server as part of
    # the response to a request. The return value from the method is a list
    # of one or two elements. The first element is one of the following:
    #   `done`       - The full response has been received. The application
    #                  can then call any of the retrieval methods or initiate
    #                  another request. If the second element is present and
    #                  and not empty, it is data to be sent to the server.
    #                  The application can call other methods to retrieve
    #                  the result of the request. The application may also
    #                  call methods to initiate the next request.
    #   `continue`   - The response has only been partially received. If
    #                  the second element is present and not empty, it is
    #                  data to be sent to the server. In either case, the
    #                  application should read more data from the server
    #                  and invoke the `input` method again passing it the
    #                  read data.
    #   `writable`   - This value is only returned if the current operation
    #                  was a streaming `put` operation initiated with
    #                  [put_stream]. It indicates that [put_stream] should
    #                  be called again to send the next chunk of data.
    #   `failed`     - The request failed. See [Request completion status]
    #                  for dealing with errors and failures. If
    #                  the second element is present and not empty, it is
    #                  data to be sent to the server. In either case, the
    #                  application must not invoke additional requests
    #                  without first calling the [reset] method.
    #
    # This method will raise an exception if no request is currently
    # outstanding.
    #
    # Returns a list of one or two elements, the first being one of
    # `done`, `failed`, `continue` or `writable` and the second optional
    # element being data to send to the server.

    my AssertState BUSY

    # Append new data to existing
    append state(input) $data

    if {! [response decode $state(input) $state(op) response]} {
        return continue;    # Incomplete packet, need more input
    }

    # TBD - should this be a protocol error if input was longer than packet
    # Possibly a response followed by an ABORT?
    set state(input) [string range $state(input) [dict get $response PacketLength] end]

    # If we have a connection id, the incoming one must match if present
    if {[info exists connection_id]} {
        if {![header find $response(Headers) ConnectionId conn_id] ||
            $connection_id != $conn_id} {
            # TBD - ignore mismatches for now
        }
    }

    # Save as latest response
    set state(response) $response

    # For multipart responses, collect headers
    lappend state(headers_in) {*}[dict get $response Headers]

    # Do request-specific processing
    return [switch -exact -- $state(op) {
        connect    { my ConnectResponseHandler }
        disconnect { my ResponseHandler false }
        put        { my ResponseHandler true }
        get        { my ResponseHandler true }
        setpath    { my ResponseHandler false }
        session    { error "Internal error: session request."}
        abort      { my ResponseHandler false }
        default {
            error "Unexpected request opcode $state(op)."
        }
    }]
}

put [::obex::Client]Client, Top, Main, Index

Generates a Obex PUT request.

OBJECT put content ?headers?
Parameters
contentContent to be sent to server as-is. This must be formatted appropriately based on the Type header, Http or ObjectClass headers passed in. If none of these are present, the server may interpret $content in any manner it chooses, possibly looking at the Name header if present, some default handling or even rejecting the request.
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if any are acceptable in put request. The following headers are commonly used in put operations: Name, Type, Http, Timestamp and Description. The headers Body, EndOfBody, Length and ConnectionId are automatically generated and should not be passed in.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method put {content {headers {}}} {

    # Generates a Obex `PUT` request.
    #  content - Content to be sent to server as-is. This must be formatted
    #   appropriately based on the `Type` header, `Http` or `ObjectClass`
    #   headers passed in. If none of these are present,
    #   the server may interpret $content in any
    #   manner it chooses, possibly looking at the `Name` header if present,
    #   some default handling or even rejecting the request.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if any are acceptable in `put` request.
    # The following headers are commonly used in put operations:
    # `Name`, `Type`, `Http`, `Timestamp` and `Description`.
    # The headers `Body`, `EndOfBody`, `Length` and `ConnectionId`
    # are automatically generated and should not be passed in.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    # TBD - maybe break up content into body headers assuming body space
    #  is packet size - packet header - connection id header. That would
    # simplify Put method

    my BeginRequest put
    lappend headers Length [string length $content] {*}[my SplitContent $content]
    set state(headers_out) [header encoden $headers]
    return [list continue [my OutgoingPacket 0x02 0]]
}

put_delete [::obex::Client]Client, Top, Main, Index

Generates a Obex PUT request to delete an object.

OBJECT put_delete ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if any are acceptable in put request. The following headers are commonly used in put operations: Name, Type, Http, Timestamp and Description. The headers Body, EndOfBody, Length should not be present in a delete operation and should not be passed in. Moreover, ConnectionId header is automatically generated and should not be passed in.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method put_delete {{headers {}}} {

    # Generates a Obex `PUT` request to delete an object.
    #  headers - List of alternating header names and values.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if any are acceptable in `put` request.
    # The following headers are commonly used in put operations:
    # `Name`, `Type`, `Http`, `Timestamp` and `Description`.
    # The headers `Body`, `EndOfBody`, `Length` should not be present
    # in a delete operation and should not be passed in. Moreover,
    # `ConnectionId` header is automatically generated and should not
    # be passed in.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    my BeginRequest put
    set state(headers_out) [header encoden $headers]
    return [list continue [my OutgoingPacket 0x02 0]]
}

put_stream [::obex::Client]Client, Top, Main, Index

Generates a Obex PUT request with content provided in chunks.

OBJECT put_stream chunk ?headers?
Parameters
chunkA content chunk to be sent. An empty string indicates end of the stream. This must be formatted appropriately based on the Type header, Http or ObjectClass headers passed in the initial call to put_stream. If none of these are present, the server may interpret content in any manner it chooses, possibly looking at the Name header if present, some default handling or even rejecting the request.
headersList of alternating header names and values. Should be empty on all calls except the first. Optional, default "".
Description

This is similar to the put method except that it permits the caller to present the data to be sent in chunks instead of all at once. The application provides each chunk of the content in repeated calls to put_stream. This is more memory-efficient for large files. Only the first call initiating the PUT operation may specify headers. Passing an empty string indicates end of the content.

Streaming operation is achieved through the following sequence of calls:

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if any are acceptable in put request. The following headers are commonly used in put operations: Name, Type, Http, Timestamp and Description. The headers Body, EndOfBody, and ConnectionId are automatically generated and should not be passed in. This method does not automatically add a Length header since the length is not known a priori. However, some server implementations require the Length header and therefore should be passed in as part of $headers in such cases.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method put_stream {chunk {headers {}}} {

    # Generates a Obex `PUT` request with content provided in chunks.
    #  chunk - A content chunk to be sent. An empty string indicates end
    #   of the stream. This must be formatted
    #   appropriately based on the `Type` header, `Http` or `ObjectClass`
    #   headers passed in the initial call to `put_stream`.
    #   If none of these are present,
    #   the server may interpret content in any
    #   manner it chooses, possibly looking at the `Name` header if present,
    #   some default handling or even rejecting the request.
    #  headers - List of alternating header names and values. Should
    #   be empty on all calls except the first.
    #
    # This is similar to the [put] method except that it permits the
    # caller to present the data to be sent in chunks instead of all at
    # once. The application provides each chunk of the content in repeated
    # calls to `put_stream`.
    # This is more memory-efficient for large files. Only the first
    # call initiating the `PUT` operation may specify headers. Passing
    # an empty string indicates end of the content.
    #
    # Streaming operation is achieved through the following sequence of
    # calls:
    #
    #  * Streaming is initiated through a call to `put_stream` which
    #    normally returns `continue` with data to send to the server.
    #  * The [input] method is called with the response from the server.
    #    This returns `writable` indicating more data can be sent. The
    #    application then calls `put_stream` again with the next chunk.
    #  * This `input`, `put_stream` sequence is repeated until there
    #    is no more data to send at which time the application should
    #    call `put_stream` with an empty chunk.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if any are acceptable in `put` request.
    # The following headers are commonly used in put operations:
    # `Name`, `Type`, `Http`, `Timestamp` and `Description`.
    # The headers `Body`, `EndOfBody`, and `ConnectionId`
    # are automatically generated and should not be passed in.
    # This method does not automatically add a `Length` header since
    # the length is not known a priori. However, some server implementations
    # require the `Length` header and therefore should be passed in
    # as part of $headers in such cases.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.
    if {$state(state) eq "IDLE"} {
        my BeginRequest put
    } else {
        my AssertState STREAMING
        if {[llength $headers] != 0} {
            my RaiseError "Headers only allowed in first chunk of a data stream."
        }
    }
    if {[string length $chunk] != 0} {
        # Note no Length header as unknown!
        lappend headers {*}[my SplitContent $chunk]
        set state(streaming) 1
    } else {
        lappend headers EndOfBody {}
        set state(streaming) 0
        # TBD - do we need to guard against more put_stream calls
        # after EndOfBody is sent?
    }
    set state(headers_out) [header encoden $headers]
    set state(state) BUSY
    return [list continue [my OutgoingPacket 0x02 0]]
}

reset [::obex::Client]Client, Top, Main, Index

Resets state of the object.

OBJECT reset
Description

The object is placed in the same state as when it was newly constructed. All state information is lost.

method reset {} {

    # Resets state of the object.
    #
    # The object is placed in the same state as when it was newly constructed.
    # All state information is lost.

    # Connection specific state
    set state(state)  IDLE
    set state(connected) false
    unset -nocomplain state(connection_id)
    unset -nocomplain state(connection_header)
    unset -nocomplain state(target)
    unset -nocomplain state(who)
    set state(max_packet_len) 255; # Assume min unless remote tells otherwise

    # Request specific state
    my ResetRequest
}

response [::obex::Client]Client, Top, Main, Index

Returns the last response received from the server.

OBJECT response
Description

The return value is a dictionary in the same form as returned by the ::obex::core::response decode command.

Return value

Returns the last response received from the server.

method response {} {

    # Returns the last response received from the server.
    #
    # The return value is a dictionary in the same form as returned by
    # the [::obex::core::response decode] command.
    return $state(response)
}

session [::obex::Client]Client, Top, Main, Index

Generate a OBEX session request.

OBJECT session ?headers?
Parameters
headersList of alternating header names and values. Optional, default "".
Description

This command is not implemented.

method session {{headers {}}} {

    # Generate a OBEX `session` request.
    #  headers   - List of alternating header names and values.
    # This command is not implemented.
    error "Sessions not implemented in this release."
}

setpath [::obex::Client]Client, Top, Main, Index

Generates a Obex SETPATH request.

OBJECT setpath ?headers? ?args?
Parameters
headersList of alternating header names and values. Optional, default "".
-nocreateDo no create folder if it does not exist.
-parentApply operation at the parent's level.
Description

It is the caller's responsibility to ensure the value associated with the header is formatted as described in OBEX headers and that the supplied headers if valid for SETPATH requests. The ConnectionId header is automatically generated as needed and shoould not be included by the caller.

Return value

Returns a list of one or two elements, the first of which is either continue or failed, and the second, if present, is data to be sent to the server. See input for details.

method setpath {{headers {}} args} {

    # Generates a Obex `SETPATH` request.
    #  headers   - List of alternating header names and values.
    #  -parent   - Apply operation at the parent's level.
    #  -nocreate - Do no create folder if it does not exist.
    #
    # It is the caller's responsibility to ensure the value associated
    # with the header is formatted as described in [OBEX headers] and
    # that the supplied headers if valid for `SETPATH` requests.
    # The `ConnectionId` header is automatically generated as needed
    # and shoould not be included by the caller.
    #
    # Returns a list of one or two elements, the first of which is either
    # `continue` or `failed`, and the second, if present, is data to be sent
    # to the server. See [input] for details.

    my BeginRequest setpath

    set flags 0
    set constants 0
    foreach opt $args {
        switch -exact -- $opt {
            -parent { set flags [expr {$flags | 1}] }
            -nocreate { set flags [expr {$flags | 2}] }
            default {
                error "Unknown option \"$opt\"."
            }
        }
    }

    set state(headers_out) [header encoden $headers]
    set packet [my OutgoingPacket 0x85 0 [binary format cucu $flags $constants]]
    if {[llength $state(headers_out)]} {
        # Not all headers fit. setpath request must be a single packet
        my RaiseError "Headers too long for setpath request."
    }
    return [list continue $packet]
}

state [::obex::Client]Client, Top, Main, Index

Returns the state of the client.

OBJECT state
Description

The returned state is in the form of a dictionary with the following elements:

StateClient state; one of IDLE, BUSY or ERROR
Connected0/1 depending on whether connected or not.
ConnectionIdThe connection id. Only present if connected and remote server sent a connection id.
MaxPacketLengthMaximum packet length negotiated.
ErrorMessageIf present, the last error seen.
Return value

Returns the state of the client.

method state {} {

    # Returns the state of the client.
    #
    # The returned state is in the form of a dictionary with the following
    # elements:
    #   State - Client state; one of `IDLE`, `BUSY` or `ERROR`
    #   Connected - 0/1 depending on whether connected or not.
    #   ConnectionId - The connection id. Only present if connected
    #                  **and** remote server sent a connection id.
    #   MaxPacketLength - Maximum packet length negotiated.
    #   ErrorMessage - If present, the last error seen.

    set l [list State $state(state)  Connected $state(connected)  MaxPacketLength $state(max_packet_len)]

    if {[info exists state(connection_id)]} {
        lappend l ConnectionId $state(connection_id)
    }
    if {[info exists state(error_message)]} {
        lappend l ErrorMessage $state(error_message)
    }

    return $l
}

status [::obex::Client]Client, Top, Main, Index

Returns the status of the last response received.

OBJECT status
Description

The returned value is one of success, informational, redirect, clienterror, servererror, databaseerror or protocolerror. See ::obex::Request completion status.

The command will raise an error if no response has been received for any request.

Return value

Returns the status of the last response received.

method status {} {

    # Returns the status of the last response received.
    #
    # The returned value is one of
    # `success`, `informational`, `redirect`,
    # `clienterror`, `servererror`, `databaseerror` or `protocolerror`.
    # See [::obex::Request completion status].
    #
    # The command will raise an error if no response has been received
    # for any request.
    return [dict get $state(response) ResponseStatus]
}

status_detail [::obex::Client]Client, Top, Main, Index

Returns the detailed status of the last response received.

OBJECT status_detail
Description

The returned dictionary has the following keys:

ResponseStatusThe generic status category.
ResponseCodeThe numeric response code from server.
ResponseCodeNameMnemonic form of ResponseCode
ErrorMessageAdditional human readable error status message. This key may not be present.

For more information on values for the above keys, see ::obex::Request completion status.

The command will raise an error if no response has been received for any request.

Return value

Returns the detailed status of the last response received.

method status_detail {} {

    # Returns the detailed status of the last response received.
    #
    # The returned dictionary has the following keys:
    #  ResponseStatus   - The generic status category.
    #  ResponseCode     - The numeric response code from server.
    #  ResponseCodeName - Mnemonic form of `ResponseCode`
    #  ErrorMessage     - Additional human readable error status message. This
    #                     key may not be present.
    #
    # For more information on values for the above keys, see
    # [::obex::Request completion status].
    #
    # The command will raise an error if no response has been received
    # for any request.
    dict with state(response) {
        lappend status ResponseStatus $ResponseStatus  ResponseCode $ResponseCode  ResponseCodeName [response::ResponseCodeName $ResponseCode]
        if {[info exists state(error_message)]} {
            lappend status ErrorMessage $state(error_message)
        }
    }
    return $status
}

Helper [::obex]obex, Top, Main, Index

Method summary
bodiesGet the data content in a request or response.
headersRetrieves the content of headers of a given type.
Subclasses

Client, Server

bodies [::obex::Helper]Helper, Top, Main, Index

Get the data content in a request or response

OBJECT bodies
Description

The data content is transferred in Obex through headers of type Body and EndOfBody. This method returns the values received through these headers as a list. The content is in binary form and needs to be appropriately interpreted depending on the application specifics. Note the content may be fragmented at arbitrary boundaries during transmission and so the returned values may need to be concatenated before operations like UTF-8 decoding.

Return value

Returns list of binary strings received through Body and EndOfBody headers.

method bodies {} {

    # Get the data content in a request or response
    #
    # The data content is transferred in Obex through headers of type `Body`
    # and `EndOfBody`. This method returns the values received through these
    # headers as a list. The content is in binary form and needs to be
    # appropriately interpreted depending on the application specifics. Note
    # the content may be fragmented at arbitrary boundaries during
    # transmission and so the returned values may need to be concatenated
    # before operations like UTF-8 decoding.
    #
    # Returns list of binary strings received through `Body` and `EndOfBody`
    # headers.

    set bodies {}
    foreach {name val} $state(headers_in) {
        if {$name in {Body EndOfBody}} {
            lappend bodies $val
        }
    }
    return $bodies
}

headers [::obex::Helper]Helper, Top, Main, Index

Retrieves the content of headers of a given type.

OBJECT headers name
Parameters
nameName of the header.
Return value

Returns a list each element of which is the value of a header

method headers {name} {

    # Retrieves the content of headers of a given type.
    #  name - name of the header
    # Returns a list each element of which is the value of a header
    #
    return [header findall $state(headers_in)]
}

Server [::obex]obex, Top, Main, Index

Method summary
constructorConstructor for the class.
bodiesSee Helper.bodies
get_requestNot documented.
headersSee Helper.headers
inputProcess data received from a client.
resetResets state of the object.
respondNot documented.
respond_contentGenerate a response containing content.
stateReturns the state of the server.
Mixins

Helper

constructor [::obex::Server]Server, Top, Main, Index

Server create OBJNAME ?args?
Server new ?args?
Parameters
method constructor {args} {

    namespace path [linsert [namespace path] end ::obex ::obex::core]
    my reset
    if {[llength [self next]]} {
        next {*}$args
    }
}

get_request [::obex::Server]Server, Top, Main, Index

OBJECT get_request
method get_request {} {

    return $state(request)
}

input [::obex::Server]Server, Top, Main, Index

Process data received from a client.

OBJECT input data
Parameters
dataBinary data as received from client.
Description

The method takes as input data received from a client. The return value from the method is a list of one or two of one or two elements. The first element is one of done continue or failed. The semantics depend on whether the request is in the request phase or the response phase.

In the request phase,

doneThe request was completed without need for an explicit response from the application. If the the second element is present and not empty, it is data to be sent to the client.
respondThe full request has been received. The application should then call one of the response methods to reply to the client. If the second element is present and not empty, it is data to be sent to the client. The application can call other methods to retrieve the request.
continueThe request has only been partially received. If the second element is present and not empty, it is data to be sent to the client. In either case, the application should read more data from the client and again invoke the input method passing it the read data.
failedThe request has failed. See Request completion status for dealing with errors and failures. If the second element is present and not empty, it is data to be sent to the client. In either case, the application must not use this instance to accept additional requests without first calling the reset method.

In the response phase,

doneThe full response has been sent to the client. If the second element is present and not empty, it is data to be sent to the client. The application can then process a new request using this object.
continueThe response has only been partially sent. If the second element is present and not empty, it is data to be sent to the client. In either case, the application should read more data from the client and invoke the input method again passing it the read data.
failedThe request has failed. See Request completion status for dealing with errors and failures. If the second element is present and not empty, it is data to be sent to the client. In either case, the application must not use this instance to accept additional requests without first calling the reset method.
method input {data} {

    # Process data received from a client.
    #   data - Binary data as received from client.
    # The method takes as input data received from a client.
    # The return value from the method is a list of one or two
    # of one or two elements. The first element is one of `done`
    # `continue` or `failed`. The semantics depend on whether
    # the request is in the **request** phase or the **response**
    # phase.
    #
    # In the **request** phase,
    #   `done`  - The request was completed without need for an explicit
    #            response from the application. If the
    #            the second element is present and not empty, it is data
    #            to be sent to the client.
    #   `respond` - The full request has been received. The application should
    #            then call one of the response methods to reply to the client. If
    #            the second element is present and not empty, it is data
    #            to be sent to the client. The application can call other
    #            methods to retrieve the request.
    #   `continue` - The request has only been partially received. If the
    #            second element is present and not empty, it is data to be
    #            sent to the client. In either case, the application should
    #            read more data from the client and again invoke the [input]
    #            method passing it the read data.
    #   `failed` - The request has failed. See [Request completion status] for dealing
    #            with errors and failures. If the second element is present
    #            and not empty, it is data to be sent to the client. In
    #            either case, the application must not use this instance
    #            to accept additional requests without first calling the
    #            [reset] method.
    #
    # In the **response** phase,
    #   `done` - The full response has been sent to the client.
    #            If the second element is present and not empty, it is data
    #            to be sent to the client. The application can then
    #            process a new request using this object.
    #   `continue` - The response has only been partially sent. If the
    #            second element is present and not empty, it is data to be
    #            sent to the client. In either case, the application should
    #            read more data from the client and invoke the [input] method
    #            again passing it the read data.
    #   `failed` - The request has failed. See [Request completion status] for dealing
    #            with errors and failures. If the second element is present
    #            and not empty, it is data to be sent to the client. In
    #            either case, the application must not use this instance
    #            to accept additional requests without first calling the
    #            [reset] method.
    #

    switch -exact -- $state(state) {
        IDLE -
        REQUEST { my RequestPhaseInput $data}
        RESPOND { my ResponsePhaseInput $data}
        ERROR   { error "Method must not be called after an error without calling the reset method first."}
        default { error "Internal error: unknown state $state(state)"}
    }
}

reset [::obex::Server]Server, Top, Main, Index

Resets state of the object.

OBJECT reset
Description

The object is placed in the same state as when it was newly constructed. All state information is lost.

method reset {} {

    # Resets state of the object.
    #
    # The object is placed in the same state as when it was newly constructed.
    # All state information is lost.

    # Connection specific state
    set state(state)  IDLE
    unset -nocomplain state(connection_id); # Set when connect comes in
    unset -nocomplain state(connection_header)
    set state(max_packet_len) 255; # Assume min until remote tells otherwise

    # Request specific state
    my ResetRequest
}

respond [::obex::Server]Server, Top, Main, Index

OBJECT respond status ?headers?
Parameters
statusNot documented.
headersNot documented. Optional, default "".
method respond {status {headers {}}} {

    my AssertState RESPOND

    set state(headers_out) [header encoden $headers]
    set status [response::ResponseCode $status]
    set state(response_code) $status

    # CONNECT and DISCONNECT need special handling.
    set op [dict get $state(request) OpName]
    if {$op eq "connect"} {
        set state(max_packet_len) [dict get $state(request) MaxLength]
        set state(connection_id) [GenerateId]
        set state(connection_header)  [header encode ConnectionId $state(connection_id)]
        set state(headers_out) $headers
        # Packet is opcode 0x80, 2 bytes length, version (1.0->0x10),
        # flags (0), 2 bytes max len
        set extra_fields [binary format cucuSu 0x10 0 $state(max_packet_len)]
    } elseif {$op eq "disconnect"} {
        set extra_fields ""
        unset -nocomplain state(connection_id)
        unset -nocomplain state(connection_header)
        set state(max_packet_len) 255
    } else {
        set extra_fields ""
    }

    # TBD - assume a single packet response
    set status [expr {$status | 0x80}]
    set packet [my OutgoingPacket $status 1 $extra_fields]
    if {[llength $state(headers_out)]} {
        # TBD - don't know exactly how multipacket responses work when
        # status is not continue.
        my RaiseError "Response does not fit in a packet."
    }
    return [list done $packet]
}

respond_content [::obex::Server]Server, Top, Main, Index

Generate a response containing content.

OBJECT respond_content content ?headers?
Parameters
contentContent to include in the response to the client.
headersList of alternating header names and values. Optional, default "".
method respond_content {content {headers {}}} {

    # Generate a response containing content.
    #  content - content to include in the response to the client.
    #  headers - List of alternating header names and values.
    #
    my AssertState RESPOND
    lappend headers Length [string length $content] {*}[my SplitContent $content]
    set state(headers_out) [header encoden $headers]
    set state(response_code) $status
    set packet [my OutgoingPacket $state(response_code) 1]
    if {[llength $state(headers_out)]} {
        return [list continue $packet]
    } else {
        return [list done $packet]
    }
}

state [::obex::Server]Server, Top, Main, Index

Returns the state of the server.

OBJECT state
Description

The returned state is in the form of a dictionary with the following elements:

StateServer state; one of IDLE, REQUEST, RESPOND or ERROR
Connected0/1 depending on whether connected or not.
ConnectionIdThe connection id. Only present if connected.
MaxPacketLengthMaximum packet length negotiated.
ErrorMessageIf present, the last error seen.
Return value

Returns the state of the server.

method state {} {

    # Returns the state of the server.
    #
    # The returned state is in the form of a dictionary with the following
    # elements:
    #   State - Server state; one of `IDLE`, `REQUEST`, `RESPOND` or `ERROR`
    #   Connected - 0/1 depending on whether connected or not.
    #   ConnectionId - The connection id. Only present if connected.
    #   MaxPacketLength - Maximum packet length negotiated.
    #   ErrorMessage - If present, the last error seen.

    set l [list State $state(state)  MaxPacketLength $state(max_packet_len)]
    if {[info exists state(connection_id)]} {
        lappend l Connected 1 ConnectionId $state(connection_id)
    } else {
        lappend l Connected 0
    }
    if {[info exists state(error_message)]} {
        lappend l ErrorMessage $state(error_message)
    }
    return $l
}