Categoria: UCC API

UCC API – Unified Communications Client API – Parte 6 – Conferências

Overview

No post anterior, vimos como criar uma sessão do tipo instant messaging. As sessões do tipo conferência usam praticamente a mesma mecânica.

A diferença é que a conference session é o lugar onde será criada uma conferência no OCS server.

Cada participante deve se adicionar na sessão.

Tudo parece muito fácil na teoria, mas alguams particuularidades na API tornam o trabalho um pouco complicado.

Assim como nas sessões de instant messaging, precisamos tratar os dois caminhos: outgoing session e incoming session.

Fluxo de criação da conferência

Este desenho mostra a idéia geral de como é o processo de criação de conferência na UCC API:

  • O criador da conferência cria um “lugar” no servidor onde a conferência vai acontecer.
  • O criador da conferência manda o endereço deste lugar para os usuários convidados.
  • Cada usuário convidado cria uma sessão de comunicação entre ele e o lugar onde a conferência acontece.

Obtendo a Focus Uri

O primeiro desafio é obter o focus Uri. O Focus URI é um “lugar” no servidor onde os usuários criam as sessões de conferência. Nós precisamos obter a Focus Uri para poder obter uma Conference Uri posteriormente.

Para obter a Focus Uri, precisamos fazer uma “query” na categoria “Server Configuration”.

    public void SubscribeServerConfiguration(IUccEndpoint endpoint)
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)endpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("ServerConfiguration");
      serverConfigurationSubscription.Query(null);
    }

A idéia por trás deste código está descrita em: Category e Category Instance.

As informações são devolvidasp elo servidor no método _IUccCategoryContextEvents.OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "ServerConfiguration")
      {
        IUccServerConfigurationCategory category = (IUccServerConfigurationCategory)pCategoryInstanceEvent.CategoryInstance;
        this.FocusUri = category.FocusFactory;
        MessageBox.Show(FocusUri.Value);
      }
    }

O valor que precisamos é devolvido em FocusUri.Value.

Criando uma conferência (ou Scheduling Conference)

O processo de criação de uma conferência no servidor é chamado de “Schedule”. As vezes nos confunde porque pensamos em criar uma conferência no futuro, mas a idéia aqui é criar um lugar no servidor onde as pessoas vão compartilhar a conversação (idéia similar a salas de chat).

Para fazer o schedule da conferência:


    public void ScheduleConference(IUccEndpoint endpoint, string strFocusUri)
    {
      UccUriManager uriManager = new UccUriManager();
      UccUri focusUri = uriManager.ParseUri(strFocusUri);

      IUccConferenceManager confManager = (IUccConferenceManager)endpoint;
      IUccConferenceManagerSession confManagerSession = confManager.CreateConferenceManagerSession(focusUri);

      ComHelper.Advise<_IUccConferenceManagerSessionEvents>(confManagerSession, this);

      UccConferenceInformation ci = confManagerSession.CreateConferenceInformation();
      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_ADMISSION_TYPE,   UCC_CONFERENCE_ADMISSION_TYPE.UCCCAT_OPEN_AUTHENTICATED);

      UccPropertyCollection mcuAttributeCollection = new UccPropertyCollection();
      mcuAttributeCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_SETTING_PROPERTY.UCCCESP_TYPE, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING);

      UccPropertyCollection mcuPropertyCollection = new UccPropertyCollection();
      mcuPropertyCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, mcuAttributeCollection);

      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_MCUS, mcuPropertyCollection);

      UccOperationContext operationContext = new UccOperationContext();
      UccContext mcuContext = new UccContext();
      mcuContext.AddNamedProperty("ScheduleConference", true);
      operationContext.Initialize(1, mcuContext);

      confManagerSession.ScheduleConference(ci, operationContext);
    }

Este código tem muita coisa específica da UCC API, mas vou tentar explicar a idéia geral. Primeiro fazemos um cast do endpoint para IUccConferenceManager.

Então precisamos criar uma sessão do conference manager. Precisamos assinar sua interface dispatch para ouvir o evento OnScheduleConference. O Conference Uri é obtido através deste evento.

Após este passo, criamos um objeto Conference Information. Aqui apenas colcoamos algumas informações de quem pode participar dessa conferência, que tipos de mídia são suportados.

Então criamos a propriedade ScheduleConference (acho que isso é fixo no servidor OCS) e chamamos o método ScheduleConference.

A resposta é obtida no método OnScheduleConference:


    public void OnScheduleConference(UccConferenceManagerSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Error scheduling conference");

      UccProperty confUriProperty = pEventData.Properties.get_Property((int)UCC_CONFERENCE_MANAGER_OPERATION_COMPLETED_EVENT_PROPERTY.UCCCMOCEP_CONFERENCE_URI);
      conferenceUri = confUriProperty.StringValue;
    }

Aqui, pegamos a Conference Uri como uma propriedade.

Se algo der errado, recebemos StatusCode diferente de 0.

Precisamos guardar esse conference uri, pois precisaremos dele no próximo passo.

Criando uma Conference Session

Uma conference session é a idéia de um usuário interagindo com uma “scheduled conference” (aquilo que fizemos no passo anterior). Não é só o criador da conferência quem cria conference sessions. Veremos adiante que quando convidamos um usuário para uma conferência (uma incoming session), vamos usar o mesmo processo para interagir com a conferência.

Para criar a conference session, precisamos da conference Uri.

O processo segue:


    public void CreateConferenceSession(IUccEndpoint endpoint, string conferenceUri)
    {
      this.endpoint = endpoint;
      this.conferenceUri = conferenceUri;

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;

      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_CONFERENCE, null);
      confSession = (IUccConferenceSession)session;

      ComHelper.Advise<_IUccConferenceSessionEvents>(confSession, this);

      confSession.Enter(UccHelper.GetUri(conferenceUri), null);

    }

