/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H
#define ANDROID_COMMON_TIME_SERVER_PACKETS_H

#include <stdint.h>
#include <common_time/ICommonClock.h>

namespace android {

/***** time sync protocol packets *****/

enum TimeServicePacketType {
    TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
    TIME_PACKET_WHO_IS_MASTER_RESPONSE,
    TIME_PACKET_SYNC_REQUEST,
    TIME_PACKET_SYNC_RESPONSE,
    TIME_PACKET_MASTER_ANNOUNCEMENT,
};

class TimeServicePacketHeader {
  public:
    friend class UniversalTimeServicePacket;
    // magic number identifying the protocol
    uint32_t magic;

    // protocol version of the packet
    uint16_t version;

    // type of the packet
    TimeServicePacketType packetType;

    // the timeline ID
    uint64_t timelineID;

    // synchronization group this packet belongs to (used to operate multiple
    // synchronization domains which all use the same master election endpoint)
    uint64_t syncGroupID;

    ssize_t serializePacket(uint8_t* data, uint32_t length);

  protected:
    void initHeader(TimeServicePacketType type,
                    const uint64_t tlID,
                    const uint64_t groupID) {
        magic              = kMagic;
        version            = kCurVersion;
        packetType         = type;
        timelineID         = tlID;
        syncGroupID        = groupID;
    }

    bool checkPacket(uint64_t expectedSyncGroupID) const {
        return ((magic       == kMagic) &&
                (version     == kCurVersion) &&
                (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID)));
    }

    ssize_t serializeHeader(uint8_t* data, uint32_t length);
    ssize_t deserializeHeader(const uint8_t* data, uint32_t length);

  private:
    static const uint32_t kMagic;
    static const uint16_t kCurVersion;
};

// packet querying for a suitable master
class WhoIsMasterRequestPacket : public TimeServicePacketHeader {
  public:
    uint64_t senderDeviceID;
    uint8_t senderDevicePriority;

    void initHeader(const uint64_t groupID) {
        TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST,
                                            ICommonClock::kInvalidTimelineID,
                                            groupID);
    }

    ssize_t serializePacket(uint8_t* data, uint32_t length);
    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
};

// response to a WhoIsMaster request
class WhoIsMasterResponsePacket : public TimeServicePacketHeader {
  public:
    uint64_t deviceID;
    uint8_t devicePriority;

    void initHeader(const uint64_t tlID, const uint64_t groupID) {
        TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE,
                                            tlID, groupID);
    }

    ssize_t serializePacket(uint8_t* data, uint32_t length);
    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
};

// packet sent by a client requesting correspondence between local
// and common time
class SyncRequestPacket : public TimeServicePacketHeader {
  public:
    // local time when this request was transmitted
    int64_t clientTxLocalTime;

    void initHeader(const uint64_t tlID, const uint64_t groupID) {
        TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST,
                                            tlID, groupID);
    }

    ssize_t serializePacket(uint8_t* data, uint32_t length);
    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
};

// response to a sync request sent by the master
class SyncResponsePacket : public TimeServicePacketHeader {
  public:
    // local time when this request was transmitted by the client
    int64_t clientTxLocalTime;

    // common time when the master received the request
    int64_t masterRxCommonTime;

    // common time when the master transmitted the response
    int64_t masterTxCommonTime;

    // flag that is set if the recipient of the sync request is not acting
    // as a master for the requested timeline
    uint32_t nak;

    void initHeader(const uint64_t tlID, const uint64_t groupID) {
        TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE,
                                            tlID, groupID);
    }

    ssize_t serializePacket(uint8_t* data, uint32_t length);
    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
};

// announcement of the master's presence
class MasterAnnouncementPacket : public TimeServicePacketHeader {
  public:
    // the master's device ID
    uint64_t deviceID;
    uint8_t devicePriority;

    void initHeader(const uint64_t tlID, const uint64_t groupID) {
        TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT,
                                            tlID, groupID);
    }

    ssize_t serializePacket(uint8_t* data, uint32_t length);
    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
};

class UniversalTimeServicePacket {
  public:
    uint16_t packetType;
    union {
        WhoIsMasterRequestPacket  who_is_master_request;
        WhoIsMasterResponsePacket who_is_master_response;
        SyncRequestPacket         sync_request;
        SyncResponsePacket        sync_response;
        MasterAnnouncementPacket  master_announcement;
    } p;

    ssize_t deserializePacket(const uint8_t* data,
                              uint32_t       length,
                              uint64_t       expectedSyncGroupID);
};

};  // namespace android

#endif  // ANDROID_COMMON_TIME_SERVER_PACKETS_H