[commits] [Wiki] changed: ActiveSync/Development
Wiki
wikiguest at horde.org
Thu Jun 25 09:20:40 UTC 2026
tdannhauer Thu, 25 Jun 2026 09:20:40 +0000
Modified page: https://wiki.horde.org/ActiveSync/Development
New Revision: 13
Change log: H6 Update
@@ -1,365 +1,109 @@
-# [ActiveSync](ActiveSync) Library Technical Information
+# ActiveSync Development
-This page is designed to give anyone working on the
[ActiveSync](ActiveSync) library in Horde 5.x a general overview of
logic flow and what happens where and when.
+Developer documentation for the Horde ActiveSync stack. **Horde 6 /
ActiveSync 3.x.**
-## General Library Structure
+## Where to read what
+| Topic | Location |
+| --- | --- |
+| Protocol versions, EAS 16 features, deployment config, integrator
API | [horde/activesync
`README.md`](https://github.com/horde/activesync/blob/FRAMEWORK_6_0/README.md)
-- **canonical**, versioned with the library |
+| Request flow, sync keys, class roles, tests | Same README --
*Architecture* and *Development and tests* sections |
+| Open refactor work and Horde 6 breaking plans |
[`doc/todo.md`](https://github.com/horde/activesync/blob/FRAMEWORK_6_0/doc/todo.md) in the package repo
|
+| Operator setup, Autodiscover, web server | [ActiveSync](ActiveSync) wiki |
+| Client quirks and server workarounds | [Broken Client
Behavior](ActiveSync/BrokenClientBehavior) |
+| Symptoms and unfixed client bugs | [Known Issues](ActiveSync/KnownIssues) |
+| Feature matrix vs other servers | [Feature Grid](ActiveSync/FeatureGrid) |
-The logic for handling [ActiveSync](ActiveSync) requests is split
between 3 different libraries currently - *Horde\_Rpc*, *Horde\_Core*,
and obviously *Horde\_ActiveSync*. Horde\_Rpc only handles the initial
request and basically just passes on control to Horde\_ActiveSync so
this page will focus only on the other two libraries.
+**Recommendation:** keep **architecture and protocol depth** in the
git `README.md` so it stays aligned with releases. This wiki page
covers **Horde deployment wiring** and points to the repo for
everything else. The old long-form class tour and 2016 synclog
walkthrough have been retired here in favour of the README.
-### Horde\_ActiveSync
+## Package split
+ActiveSync logic spans two Composer packages in a normal Horde install:
-This library contains the main logic for decoding WBXML from EAS
requests, passing the request to the appropriate controller and then
sending properly encoded responses back to the client.
+### `horde/activesync` -- protocol engine
-The following is a general overview of the important classes in this
library and what they are responsible for:
+Decodes WBXML, dispatches `Horde_ActiveSync_Request_*` handlers,
manages device
+state and sync keys, encodes responses. No direct Turba/Kronolith/IMP
calls --
+all data access goes through `Horde_ActiveSync_Driver_Base`.
-**Horde\_ActiveSync**: This is the main "server" and the entry point
for the library. Also contains some getters for various objects used
throughout.
-**Horde\_ActiveSync\_Collections**: This class is essentially a
manager for "collections". A collection is what we call a group of
properties related to a single synchable data store \(such as an email
folder, or a contact list\). It is the entry point for the SYNC and
PING handlers for asking for changes. It manages the SyncCache, is
responsible for ensuring we have all needed information when handling
empty SYNC requests, detects concurrent requests, and a bunch more.
-**Horde\_ActiveSync\_Device**: Manages device information such as OS
and version. Also contains code that works around certain quirks that
are specific to certain clients.
-**Horde\_ActiveSync\_Mime**: A wrapper around Horde\_Mime\_Part that
adds to and overrides existing behavior to make it more specific to
[ActiveSync](ActiveSync). There is also
Horde\_ActiveSync\_Mime\_Iterator that is changed from the the normal
Horde\_Mime\_Iterator behavior in that we don't include what EAS
considers attachments to be returned, and we also include the base
part when iterating \(whereas Horde\_Mime does not\). Objects are
instantiated from Horde\_ActiveSync\_Imap\_\* classes.
-**Horde\_ActiveSync\_Rfc822**: Deals with handling RFC822 email
message strings/streams in the context of EAS. E.g., used when
handling incoming SENDMAIL commands.
-**Horde\_ActiveSync\_SyncCache**: This class is responsible for
storing/loading the SyncCache data from the storage/state driver. The
SyncCache started life as a way of caching data from SYNC and PING
requests that would be needed if the client issued an "empty"
SYNC/PING request. Empty requests are required to use the same values
as the last non-empty request. It has since grown to be essentially a
shared data store that helps keep collection data up to date between
any and all running requests. Most \(if not all\) interaction with
this class is through the Horde\_ActiveSync\_Collections object.
-**Horde\_ActiveSync\_Connector**: These classes are responsible for
sending/receiving the structured WBXML for certain requests. E.g., the
H\_AS\_C\_Exporter\_Sync object contain methods that are called from
the H\_AS\_Request\_Sync object that send the WBXML needed for sending
each change. It is passed the message object, encodes it to WBXML, and
wraps it with any needed response WBXML before sending it down the
output stream. Likewise, the H\_AS\_C\_Importer object reads the
structured WBXML for importing changes. These methods are called from
the Horde\_ActiveSync\_Request\_\* classes after the changes have been
fetched.
-**Horde\_ActiveSync\_Driver\_Base**: This is the base class that
Horde\_Core\_ActiveSync\_Driver extends.
-**Horde\_ActiveSync\_Folder\_\***: These classes represent a specific
collection's state. It's an abstraction around the state data and
represents the point in time of the sync represented by the synckey.
For non-email collections, this basically stores the last known
syncstamp/time along with a few other details. For email folders, this
class stores things like the HIGHESTMODSEQ, UIDVALIDITY, NEXTUID, and
the list of IMAP UIDS that the are on the client etc... If not using a
server that supports CONDSTORE then this will also contain flag
information. Also contains logic used to set/get/calculate what UIDs
have changed/vanished etc... These objects are used to transport the
change set back and forth from the backend driver to the
[ActiveSync](ActiveSync) code that fetches and sends each detected
change.
-**Horde\_ActiveSync\_Imap\_\***: These classes interact with the IMAP
server. H\_AS\_I\_Adapter contains the bulk of the logic for fetching
changes, messages, etc.... The Message object wraps a single IMAP
message and the MessageBodyData object abstracts access to the
message's body taking various things into account \(if the client
needs HTML body, if it needs truncation\) and ensuring the data is
proper UTF-8 data.
-**Horde\_ActiveSync\_Message\_\***: These classes represent either an
actual message item i.e., a Calendar item or a part of a message item
that is contained by a message item i.e., and Attendee or a Flag. Each
class is responsible for knowing how to encode itself into WBXML or to
decode a WBXML stream into the item's properties.
-**Horde\_ActiveSync\_State\_\***: This is the state/storage driver.
Responsible for interacting with the persistent storage used by
[ActiveSync](ActiveSync) \(normally Sql\). This is another one of
those classes that has grown too big for it's original name and needs
to be broken down for H6. Started as a driver for maintaining the
state/synckey "sync-points" but has grown to encompass managing all
storage needs. E.g., the SyncCache is actually persisted to storage
using this class.
-### Horde\_Core
+Key areas: `Request/`, `Message/`, `State/`, `Wbxml/`, `Collections`,
+`SyncCache`, `Connector/`, `Folder/`, `Imap/`, `Device.php`.
+### `horde/core` -- Horde groupware backend
-Core contains any code specific to handling Horde Groupware
collections. This is where requests for information and changes to
information are actually handled.
+`Horde_Core_ActiveSync_Driver` implements the driver for IMP
\(mail\), Kronolith,
+Turba, Nag, and Mnemo. Supporting classes:
-**Horde\_Core\_ActiveSync\_Auth**: Wraps authentication driver for
use in [ActiveSync](ActiveSync) \(see Authentication section below\).
-**Horde\_Core\_ActiveSync\_Connector**: Wraps calls to the various
application APIs \(except for IMP\) from
Horde\_Core\_ActiveSync\_Driver.
-**Horde\_Core\_ActiveSync\_Driver**: Concrete implementation of
Horde\_ActiveSync\_Driver\_Base.
-**Horde\_Core\_ActiveSync\_Mail**: Handles sending/replying/forwarding email.
-#### Factories
-**\[\[# activesyncserver
Horde\_Core\_Factory\_ActiveSyncServer\]\]**: Creates the main
*Horde\_ActiveSync* object. Injects the
*Horde\_Core\_ActiveSync\_Driver*, the WBXML encoder/decoder objects,
the *Horde\_ActiveSync\_State\_\[Sql\|Mongo\]* storage object and the
*Horde\_Controller\_Request* object.
-**Horde\_Core\_Factory\_ActiveSyncState**: Creates the state storage
handler. The name is misleading \(and will change in Horde 6\) as this
class has evolved to be more of a general storage handler and now
handles more than just device state. We will assume a Sql storage
backend for this document.
-**Horde\_Core\_Factory\_ActiveSyncBackend**: Creates the
*Horde\_Core\_ActiveSync\_Driver* backend driver. Injects
*Horde\_Core\_ActiveSync\_Connector*,
*Horde\_Core\_ActiveSync\_Imap\_Factory* \(if needed\), the state
storage driver, and the *Horde\_Core\_ActiveSync\_Auth* object.
-**Horde\_Core\_ActiveSync\_Imap\_Factory**: Responsible for
constructing the Horde\_ActiveSync\_Imap\_Adapter, for returning the
folder tree, and information on available user-defined flags. This
factory is injected into the backend driver.
-**Horde\_Core\_ActiveSync\_Logger\_Factory**: Obviously, provides the
logger object used for the synclog.
-## Protocol Overview
+| Class | Role |
+| --- | --- |
+| `Horde_Core_ActiveSync_Auth` | Auth driver wrapper \(Basic +
optional certificate transparent driver\) |
+| `Horde_Core_ActiveSync_Connector` | Registry calls into Horde apps |
+| `Horde_Core_ActiveSync_Mail` | Send / reply / forward |
+| `Horde_Core_ActiveSync_Imap_Factory` | IMAP adapter and folder tree
for mail |
+### Entry point
-It's beyond the scope to explain the [ActiveSync](ActiveSync)
protocol in detail. For that, there is the official
[documentation](https://msdn.microsoft.com/en-us/library/cc425499(v=exchg.80).aspx). The most useful of these
are
-**MS-ASCMD**: Details every command/request and it's schema.
-**MS-ASHTTP**: Documents the requirements and flow of the HTTP
protocol as used in the EAS protocol.
+`web/horde/rpc.php` -> `Horde_Rpc_ActiveSync` -> injector factories:
-There are also the individual documents for each collection type,
such as *MS-ASEMAIL*, *MS-ASTASKS* etc... These documents are the best
place to start when trying to track down issues such as "Protocol
Error" issues with certain clients.
+- `Horde_Core_Factory_ActiveSyncServer` -- builds `Horde_ActiveSync`,
applies global `conf['activesync']['version']`
+- `Horde_Core_Factory_ActiveSyncBackend` -- `Horde_Core_ActiveSync_Driver`
+- `Horde_Core_Factory_ActiveSyncState` -- SQL or Mongo state storage
-The basic bit to know about the EAS protocol is that it is encoded
using WBXML. That is, binary encoded XML data. Again, the
structure/schema of WBXML data is beyond the scope of this page.
Instead of decoding the entire request first and then handling it,
Horde\_ActiveSync decodes the data and handles it on the fly. That is,
we decode each individual message/object as it comes in and handle it
in-line, so to speak. This is to avoid keeping more in memory than is
necessary. The same is true for encoding - we don't wait to enocde the
entire response - we encode and send the response to the output stream
as soon as we can. The actual encoding/decoding is done in the
*Horde\_ActiveSync\_Wbxml\_\** classes. The codepages/schema is
defined in *Horde\_ActiveSync\_Wbxml::.
+EAS requests are **sessionless** (`$session_control = 'None'`); each HTTP hit
+bootstraps Horde without a web session cookie.
-++ Logic Flow
+`OPTIONS` and **Autodiscover** \(v1 XML and v2 JSON\) are handled in the RPC
+layer before the normal command loop.
-I have a long-standing item on my todo list to generate an activity
diagram for the program flow of an EAS request, but in the meantime
here is a description of what happens.
+## Authentication note
-EAS requests hit rpc.php first. There are a number of ways that these
are differentiated from other RPC requests, but the main give-away is
the REQUEST\_URI containing *Microsoft-Server-ActiveSync*. A few
things happen here before continuing. First, EAS requests are
session-less, meaning that the entire Horde environment needs to be
setup for each request. This is handled by explicitly setting
$session\_control = 'None' to force the use of the Null session driver
in Horde. From there, we perform the same logic and checks like every
other Horde RPC request. This includes having to initialize the Horde
environment with NO authentication.
+Clients send HTTP Basic credentials \(or certificates with a transparent auth
+driver\). `Horde_ActiveSync_Credentials` normalises usernames --
Autodiscover and
+some mobile clients use the **email address**;
`getUsernameFromEmail()` in the
+core driver maps to the Horde account. This is controlled in part by
+`conf['activesync']['autodiscovery']` \(name is historical; used
beyond Autodiscover\).
-From here, we instantiate a *Horde\_Rpc\_ActiveSync* object and
inject a *Horde\_ActiveSync* object \(which is created using
*Horde\_Core\_Factory\_ActiveSyncServer*\).
+See README *Protocol version configuration* for per-user permissions and the
+`activesync_device_version` hook.
-Here, we perform some sanity checking on the request and sniff out
what type of request we are handling. OPTIONS and Autodiscover
requests are handled a little differently, but for now we will
concentrate on the "normal" request handling.
+## Debugging
-The client must send certain data, apart from the WBXML structure,
with each request. This data is either present as "normal" GET
variables or is sent as BASE64 encoded binary data sent in
QUERY\_STRING \(see ***Horde\_ActiveSync::getGetVars\(\)***\). The
format of this binary data is beyond the scope of this page, but the
data is decoded in ***Horde\_ActiveSync\_Utils::decodeBase64\(\)***.
+1. Enable ActiveSync logging in Horde administration or `conf.php`
+ \(`logging.type = perdevice` writes one file per device id under the
+ configured log path\).
+2. Reproduce with a single collection where possible.
+3. Read README *Request flow* and *Client pairing lifecycle* to interpret
+ `SyncKey`, `FolderSync`, and status codes.
+4. Check [Broken Client Behavior](ActiveSync/BrokenClientBehavior) before
+ assuming a server bug.
-: Cmd : This is the command or request type. E.g., SYNC, PING, FOLDESYNC
-: DeviceId : This is a unique identifier for the client. This value
is only unique to the client, not to the account. I.e., the same
physical device/application will have the same DeviceId. Multiple
users can be associated with the same DeviceId.
+Deployment log paths and the author's test device id are documented in the
+deployment `AGENTS.md` / project rules -- not repeated here.
-Flow is turned over to ***Horde\_ActiveSync::handleRequest\(\)***.
This is where the interesting stuff starts to happen.
+## Running tests
-+++ Authentication
-First, we perform some checks, normalization, and call the
versionCallback hook if it's present. If all is well, we finally
attempt authentication. Authentication to Horde is a massive topic and
it's only complicated more by layering [ActiveSync](ActiveSync) on top
of it. I will try to give a brief overview here. For a more detailed
description of the general Horde authentication layer, see Jan's
excellent series of posts on his \[http:*janschneider.de blog\].
+From a Composer deployment root:
-Thanks to broken clients, different supported authentication
mechanisms and other idiosyncrasies, we need to perform some magic to
make sure we have the user's credentials. For this, there is
*Horde\_ActiveSync\_Credentials*. This class, when constructed and
injected with the *Horde\_ActiveSync* object, will have two properties
set: *username* and *password*. For the purposes of this page, we will
assume a typical setup where the client properly sends credentials
using HTTP BASIC and we are NOT using X509 certificates.
+```bash
+vendor/bin/phpunit -c vendor/horde/activesync/phpunit.xml.dist
+```
+
+Calendar EAS tests:
`vendor/horde/kronolith/test/Kronolith/Unit/EventActiveSyncTest.php`.
+See README for bootstrap details and integration fixtures under
+`vendor/horde/activesync/test/`.
-Once we have found the credentials, we call
***Horde\_ActiveSync::authenticate\(\)***. This performs a few checks
then ultimately passes control to
***Horde\_Core\_ActiveSync\_Driver::authenticate\(\)***. Since some
broken clients always send the email address as the username, plus the
fact the Autodiscover requests ALWAYS use the email address, we need
to normalize the username to the correct form. This is handled in
***Horde\_Core\_ActiveSync\_Driver::getUsernameFromEmail\(\)*** and is
partially affected by the
$GLOBALS\['conf'\]\['activesync'\]\['autodiscovery'\] setting \(yes,
this name is misleading since it is now used for more than just
autodiscovery\).
-
-Once in ***Horde\_Core\_ActiveSync\_Driver::authenticate\(\)*** we
use the *Horde\_Core\_ActiveSync\_Auth* object that was injected when
the driver was created to perform the actual authentication. The
reason for this authentication wrapper is to allow for combinations of
a "normal" Horde auth driver along with a transparent driver like X509
to support clients that allow for certificates along WITH credentials.
For this page, we will assume a "normal" authentication - and as such,
the actual authentication task is delegated to the 'base\_driver' in
*Horde\_Core\_ActiveSync\_Auth*. That is, the authentication driver
that is returned by
-```
-
-$injector->getInstance('Horde_Core_Factory_Auth')->create()
-```
-### Device
-
-Note that for BC reasons we refer to the client as "Device". We treat
these two terms are interchangeable unless explicitly mentioned that
we are talking about the physical device that the client is running on.
-
-Once we have gotten authentication out the way, we perform some
checks on the device/client. This includes things like making sure the
device entry exists in our storage backend \(or gets created if it
doesn't\), checking to see if the maximum protocol version we can
handle has changed - and if so, notifying the client. We also call
some hooks and callbacks to allow checking various permissions etc...
All of this is handled in *Horde\_ActiveSync::\_handleDevice\(\)*. At
the end of that call \(if successful\), we know we have a device
object available and the client is allowed to connect. Any failure
here will result in an Exception being thrown and subsequently an
appropriate HTTP error code to be sent back to the client.
-
-### Provisioning
-
-Now we take care of setting the provisioning flag and read the WBXML
header in from the input stream. Provisioning support allows the
server to take control of certain security settings on the client and
also enables the ability to remote-wipe the client from the server.
The provisioning flag is from Horde's permissions system and indicates
the level of security required:
-
-**Enable**: This means the only clients that support full
provisioning are allowed to connect to the server. Clients that don't
support this or that have broken support \(early Android clients\)
will NOT work.
-**Allow**: This means that we enforce provisioning on clients that
support it, but also allow non-compliant clients to connect.
-**Disable**: No provisioning is done.
-### Multipart
-
-Next we check to see if the client accepts Multipart responses. This
is typically used when fetching large amounts of data like e.g, an
email attachment. This is indicated by the presence of a specific
header or GET variable. We will assume this is not the case for this
page.
-
-### Command
-
-
-Now we can take care of the actual command the client is attempting
to perform. These are handled by Horde\_ActiveSync\_Request\_\*
objects. Again, the name of these objects are a bit outdated and are
planned to be renamed in H6. The object is instantiated and the
handle\(\) request is called. If all goes well, a value of true is
returned and control is returned back to the RPC layer to finish up.
-
-The Horde\_ActiveSync\_Request\_\* objects are responsible for
enforcing the schema of whatever request is being handled, passing off
any incoming additions/deletions/changes to the backend and delegating
the responsibility for storing any data that needs to be persisted.
This includes the collection state and the sync cache. This are
explained in the section on the client life cycle.
-
-I should be noted here that [ActiveSync](ActiveSync) clients are able
to issue requests while other requests are still running. I.e., if the
user changes an entry, a new SYNC request can be issued, even though
there is currently a PING request running. After the SYNC request is
handled, the client can then issue another PING reqeust etc... The
sync cache is used to track this and detect these concurrent requests
and kill off the most appropriate request.
-
-There are almost 2 dozen different types of requests that handle
things from synchronizing the folder structure to validating S/MIME
certificates. The more common request objects are:
-
-**Horde\_ActiveSync\_Request\_Sync**: This is the main code
responsible for accepting and sending object changes, handling
"hanging syncs" \(which are SYNC requests that also act as PING
requests\) as well as handling certain options that the client sets
\(such as truncation, sync window etc...\).
-**Horde\_ActiveSync\_Request\_Ping**: This handles PING requests,
which basically just continuously check for a change in the backend.
It doesn't care what the change is, or how many there are. Much less
resource intensive than a SYNC for this reason.
-
-This is where the bulk of the action is performed and this
description is of course a gross over-simplification. There are many
other objects that are involved in handling the request - the
responsibility of each of these objects are outlined in the General
Library Structure section.
-
-
-## Life Cycle of a Client.
-
-
-What we will describe is the life cycle of a client-server pairing
from the initial connection to be able to synchronize changes. Let's
start with a fresh, never before connected client. The details and
order of things vary from EAS version to version. This is designed as
a general overview and most closely resembles what happens in EAS
version \>= 14.
-
-### OPTIONS
-
-The first thing that happens is the OPTIONS request. This essentially
tells the server what protocol versions the client supports and the
server responds with \(among other things\) the version that it will
be using. This is usually where the device object is first created.
TODO: Flesh this out with more details.
-
-### SETTINGS
-
-Second will normally be a SETTINGS request. This is where the client
can give the server details about the device and/or OS, application
name etc... Normally this is responded to with server details about
the user account. The following is an example synclog of the SETTINGS
request and response using a Windows 10 Mail client:
-
-```
-
-2016-11-14T17:03:59+00:00 INFO: [10030]
Horde_Core_ActiveSync_Driver::authenticate() attempt for mike
-2016-11-14T17:03:59+00:00 INFO: [10030] SETTINGS request received for
user mike
-2016-11-14T17:03:59+00:00 INFO: [10030] Device entry exists for
340B5431BBC4241BB73F622B4C6D18E9, updating userAgent, version, and
supported.
-2016-11-14T17:03:59+00:00 INFO: [10030] GET VARIABLES: Array
-(
- [ProtVer] => 14.1
- [Cmd] => Settings
- [Locale] => 1033
- [DeviceId] => 340b5431bbc4241bb73f622b4c6d18e9
- [PolicyKey] => 0
- [DeviceType] => WindowsMail
-)
-
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:Settings>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:DeviceInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:Set>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:Model>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I VMware Virtual Platform
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:Model>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:IMEI />
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:FriendlyName>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I DESKTOP-E96J0OF
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:FriendlyName>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:OS>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I Windows 10.0.14393
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:OS>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:OSLanguage>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I English
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:OSLanguage>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:PhoneNumber />
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:UserAgent>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I MSFT-WIN-3/10.0.14393
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:UserAgent>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:EnableOutboundSMS>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:MobileOperator>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I OperatorName
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:MobileOperator>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:Set>
-2016-11-14T17:03:59+00:00 INFO: [10030] Device entry exists for
340B5431BBC4241BB73F622B4C6D18E9, updating userAgent, version, and
supported.
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:DeviceInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:UserInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I <Settings:Get />
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:UserInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] I </Settings:Settings>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Settings>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O 1
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:DeviceInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O 1
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:DeviceInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:UserInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O 1
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Status>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Get>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Accounts>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:Account>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:AccountName>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O Default Identity
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:AccountName>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:EmailAddresses>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O
<Settings:PrimarySmtpAddress>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O mike at theupstairsroom.com
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O
</Settings:PrimarySmtpAddress>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O <Settings:SmtpAddress>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O mike at theupstairsroom.com
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:SmtpAddress>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:EmailAddresses>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Account>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Accounts>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Get>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:UserInformation>
-2016-11-14T17:03:59+00:00 DEBUG: [10030] O </Settings:Settings>
-```
-### FOLDERSYNC
-
-For the purposes of this page, we are going to ignore the
PROVISIONING stuff. The next step is the FOLDERSYNC request:
-```
-
-2016-11-14T17:03:59+00:00 DEBUG: [10029] I <FolderHierarchy:FolderSync>
-2016-11-14T17:03:59+00:00 DEBUG: [10029] I <FolderHierarchy:SyncKey>
-2016-11-14T17:03:59+00:00 DEBUG: [10029] I 0
-2016-11-14T17:03:59+00:00 DEBUG: [10029] I </FolderHierarchy:SyncKey>
-2016-11-14T17:03:59+00:00 DEBUG: [10029] I </FolderHierarchy:FolderSync>
-```
-
-First, the client requests a SyncKey of 0. This indicates it is a
fresh start and that the server should discard any previous sets of
state it may have for this client and user. The server fetches the
list of folders that are available. For this purpose each groupware
share \(address book, calendar etc...\) is considered a folder. The
entry point for this is
***Horde\_Core\_ActiveSync\_Driver::getFolderList\(\)***.
-
-At this point we should talk about the backend's id and the
[ActiveSync](ActiveSync) UID for each folder. From the client's point
of view, all it cares about is the uid for the folder. We create new
UIDs whenever a new folder is encountered. The mapping of UIDs to
backend ids is managed by the Horde\_ActiveSync\_State driver. So, you
will see something like this in the sync log:
-
-```
-
-2016-11-14T17:03:59+00:00 INFO: [10029]
Horde_Core_ActiveSync_Driver::getFolderList()
-2016-11-14T17:03:59+00:00 INFO: [10029] Device entry exists for
340B5431BBC4241BB73F622B4C6D18E9, updating userAgent, version, and
supported.
-2016-11-14T12:04:00-05:00 INFO: [10029] Creating new folder uuid for
Calendar:1w_UP1B0Fbgun61fg3dAqLJ: A2603e3ee
-2016-11-14T12:04:00-05:00 INFO: [10029] Creating new folder uuid for
@Contacts@: Cd1b7031f
-2016-11-14T12:04:00-05:00 INFO: [10029] Creating new folder uuid for
@Tasks@: Tb666e6ce
-2016-11-14T12:04:00-05:00 INFO: [10029] Creating new folder uuid for
@Notes@: N6d6ecbe3
-2016-11-14T12:04:00-05:00 INFO: [10029] Polling
Horde_Core_ActiveSync_Driver::_getMailFolders()
-2016-11-14T12:04:00-05:00 INFO: [10029] Creating new folder uuid for
INBOX: Fe4d57a40
-```
-
-Now we send the folder tree to the client, along with a synckey
\(only showing one folder for brevity's sake\):
-
-```
-
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:FolderSync>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:Status>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O 1
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:Status>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:SyncKey>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O
{5829ee7f-b60c-405a-822f-272dc0a80160}1
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:SyncKey>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:Changes>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:Count>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O 87
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:Count>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:Add>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O
<FolderHierarchy:ServerEntryId>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O Tb666e6ce
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O
</FolderHierarchy:ServerEntryId>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:ParentId>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O 0
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:ParentId>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:DisplayName>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O Tasks
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:DisplayName>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O <FolderHierarchy:Type>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O 7
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:Type>
-2016-11-14T12:04:00-05:00 DEBUG: [10029] O </FolderHierarchy:Add>
-2016-11-14T12:04:01-05:00 DEBUG: [10029] O </FolderHierarchy:Changes>
-2016-11-14T12:04:01-05:00 DEBUG: [10029] O </FolderHierarchy:FolderSync>
-```
-
-This is a good time to mention the SyncKey. The SyncKey is the unique
identifier a specific set of state. It represents the state of the
collection being synced at that point in time. It always has the
following form:
-
-\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\}y where y is a continuously
increasing counter. That increments any time changes are received from
or sent to the client. The GUID-looking value in front of the counter
will remain constant after it's generated for each collection.
-
-### SYNC
-
-Next, the client can issue SYNC requests for each folder it wants to
populate. Unlike the FOLDERSYNC request, the SYNC request doesn't send
the actual changes back with the 1st synckey. E.g., the first SYNC
request for a mail folder contains some options such as the filtertype
\(how far back to sync\), bodytype preferences etc... The server sets
those options in the SyncCache for that collection, creates a new
synckey, stores it in the collection's state and sends back the
initial response.
-
-```
-
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <Synchronize>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <Folders>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <Folder>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <SyncKey>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 0
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </SyncKey>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <FolderId>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I Fe4d57a40
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </FolderId>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <Options>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <FilterType>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 5
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </FilterType>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 128
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 2
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 128
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <MIMESupport>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </MIMESupport>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 4
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Type>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:TruncationSize>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 107520
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I
</AirSyncBase:TruncationSize>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 128
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:Preview>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </AirSyncBase:BodyPreference>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I <Conflict>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </Conflict>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I
<RightsManagement:RightsManagementSupport>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I
</RightsManagement:RightsManagementSupport>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </Options>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </Folder>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </Folders>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] I </Synchronize>
-
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <Synchronize>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <Status>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </Status>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <Folders>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <Folder>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <SyncKey>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O
{5829ee83-11e8-472d-bc66-272ec0a80160}1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </SyncKey>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <FolderId>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O Fe4d57a40
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </FolderId>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O <Status>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O 1
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </Status>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </Folder>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </Folders>
-2016-11-14T17:04:03+00:00 DEBUG: [10030] O </Synchronize>
-```
+## Contributing
-Now, the next SYNC request for this folder will be sent with the
SyncKey of \{5829ee83-11e8-472d-bc66-272ec0a80160\}1. At this point we
know it's the "first sync" i.e., all objects are "new" from the
client's point of view, so we do some optimizations and take
advantage of this. Otherwise, all the future SYNC requests will behave
the same way - the client sends the last SyncKey it knows about, the
server uses that to get the state of whatever collection we are
syncing, calculates the changes, increments the SyncKey counter, sends
it all and saves the new state.
+- Library changes:
[horde/activesync](https://github.com/horde/activesync) on branch
`FRAMEWORK_6_0`
+- Horde driver / admin UI:
[horde/core](https://github.com/horde/core),
[horde/horde](https://github.com/horde/horde)
+- App-specific mapping \(calendar, contacts, tasks\): respective app repos
-The general flow for getting server changes would be something like this:
+Update `README.md` when adding commands, changing negotiation, or
altering state
+shape. Add a short wiki note only when operators need new config or behaviour
+is client-visible.
-In ActiveSync\_Request\_Sync::\_handle\(\) ---\>
- call into Horde\_ActiveSync\_Collections::getCollectionChanges\(\) ---\>
- which calls into Horde\_ActiveSync\_State\_Base::getChanges\(\) ---\>
- which finally calls
Horde\_Core\_ActiveSync\_Driver::getServerChanges\(\) to get the
changes.
+Bug reports: [bugs.horde.org](http://bugs.horde.org/queue/sync) -- include
+`MS-ASProtocolVersion`, `DeviceType`, `User-Agent`, and redacted
per-device log
+excerpts.
-Once we have the change set in H\_AS\_Request\_Sync::\_handle\(\), we
set it into the exporter object the loop and send each change \(see
Horde\_ActiveSync\_Connector\_Exporter\_Sync::sendNextChange\(\)\).
More information about the commits
mailing list