Esse código não tem segredo. Logo depois que o método “Enter” é chamado, o evento _IUccConferenceSessionEvents.OnEnter é chamado assincronamente.

    public void OnEnter(UccConferenceSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");
            

      IUccConferenceSessionParticipant localParticipant = (IUccConferenceSessionParticipant)((IUccSession)pEventSource).LocalParticipant;
      UccConferenceSessionParticipantEndpoint partEndpoint = localParticipant.CreateParticipantEndpoint(this.endpoint.uccEndpoint.Uri, this.endpoint.uccEndpoint.Id, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, null);
      ComHelper.Advise<_IUccConferenceSessionParticipantEvents>(localParticipant, this);

      IUccMediaChannel channel = partEndpoint.CreateChannel(UCC_MEDIA_TYPES.UCCMT_MESSAGE, null);
      ((IUccMediaChannel)channel).PreferredMedia = (int)(UCC_MEDIA_DIRECTIONS.UCCMD_RECEIVE | UCC_MEDIA_DIRECTIONS.UCCMD_SEND);
      partEndpoint.AddChannel(channel);

      ((IUccSession)pEventSource).LocalParticipant.AddParticipantEndpoint((IUccSessionParticipantEndpoint)partEndpoint, null);
    }

No método OnEnter, checamos por erros e na sequencia criamos o endpoint local do participante. No endpoint é onde especificamos o tipo de mídia que irá interagir com a conferência. No nosso exemplo, usaremos instant message.

Vamos fazer um advise nos eventos de _IUccConferenceSessionParticipantEvents. Após a chamada de OnEnter, _IUccConferenceSessionParticipantEvents.OnParticipantAdded será chamado assincronamente.

    public void OnAddEndpoint(UccConferenceSessionParticipant pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");

      IUccSessionManager sessionManager = (IUccSessionManager)this.endpoint.uccEndpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING, null);

      string imMCUUri = confSession.Properties.get_Property((int)UCC_CONFERENCE_SESSION_PROPERTY.UCCCSPID_CONFERENCE_INSTANT_MESSAGING_URI).StringValue;

      IUccInstantMessagingSessionParticipant imParticipant = (IUccInstantMessagingSessionParticipant)session.CreateParticipant(
        UccHelper.GetUri(imMCUUri), null);

      ComHelper.Advise<_IUccInstantMessagingSessionParticipantEvents>(imParticipant, this);

      session.AddParticipant((IUccSessionParticipant)imParticipant);

      this.imSession = (IUccInstantMessagingSession)session;

      ComHelper.Advise<_IUccSessionEvents>(session, this);
    }

No método OnAddEndponit, vamos checar por erros e então criaremos uma sessão de instant messaging, da mesma forma que criamos na parte 5. A diferença é que o participante é a conference Uri.

Os mesmos métodos são usados para mandar e receber mensagens na conferência.

Outros métodos interessantes na interface _IUccInstantMessagingSessionParticipantEvents são: OnRemoveParticipant, OnAddParticipant e OnTerminate. Esses eventos são usados para controlar quando os usuários estão entrando ou saindo da conferência.

Convidando usuários para uma conferência

Os passos anteriores nos levaram a um estado muito interessante. Temos uma conferência criada e uma sessão de instant messaging apontando pra ela, mas como convidamos usuários para a conferência que acabamos de criar?

Vamos entrar nesse ambiente nebuloso.

Para permitir que um usuário entre na conferência, devemos enviar o conference Uri para ele. O único mecanismo disponível para fazer isso na UCC API é a “application session”. Application session é um tipo de sessão que permite que qualquer tipo de informação seja transferida entre os usuários. O Microsoft Office Communicator usa o mesmo mecanismo descrito aqui.

Primeiro vamos explicar como enviar o convite. Posteriormente veremos como receber o convite e tratar a application session.

Enviando o convite:


    public void InviteUser(IUccEndpoint endpoint, string userUri, string conferenceUri)
    {
      this.conferenceUri = conferenceUri;

      UccContext inviteContext = new UccContext();
      inviteContext.AddNamedProperty("ConfInvitee", userUri);

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_APPLICATION, inviteContext);

      UccUriManager uriManager = new UccUriManager();
      UccUri userUriUri = uriManager.ParseUri(userUri);

      IUccSessionParticipant participant = session.CreateParticipant(userUriUri, inviteContext);
      ComHelper.Advise<_IUccApplicationSessionParticipantEvents>(participant, this);
      session.AddParticipant(participant, null);
    }

Precisamos salvar o conferenceUri pois será usado posteriormente numa chamada assíncrona.

A idéia aqui é muito simples. Criamos uma sessão do tipo application (uma sessão para mandar informações arbitrárias), então criamos o participante com a Uri do usuário remoto. Então fazemos um advise na interface _IUccApplicationSessionParticipantEvents e adicionamos o participante na sessão.

O método _IUccApplicationSessionParticipantEvents.OnOutgoingInvitation será chamado:


    public void OnOutgoingInvitation(UccApplicationSessionParticipant pEventSource, UccOutgoingInvitationEvent pEventData)
    {
      pEventData.ContentType = "application/ms-conf-invite+xml";
      pEventData.SessionDescription =
        "<Conferencing version=\"2.0\"><focus-uri>" + this.conferenceUri + "</focus-uri><subject>AE Chat</subject><im available=\"true\"><first-im></first-im></im></Conferencing>";

      pEventData.Send();
    }

Por que todo esse XML inventado? Resp: Porque é o formato usado pelo Microsoft Office Communicator. POdemos mudar esse formato para qualquer outro, desde que seja uma convenção entre a aplicação cliente. O OCS Server e a UCC API somente propagam o que você preencher aqui.

Posteriormente, o método OnInvitationAccepted será chamado quando o usuário aceitar o convite.

Esse fluxo é olhando pela perspectiva do usuário que cria a conferência e convida os outros usuários. Quando o usuário entra na conferência, o método OnParticipantAdded será chamado.

Recebendo convites

Agora veremos a mesma lógica sob a ótica de quem está sendo convidado para a conferência.

O convite é enviado através de uma application session. A primeira pergunta é: Como a UCC API sabe que uma sessão que está sendo recebida (incoming session) é uma application session?

A resposta é: Ela não sabe. Você precisa dizer pra ela.

A mágica é:


    public void EnableEndpoint(IUccPlatform uccPlatform, string UserUri, string UserName, string UserPassword, string UserDomain, string ServerName, UCC_TRANSPORT_MODE transportMode)
    {
      UccUri uri = UccHelper.GetUri(UserUri);
      uccEndpoint = uccPlatform.CreateEndpoint(UCC_ENDPOINT_TYPE.UCCET_PRINCIPAL_SERVER_BASED, uri, null, null);

      ComHelper.Advise<_IUccEndpointEvents>(uccEndpoint, this);     

      IUccSessionManager sm = (IUccSessionManager)uccEndpoint;
      sm.RegisterSessionDescriptionEvaluator(this);
      ComHelper.Advise<_IUccSessionManagerEvents>(sm, this);
      

      IUccServerSignalingSettings signalingSettings = (IUccServerSignalingSettings)uccEndpoint;
      
      UccCredential credential = signalingSettings.CredentialCache.CreateCredential(UserName, UserPassword, UserDomain);
      signalingSettings.CredentialCache.SetCredential("*", credential);
      signalingSettings.AllowedAuthenticationModes = (int)UCC_AUTHENTICATION_MODES.UCCAM_NTLM;
      signalingSettings.Server = signalingSettings.CreateSignalingServer(ServerName, transportMode);

      uccEndpoint.Enable(null);
    }

Esse código está no momento em que o endpoint é habilitado. O método RegisterSessionDescriptorEvaluator method força o objeto passado como parâmetro a implementar _IUccSessionDescriptionEvaluator.

Essa interface pede a implementação do seguinte método:

    public bool Evaluate(string bstrContentType, string bstrSessionDescription)
    {
      if (bstrContentType == "application/ms-conf-invite+xml")
        return true;
      else
        return false;
    }

Isso é complicadinho. Para cada incoming session, o método “Evaluate” será chamado. Se ele retornar “true”, estamos dizendo para a API que esta sessão é uma application session. Se ele retornar false, a UCC API vai tentar descobrir o tipo de sessão. Claro que a UCC API conhece os tipos default.

Quando o usuário recebe o convite, ele detecta que este content-type mapeia para uma application session então método “OnIncomingSession” é chamado:

    public void OnIncomingSession(IUccEndpoint pEventSource, UccIncomingSessionEvent pEventData)
    {
      if (pEventData.Session.Type == UCC_SESSION_TYPE.UCCST_APPLICATION)
      {
        pEventData.Accept();
        UccIncomingInvitationEvent inInvitation = (UccIncomingInvitationEvent)pEventData;
        
        TextReader tr = new StringReader(inInvitation.RemoteSessionDescription);
        XmlDocument inviteDoc = new XmlDocument();
        inviteDoc.Load(tr);

        // Get the conference Focus URI from the XML stream.
        string conferenceUri = inviteDoc.SelectSingleNode("Conferencing/focus-uri").InnerText;

        CreateConferenceSession(this, conferenceUri);
      }
    }

O código acima interpreta o XML e obtém o conferenceURI enviado através da application session. O CreateConferenceSession faz o mesmo explicado anteriormente no tópico “Criando uma Conference Session”.

Depois que o usuário cria a conference session, habilita o endpoint local ele consegue enviar e receber mensagens através da conference session.

Informações importantes sobre a Conferência

Quando estava desenvolvendo uma aplicação de chat sobre a UCC API, eu tive vários issues estranhos com a conferênica.

Algumas mensagens não estavam sendo roteadas para todos os usuários na conferência. Após uma semana lutando com este problema, reparei que o problema estava no content-type.

Quando o content-type da mensagem é diferente de text/plain, a mensagem não é roteada para todos os usuários. Eu não encontrei nenhuma informação sobre isso em nenhuma documentação oficial (ou forums).

Após fazer alguns sniffs bore os pacotes do Microsoft Office Communicator, reparei que o MOC manda todas as mensagens em text/plain e dentro do content-type manda a mensagem formata, codificada em Base64.

Código fonte

Aqui está todo o código fonte de todas as partes do tutorial: UCCApiSample. Está bastante bagunçado e confuso, mas mesmo assim decidi publicá-lo.

Conclusão

Existem muitas particularidades sobre a conferência na UCC API. Eu espero que este overview possa poupá-lo de grande parte do trabalho.

Infelizmente a falta de recursos sobre a UCC API na web não ajuda muito. Isso foi uma das minhas principais motivações para escrever tudo isso.

Esse é o último. Divirta-se.

Anúncios

UCC API – Unified Communications Client API – Parte 5 – Instant Messaging Session

Visão Geral

Nesta parte veremos como estabelecer uma sessão de instant messaging entre dois usuários na UCC API.

Uma informação importante sobre esse tipo de sessão é que ela tem apenas UM participante. É por isso que conferências são um pouco mais complicadas do que sessão “single-user”.

Eu vou falar nessa parte apenas de sessões para um único usuário.

Outra particularidade é que existem tratamentos diferentes para sessões originárias do usuário conectado (outgoing sessions) e sessões iniciadas por outros usuários (incoming session).

Outgoing Sessions

Iniciar uma sessão é simples:


    public void CreateIMSession(IUccEndpoint endpoint, string user)
    {
      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;

      UccUriManager uriManager = new UccUriManager();      
      UccUri destUser = uriManager.ParseUri(user);

      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING, null);
      IUccSessionParticipant participant = session.CreateParticipant(destUser, null);
      ComHelper.Advise<_IUccInstantMessagingSessionParticipantEvents>(participant, this);     
      session.AddParticipant(participant);

      imSession = (IUccInstantMessagingSession)session;
      ComHelper.Advise<_IUccInstantMessagingSessionEvents>(imSession, this);     
    }

Esta função recebe um endpoint habilitado e o usuário que deseja convidar para a sessão. O formato esperado é “sip:user@domain.com”. Praticamente todo lugar que espera por uma Uri utiliza este formato na UCC API.

Na primeira linha, fazemos um cast do endpoin para IUccSessionManager. O session manager é o mecanismo para iniciar qualquer tipo de sessão na UCC API. O tipo de sessão é especificado no primeiro parâmetro do método CreateSession.

O próximo passo é instanciar um participante. É aqui que colocamos a Uri do usuário remoto. Então fazemos um advise na interface _IUccInstantMessagingSessionParticipantEvents no participante criado.

O último passo é fazer um advise da interface _IUccInstantMessagingSessionEvents no objeto imSession. O objeto imSession object é um objeto do tipo IUccInstantMessagingSession. Nós precisamos guardar uma referência para ele para podermos enviar mensagens.

A interface _IUccInstantMessagingSessionParticipantEvents tem métodos importantes como OnComposing e OnIdle, usados para podermos criar features do tipo “o usuário está digitando” numa aplicação de instant messaging. O OnInstantMessageReceived é o evento mais importante. Esse método será chamado toda vez que o participante remoto manda uma mensagem.

    public void OnInstantMessageReceived(UccInstantMessagingSessionParticipant pEventSource, UccIncomingInstantMessageEvent pEventData)
    {
      MessageBox.Show(pEventData.Content);
    }

É importante tratarmos o content-type da mensagem.

Para mandar uma mensagem, usamos o método SendMessage da IUccInstantMessagingSession.

    public void SendMessage(string msg)
    {
      imSession.SendMessage("text/plain", msg);
    }

Incoming Sessions

Toda vez que uma incoming session é iniciada, o método IUccEndpoint.OnIncomingSession é chamado (se o endpoint corretamente ouve os eventos de _IUccEndpointEvents).

Neste evento, é necessário tratarmos a sessão da seguinte forma:


    public void OnIncomingSession(IUccEndpoint pEventSource, UccIncomingSessionEvent pEventData)
    {
      if (pEventData.Session.Type == UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING)
      {
        pEventData.Accept();
        imSession = (IUccInstantMessagingSession)pEventData.Session;

        IUccSessionParticipant participant = (IUccSessionParticipant) pEventData.Inviter;
        ComHelper.Advise(participant, this);     
      }
    }

A idéia é muito parecida com uma outgoing session. A diferença é que recebemos o objeto IUccInstantMessageSession e o IUccInstantMessagingSessionParticipant do parâmetro pEventData.

Devemos manter a referência do objeto imSession para enviarmos mensagens através do método SendMessage.

Ouvindo os eventos de IUccInstantMessagingSessionParticipantEvents, teremos acesso ao evento OnInstantMessageReceived.

Desta forma, podemos estabelecer o canal de comunicação para incoming sessions.

UCC API – Unified Communications Client API – Parte 4 – Containers

Overview

No post anterior, eu expliquei a idéia por trás da publicação de category instances. Nesse parte vamos ver como podemos controlar para quais usuários a informação é publicada.

Essa feature funciona para qualquer tipo de category instance. Está relacionada com publicar informações diferentes para diferentes grupos de usuários.

O Container

O container é uma especialização de uma category instance. Isso significa que adicionar ou remover containers é feito através do processo de publicação de category instances.

O container tem a idéia de armazenar informações de publicação para um conjunto de usuários. O conjunto de usuários é definido por: usuários do mesmo domínio, mesma empresa, “federated enterprise”, publico ou todos, ou ainda construindo o grupo usuário a usuário.

Geralmente a organização dos containers vem do mais permissivo com Id’s mais baixos e os mais restritivos com Id’s mais altos.

Uma boa organização de containers seria:

  • ContainerID 0: Everyone
  • ContainerID 100: Federated enterprise
  • ContainerID 200: Company
  • ContainerID 32000: Blocked

O Microsoft Office Communicator tem uma organização similar. A lista completa está aqui: Microsoft Office Communicator Presence Categories

Se você está escrevendo o seu próprio cliente baseado na UCC API, você precisa criar a organização inicial dos containers, publicando as category instances como “static” (para que o servidor as persista). O Microsoft Office Communicateor faz a configuração inicial no seu primeiro login.

Como os direitos no container são verificados

Para publicar as informações de categorias usando listas de controle de acesso (ACL ou access control lists), o servidor OCS segue a seguinte lógica:

  • O servidor começa a publicação dos containers mais restritivos para os mais permissivos. Exemplo: Se um usuário está no container “Blocked” (32000), este usuário irá receber as informações publicadas no container 32000.
  • Se existe informação publicada no container, essa informação é enviada.
  • Se não existe informação publicada no container mais restritivo, a informação do container mais permissivo é publicada.

Vou mostrar alguns exemplos para ilustrar essa idéia. Todos os exemplos seguem a configuração de container mostrada anteriormente.

Example 1:

  • Usuário Paul não tem o usuário Mary como membro de nenhum container.
  • Usuário Paul tem o usuário John como membro do seu container 32000 (blocked users)
  • Usuário Paul publica o estado “Available” nos containers 2 e 3.
  • Usuário Paul publica o estado “Offline” no conatiner 32000.
  • Usuário Mary recebe o estado “Available” do usuário Paul. Isso acontece porque Mary cai na regra “Everyone” na configuração de containers.
  • Usuário John recebe o estado “Offline” do Paul.

Example 2:

  • Usuário Paul não tem Mary como membro de nenhum container.
  • Usuário Paul tem John como membro do container 32000 (blocked users)
  • Usuário Paul publica o estado “Available” nos containers 2 and 3.
  • Usuário Mary recebe o estado “Available” do Paul. Isto acontece porque Mary cai na regra “Everyone” da configuração de containers.
  • Usuário John recebe o estado “Available” do Paul. Isto acontece porque nenhum estado foi publicado no container 32000. Quando isso acontece a informação mais permissiva é publicada.

Esta regra aplica-se para todo e qualquer tipo de category instance. Informações de contato, por exemplo, segue a mesma regra. Você pode publicar mais informações para usuários mais confiáveis e menos informações para usuários que você não confia.

Especificamente para a feature de bloqueio de usuários, esse é o jeito certo de fazer. Nâo existe uma feature especifica para bloquear usuários, exceto esta de “remover permissão de ver o estado “disponível” para o usuário.

Conclusão

Para evitar problemas as listas de controle de acesso (ACL’s) da UCC API devem ser corretamente configuradas, especificamente os containers e membros dos containers. Sempre que ocorre uma publicação, é importante publicar a informação mais restritiva e mais permissiva em cada um dos seus containers (Ex.: Estado disponível e offline).

Infelizmente a documentação do MSDN não é muito clara sobre este assunto, especificamente a idéia de ter que publicar a informação mais restritiva. Em outras palavras, o estado “Offline” no exemplo.

UCC API – Unified Communications Client API – Parte 3 – Publicando Category Instances

Objetivo

Nessa parte vamos discutir o concieto de publicação de category instances na UCC API. A publicação é a idéia de mandar uma informação para o servidor e propagar essa informação para outros usuários, ou seja, mandar seu cartão de contato, criar grupos, adicionar contatos a sua lista de contatos são operações de publicação.

Qunado publicamos uma informação você verá uma propriedade “container id”. Essa propriedade signfica quem pode ver essa publicação. Essa parte é um pouco complicado e pode gerar alguns comportamentos estranhos que veremos no próximo post.

A idéia deste é somente explicar como fazer uma publicação. No próximo veremos como tratar o container id e quem pode ver essa informação.

Publicando

O padrão para fazer uma publicação é relativamente simples:

  • Obter um category instance publicável (publishable category instance)
  • Publicar
  • Ver se a publicação foi bem-sucedida.

Existem duas formas de se obter uma category instance publicável:

  • Através do método CreatePublishableCategoryInstance no IUccPublicationManager
  • Criar uma através de uma category instance recebida por uma publicação (parte 2). O IUccCategoryInstance também possui um método CreatePublishableCategoryInstance.
  • Para mostrar como esse padrão funciona, vamos ver um exemplo de como publicar o estado de um usuário (o estado também é um category instance!).

        public void PublishPresence(MyEndpoint Endpoint, int instanceId, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE expireType, int availability,
          UCC_PRESENCE_STATE_TYPE presenceStateType, bool manual)
        {
          IUccPublicationManager pubManager = (IUccPublicationManager)uccEndpoint;
    
          IUccCategoryInstance pubInstance = (IUccCategoryInstance)pubManager.CreatePublishableCategoryInstance("state", 2, instanceId, expireType, 0);
          
          IUccPresenceStateInstance state = (IUccPresenceStateInstance)pubInstance;
          state.Availability = (int)availability;
          state.Type = presenceStateType;
          state.IsManual = manual;
    
          pubInstance.PublicationOperation = UCC_PUBLICATION_OPERATION_TYPE.UCCPOT_ADD;
          
          IUccPublication pub = pubManager.CreatePublication();
          ComHelper.Advise<_IUccPublicationEvent>(pub, this);
          pub.AddPublishableCategoryInstance(pubInstance);
          pub.Publish();
        }
    

    Primeiro, obtemos a instancia do IUccPublicationManager através do endpoint do usuário conectado. O próximo passo é obter uma category instance publicável (publishable category instance) usando o método CreatePublishableCategoryInstance.

    Este método recebe o “state” que é uma string “reservada” (ou hard-coded, como prefiram). O próximo parâmetro é o ContainerId. Veremos o porque do valor “2” no próximo post deste blog. O outro parâmetro é o InstanceId. O InstanceId depende do tipo de categoria que está sendo publicada. Verifique na documentação da categoria. O expire type determina quando a publicação expira. Está relacionado com o tempo de vida da publicação. O último parâmetro indica o tempo caso o expire type seja tempo (time).

    O mais importante aqui é que publicações do tipo STATIC são armazenadas no servidor. Mesmo se o usuário desconectar, a publicação é mantida no servidor.

    O próximo passo é fazer um cast da categoria para seu tipo especializado. Neste caso, user presence state. Então definios as propriedades específicas ali.

    A operação de publicação geralmente é UCCPOT_ADD. Eu não sei exatamente o porque da existência do UCCPOT_REMOV, já que se você deseja remover uma categoria deve usar o método RemovePublishableCategoryInstance e não um tipo diferente de publicação.

    No próximo passo criamos uma instância de IUccPublication. Isso é feito através do método CreatePublication no publication manager.

    Então, fazemos um advise nos eventos da publicação, adicionamos a category instance publicável e chamamos o método Publish().

    Então, o OnPublish da interface _IUccPublicationEvents será chamado:

        public void OnPublish(IUccPublication pPublication, IUccOperationProgressEvent pPublicationEventInfo)
        {
          if (!pPublicationEventInfo.IsComplete)
            return;
    
          if (pPublicationEventInfo.StatusCode != 0)
            throw new Exception("Error");
        }
    

    Neste método apenas checamos o StatusCode. Se é 0, a publicação foi bem sucedida.

    Publicando Estado de Presença (Presence State)

    Apenas para completar o exemplo, vamos mostrar como publicar estado de presença. O código 3500 está relacionado com o status “Available”. Lá vai o código:

        private void PublishOnlinePresence(object sender, EventArgs e)
        {
          MyPublicationManager myPublicationManager = new MyPublicationManager();
          myPublicationManager.PublishPresence(myEndpoint, 0x20000000, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE.UCCCIET_USER, 3500, UCC_PRESENCE_STATE_TYPE.UCCPST_USER_STATE, true);
    
          MyPublicationManager myPublicationManager2 = new MyPublicationManager();
          myPublicationManager2.PublishPresence(myEndpoint, 0x30000000, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE.UCCCIET_DEVICE, 3500, UCC_PRESENCE_STATE_TYPE.UCCPST_MACHINE_STATE, false);
        }
    

    Neste exemplo, usamos o instance Id 0x20000000 para estado do usuário e 0x30000000 para estado da máquina. Esses valores são específicos para publicação de estado. Verifique a documentação da msdn para outros códigos.

    Conclusão

    Toda vez que houver necessidade de publicar um category instance, apenas siga este padrão. Você precisará saber alguns detalhes específicos sobre a category instance que deseja publicar, como se você está publicando o InstanceId correto e propriedades específicas.

    O ContainerId será explicado no próximo post.

    Espero que ajude.

UCC API – Unified Communications Client API – Parte 2 – Category e Category Instances

Objetivo

Nessa parte veremos o conceito de “Category” e “Category instance” da UCC API. A idéia é ilustrar o padrão existente para fazer as assinaturas e recuperar as informações do servidor.

O que é um “Category” na UCC API

Category é uma abstração de dados. Toda informação consultada do servidor OCS faz parte de uma categoria e é representada por um category instance (instância de categoria).

A forma de assinar essas informações são as mesmas independente de qual categoria é assinada. Para saber informações detalhadas de um category instance, é necessário fazer um cast da instância para a interface COM especializada desta category instance.

Um category instance pode ser:

  • Presença: estado, cartão de contato, notas, etc.:
  • Contatos e grupos
  • Containers: relacionados ao controle de acesso da publicação das category instances
  • Configurações

Você pode encontrar mais informações no MSDN: Category and category instances.

Como obter informações de categorias

A informação de uma categoria pode ser obtida através de uma assinatura. Uma assinatura é criada e é feito um Advise dos eventos nesta categoria. As informações são chamadas através de eventos numa interface de dispatch.

O primeiro passo é fazer um cast do IUccEndpoint para um IUccSubscriptionManager e criar a assinatura:

    public void SubscribeServerConfiguration()
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("ServerConfiguration");
      serverConfigurationSubscription.Query(null);
    }

Esse código cria uma assinatura para uma categoria “ServerConfiguration”. Sim, as categorias são strings hard-coded (ou reservadas, para ser mais elegante).

O myEndpoint.uccEndpoint é um endpoint já inicializado (ver parte 1). O método CreateSubscription instancia um novo objeto “Subscription” (assinatura).

Os objetos “Presentity” são os que vão receber os eventos de assinatura. Eu não sei porque não é o próprio objeto subscription que recebe os eventos, mas deixo essa pergunta para os projetistas da API. Por enquanto, vamos apenas explicar o código acima. Nos fazemos um advise no _IUccPresentityEvents porque ele vai receber a notificação da assinatura no método OnCategoryContextAdded. Isso significa que o objeto presentity ouve esta categoria. Toda vez que uma categoria é adicionada ou removida ocorrerão chamadas nos eventos OnCategoryContextAdded e OnCategoryContextRemoved.

Então chamamos o método Query. Dependendo da categoria assinada (a string hard-coded) você precisa chamar o método Query ou Subscribe. A documentação da MSDN explica qual método chamar para cada uma das categorias.

Seguimos então para a chamada do OnCategoryInstanceAdded:

    public void OnCategoryContextAdded(UccPresentity pPresentity, UccCategoryContextEvent pCategoryCtxtEvent)
    {
      ComHelper.Advise<_IUccCategoryContextEvents>(pCategoryCtxtEvent.CategoryContext, this);
    }

Nós apenas fazemos um advise da interface _IUccCategoryContextEvents. Nesta interface temos os métodos OnCategoryInstanceAdded e OnCategoryInstanceRemoved. Isso significa que toda vez que um valor mudar dentro de uma categoria (ex.: um contato, uma configuração, o estado de um usuário), ocorrerá uma chamada em um destes métodos.

Por último, vamos tratar o evento OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "ServerConfiguration")
      {
        IUccServerConfigurationCategory category = (IUccServerConfigurationCategory)pCategoryInstanceEvent.CategoryInstance;
        this.FocusUri = category.FocusFactory;
        MessageBox.Show(FocusUri.Value);
      }
}

Neste código, vamos apenas testar se a categoria é do tipo “ServerConfiguration”. Isso é útil quando você tem mais de uma categoria no mesmo subscription. Você pode usar o mesmo método para tratar uma ou mais assinaturas.

Então fazemos um cast do pCategoryInstanceEvent.CategoryInstance (o category instance recém adicionado no category context) para um IUccServerConfigurationCategory. Nós podemos fazer isso porque nós sabemos (através da documentação da MSDN)) que podemos fazer isso. Cada um dos category contexts possui uma interface equivalente.

Então podemos acessar os valores especializados da category instance, como o FocusUri (útil para conferências).

Informações de Contatos

O MSDN fornece um exmeplo completo que nos mostra como obter informações dos contatos, mas isso não é muito simples de compreender. Como é um pouco difícil entender o exemplo, resolvi escrever essa parte pra tentar manter a coisa simples.

Para assinar a categoria de contatos, fazemos:

    public void SubscribeContacts()
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("contacts");
      serverConfigurationSubscription.Subscribe(null);
    }

É a mesma coisa do exemplo assima, apenas chamando a categoria “contacts”.

Então, na chamada do OnCategoryContextAdded:

    public void OnCategoryContextAdded(UccPresentity pPresentity, UccCategoryContextEvent pCategoryCtxtEvent)
    {
      ComHelper.Advise<_IUccCategoryContextEvents>(pCategoryCtxtEvent.CategoryContext, this);
    }

E na chamada do OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "contacts")
      {
        IUccContact contact = (IUccContact)pCategoryInstanceEvent.CategoryInstance;
        MessageBox.Show("Contact: " + contact.Uri.Value + " / " + contact.Name);

        IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;
  
        IUccSubscription theContactSubscription = subscriptionManager.CreateSubscription(null);

        UccPresentity presentity = theContactSubscription.CreatePresentity(contact.Uri, null);
        ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
        theContactSubscription.AddPresentity(presentity);
        theContactSubscription.AddCategoryName("contactCard");
        theContactSubscription.AddCategoryName("state");
        theContactSubscription.Subscribe(null);      
      }
  }

O que muda neste exemplo é que criamos um presentity baseado na Uri do contato para então fazermos novas assinaturas para cada um dos contatos e pegar suas informações de contato e presença.

Conclusão

Espero que com esses exemplos extremamente simplistas fique fácil entender a mecânica da assinatura das categorias. Eu escrevi essa parte apenas para ficar mais simples o entendimento de outras mais complexas, como controle de acesso a containers e criação de conferências.

O objetivo desse post é ser somente uma introdução. A documentação da MSDN é muito mais completa como uma referência.

UCC API – Unified Communications Client API – Parte 1 – Visão Geral

Objetivo

Algum tempo atrás, tive a oportunidade de tarabalhar com a Microsoft Unified Communications API, também conhecida como UCC API. Para as pessoas que não estão familiarizadas com esse nome, trata-se da API do Microsoft Office Communicator (OCS).

O OCS disponibiliza esta API para pessoas que querem fazer seu próprio cliente de chat (ou outros tipos de comunicação) utilizando toda a infra-estrutura do servidor.

Enquanto estava mexendo com a API, percebi que a documentação da MSDN não era muito completa e nem muito simples de entender. Essa foi a motivação para postar toda minha experiência aqui.

O forum americano da UCC API não tem muita gente postando e não é muito ativo. A soma de todos estes elementos tornaram o trabalho um pouquinho complicado.

Infelizmente, não tenho muito a colaborar sobre tipos de mídia diferente de IM (voz por exmplo), porém, os conceitos da API são os mesmos quando se trata de outro tipo de mídia.

Visão Geral da UCC API

A API é a mesma para C++ e .Net (C#). Ela é implementada através de um componente COM e um primary interop assembly para .NET. O primeiro problema aqui é que geralmente a API é utilizada com registro “side-by-side” do componente COM.

Aqui está o link da documentação da MSDN que explica isso: Sample Application Manifest for Side-by-Side Execution.

Esse registro através de um arquivo de manifesto é um pouco complicado, mas evita a necessidade de registrar o componente COM (regsvr32) e minimiza problemas de deploy futuros.

O Visual Studio 2008 tem um bug engraçado com manifestos. Mesmo se vc adicionar o .manifest dentro do projeto, sempre que você modifica o csproj (adicionando um arquivo, por exemplo) e pressiona F5, ocorre um erro no momento de carregar o manifesto. Fechando e reabrindo o Visual Studio resolve o problema. Não sei se o mesmo ocorre no VS 2010.

Outra característica da UCC API é que ela é toda assícrona. Na prática, isso significa que se você executa um Endpoint.Enable() por exemplo, um método OnEnable da interface COM é chamada em resposta a essa solicitação. Quando você começa a aninhar esse tipo de chamada, o código fica bem complicado para entender. Se você não conhece a API, fica sem saber onde é o próximo passo no fluxo de execução da aplicação.

Todo objeto COM tem uma interface de “dispatch” amiga. Por exemplo: IUccEndpoint e _IUccEndpointEvents.

O que eu sugiro é que você nunca faça um advise de diferentes interfaces dispatch com cardinalidades diferentes. Por exemplo: Num endpoint você tem “n” sessions. Se você implementar _IUccEndpointEvents e _IUccSessionEvents no mesmo objeto, você está pedindo para ter problemas.

Para objetos de mesma cardinalidade, por exemplo _IUccSessionEvents e _IUccInstantMessagingSessionEvents não tem problema. Isso acontecerá várias vezes porque o COM estimula esse tipo de design. O objeto é o mesmo, porém o polimorfismo no COM é bastante peculiar.

Principais pontos da UCC API

Eu divido a UCC API nos seguintes pontos-chave:

  • Criando conexão com o servidor: É como conectar e desconectar num servidor.
  • Tratando eventos de category instances: Esse tópico trata-se de como receber informações do servidor. Tudo está no mesmo saco: informação de presença do usuário, configuração do servidor, containers, pesquisa.
  • Tratando controle de acesso de informações nos containers: Esse é um ponto complicado da API. Você pode publicar informações diferentes para usuários diferentes, baseado em controle de acesso. Você pode dizer para um usuário que está online e para outro que está ocupado.
  • Tratando conversações: Esse tópico fala sobre mandar e receber mensagens. Iniciar e abandonar sessões de conversação (vale para IM e outros tipos de mídia). Conferência faz parte de sessões de conversação, mas merece um capítulo a parte.

Informações de Login do OCS

As informações necessárias para login são:

  • Uri: A Uri “Sip” do usuário. É necessário informar o prefixo sip:
  • UserName: Nome do usuário. Pode ser o nome completo (user@domain.com) ou apenas user.
  • Password: A senha do usuário
  • Domain: Domínio

Quando vi isso pela primeira vez, pensei comigo: “Porque dois nomes de usuário?”. Continuo me perguntando, mas desenvolvi algumas teorias (não baseadas em nenhuma documentação, apenas num palpite): Está relacionada à estrutura do servidor OCS.

O processo de conexão segue os seguintes passos:

  • A UCC API manda um pacote SIP para o servidor, localizando o usuário no OCS Front End server através de sua URL SIP. O servidor responde: Sim, este usuário é um usuário do OCS.
  • A UCC API manda o usuário/senha/domínio para o OCS Front End Server. O servidor faz um forward dessa informação para o Active Directory que responde: Okay, amigo ou NÃO, você é do mal.

As informações de usuário, senha e domínio podem ser enviadas para o servidor de duas formas:

  • User/Domain/Password: O nome “curto” do usuário. Este é validado no Active Directory’s com o nome “antigo” do usuário (pre-windows 2000). Por isso não precisa de domínio.
  • Full Qualified Name / Password: O formato user@domain.com. Nesse caso, ele vai ser validado contra o “novo” login name no active directory e simplesmente ignora o campo domínio. Mande uma string vazia nele.

Tratando a conexão com o servidor: Endpoint

O conceito neste caso é simples. Você possui um objeto UccPlatform uqe representa uma instância da API na sua aplicação. Você terá apenas um e deverá inicializá-lo uma única vez:

    public void CreatePlatform()
    {
      uccPlatform = new UccPlatform();
      uccPlatform.Initialize("UCCConferenceSample", null);      
    }

Se você quiser derrumar a plataforma, use o método Shutdown.

O endpoint é sua conexão com o servidor. Você precisa de uma plataforma inicializada. Você pode habilitar o endpoint da seguinte forma:

    public void EnableEndpoint(MyPlatform platform, string UserUri, string UserName, string UserPassword, string UserDomain, string ServerName, UCC_TRANSPORT_MODE transportMode)
    {
      UccUri uri = UccHelper.GetUri(UserUri);
      uccEndpoint = platform.uccPlatform.CreateEndpoint(UCC_ENDPOINT_TYPE.UCCET_PRINCIPAL_SERVER_BASED, uri, null, null);

      ComHelper.Advise<_IUccEndpointEvents>(uccEndpoint, this);     


      IUccServerSignalingSettings signalingSettings = (IUccServerSignalingSettings)uccEndpoint;

      UccCredential credential = signalingSettings.CredentialCache.CreateCredential(UserName, UserPassword, UserDomain);
      signalingSettings.CredentialCache.SetCredential("*", credential);
      signalingSettings.AllowedAuthenticationModes = (int)UCC_AUTHENTICATION_MODES.UCCAM_NTLM;
      signalingSettings.Server = signalingSettings.CreateSignalingServer(ServerName, UCC_TRANSPORT_MODE.UCCTM_TCP);

      uccEndpoint.Enable(null);
    }

Vamos discutir um pouco este código. UccHelper é um pequeno wrapper para interpretar Uri’s. Você precisará dele várias vezes. Eu vou fornecer o código logo abaixo. CreateEndpoint é o método principal aqui. Toda vez que aparecer um “Create” na API, não significa que você está “usando” o objeto. Apenas instanciando ele. Esse comportamento também ocorre várias vezes.

ComHelper é outra classe auxiliar. É um wrapper rápido para associar os eventos disparados por uma dispatch interface com um objeto. Neste caso estamos dizendo que os eventos disparados por _IUccEndpointEvents em “this”. Essa é uma característica do COM.

A classe precisa implementar os métodos da interface _IUccEndpointEvents e terá os métodos OnEnable ou OnDisable disparados assincronamente após a chamada do método Enable.

  public class ComHelper
  {
    public static void Advise<T>(object source, T sink)
    {
      IConnectionPointContainer container = (IConnectionPointContainer)source;
      IConnectionPoint cp;
      int cookie = 0;
      Guid guid = typeof(T).GUID;

      container.FindConnectionPoint(ref guid, out cp);
      cp.Advise(sink, out cookie);
    }
  }

O cast que aparece aqui é muito interessante também. Quando você faz um cast (IUccServerSignallingSettings)uccEndpoint, internamente você está chamando o método QueryInterface do COM. Isso confunde muito desenvolvedores .Net porque quando você está inspecionando o código, não é possível perceber que a instância uccEndpoint “é” um IUccServerSignallingSettings. Você só tem essa informação lendo a documentação da API (os links estão no rodapé do post). Quando a documentação diz IUccAlgumaCoisa can be queried from the IUccOutraCoisa significa que no C# você pode fazer um cast direto.

As próximas linhas referentes ao “signalling settings” estão apenas passando as informações de conexão antes de chamar o método Enable.

A resposta será enviada pelo servidor no método OnEnable:

    public void OnEnable(IUccEndpoint pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Bad luck");
      MessageBox.Show("Endpoint enabled");      
    }

Os controles para evitar que sua aplicação faça algo antes de receber a chamada em OnEnable devem ser feitos por sua conta.

A interface IUccOperationProgressEvent tem uma propriedade chamada StatusCode. Se for diferente de 0, ocorreu um problema. Sempre verifique isso em cada resposta.

Para compreender melhor os erros, ligue o “Hexadecimal View” do inspetor do C# e ele fará um pouco mais de sentido (não muito). Você pode encontrar o significado para os códigos de erro nos links:

Unified Communications Client API Error Codes
Unified Communications Client API Error Messages

Conclusão

Essa primeira parte não ajudará muito, mas é um começo. Em breve estarei trazendo os outros posts com os próximos passos.

Espero que com essas informações seja possível compreender um pouco das características da UCC API que ajudem no desenho de um novo cliente baseado nesta API e como funciona o processo de conexão com um servidor OCS.

Links úteis

MSDN SDK Documentation.
MSDN Forum