//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef ProtocolParser_h__
#define ProtocolParser_h__

#include "HashStore.h"
#include "nsICryptoHMAC.h"
#include "safebrowsing.pb.h"

namespace mozilla {
namespace safebrowsing {

/**
 * Abstract base class for parsing update data in multiple formats.
 */
class ProtocolParser {
public:
  struct ForwardedUpdate {
    nsCString table;
    nsCString url;
  };

  ProtocolParser();
  virtual ~ProtocolParser();

  nsresult Status() const { return mUpdateStatus; }

  nsresult Init(nsICryptoHash* aHasher);

#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
  nsCString GetRawTableUpdates() const { return mPending; }
#endif

  virtual void SetCurrentTable(const nsACString& aTable) = 0;

  void SetRequestedTables(const nsTArray<nsCString>& aRequestTables)
  {
    mRequestedTables = aRequestTables;
  }

  nsresult Begin();
  virtual nsresult AppendStream(const nsACString& aData) = 0;

  uint32_t UpdateWaitSec() { return mUpdateWaitSec; }

  // Notify that the inbound data is ready for parsing if progressive
  // parsing is not supported, for example in V4.
  virtual void End() = 0;

  // Forget the table updates that were created by this pass.  It
  // becomes the caller's responsibility to free them.  This is shitty.
  TableUpdate *GetTableUpdate(const nsACString& aTable);
  void ForgetTableUpdates() { mTableUpdates.Clear(); }
  nsTArray<TableUpdate*> &GetTableUpdates() { return mTableUpdates; }

  // These are only meaningful to V2. Since they were originally public,
  // moving them to ProtocolParserV2 requires a dymamic cast in the call
  // sites. As a result, we will leave them until we remove support
  // for V2 entirely..
  virtual const nsTArray<ForwardedUpdate> &Forwards() const { return mForwards; }
  virtual bool ResetRequested() { return false; }

protected:
  virtual TableUpdate* CreateTableUpdate(const nsACString& aTableName) const = 0;

  nsCString mPending;
  nsresult mUpdateStatus;

  // Keep track of updates to apply before passing them to the DBServiceWorkers.
  nsTArray<TableUpdate*> mTableUpdates;

  nsTArray<ForwardedUpdate> mForwards;
  nsCOMPtr<nsICryptoHash> mCryptoHash;

  // The table names that were requested from the client.
  nsTArray<nsCString> mRequestedTables;

  // How long we should wait until the next update.
  uint32_t mUpdateWaitSec;

private:
  void CleanupUpdates();
};

/**
 * Helpers to parse the "shavar", "digest256" and "simple" list formats.
 */
class ProtocolParserV2 final : public ProtocolParser {
public:
  ProtocolParserV2();
  virtual ~ProtocolParserV2();

  virtual void SetCurrentTable(const nsACString& aTable) override;
  virtual nsresult AppendStream(const nsACString& aData) override;
  virtual void End() override;

  // Update information.
  virtual const nsTArray<ForwardedUpdate> &Forwards() const override { return mForwards; }
  virtual bool ResetRequested() override { return mResetRequested; }

private:
  virtual TableUpdate* CreateTableUpdate(const nsACString& aTableName) const override;

  nsresult ProcessControl(bool* aDone);
  nsresult ProcessExpirations(const nsCString& aLine);
  nsresult ProcessChunkControl(const nsCString& aLine);
  nsresult ProcessForward(const nsCString& aLine);
  nsresult AddForward(const nsACString& aUrl);
  nsresult ProcessChunk(bool* done);
  // Remove this, it's only used for testing
  nsresult ProcessPlaintextChunk(const nsACString& aChunk);
  nsresult ProcessShaChunk(const nsACString& aChunk);
  nsresult ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
                          const nsACString& aChunk, uint32_t* aStart);
  nsresult ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
                          const nsACString& aChunk, uint32_t* aStart);
  nsresult ProcessHostAddComplete(uint8_t aNumEntries, const nsACString& aChunk,
                                  uint32_t *aStart);
  nsresult ProcessHostSubComplete(uint8_t numEntries, const nsACString& aChunk,
                                  uint32_t* start);
  // Digest chunks are very similar to shavar chunks, except digest chunks
  // always contain the full hash, so there is no need for chunk data to
  // contain prefix sizes.
  nsresult ProcessDigestChunk(const nsACString& aChunk);
  nsresult ProcessDigestAdd(const nsACString& aChunk);
  nsresult ProcessDigestSub(const nsACString& aChunk);
  bool NextLine(nsACString& aLine);

  enum ParserState {
    PROTOCOL_STATE_CONTROL,
    PROTOCOL_STATE_CHUNK
  };
  ParserState mState;

  enum ChunkType {
    // Types for shavar tables.
    CHUNK_ADD,
    CHUNK_SUB,
    // Types for digest256 tables. digest256 tables differ in format from
    // shavar tables since they only contain complete hashes.
    CHUNK_ADD_DIGEST,
    CHUNK_SUB_DIGEST
  };

  struct ChunkState {
    ChunkType type;
    uint32_t num;
    uint32_t hashSize;
    uint32_t length;
    void Clear() { num = 0; hashSize = 0; length = 0; }
  };
  ChunkState mChunkState;

  bool mResetRequested;

  // Updates to apply to the current table being parsed.
  TableUpdateV2 *mTableUpdate;
};

// Helpers to parse the "proto" list format.
class ProtocolParserProtobuf final : public ProtocolParser {
public:
  typedef FetchThreatListUpdatesResponse_ListUpdateResponse ListUpdateResponse;
  typedef google::protobuf::RepeatedPtrField<ThreatEntrySet> ThreatEntrySetList;

public:
  ProtocolParserProtobuf();

  virtual void SetCurrentTable(const nsACString& aTable) override;
  virtual nsresult AppendStream(const nsACString& aData) override;
  virtual void End() override;

private:
  virtual ~ProtocolParserProtobuf();

  virtual TableUpdate* CreateTableUpdate(const nsACString& aTableName) const override;

  // For parsing update info.
  nsresult ProcessOneResponse(const ListUpdateResponse& aResponse);

  nsresult ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate,
                                    const ThreatEntrySetList& aUpdate,
                                    bool aIsAddition);

  nsresult ProcessRawAddition(TableUpdateV4& aTableUpdate,
                              const ThreatEntrySet& aAddition);

  nsresult ProcessRawRemoval(TableUpdateV4& aTableUpdate,
                             const ThreatEntrySet& aRemoval);

  nsresult ProcessEncodedAddition(TableUpdateV4& aTableUpdate,
                                  const ThreatEntrySet& aAddition);

  nsresult ProcessEncodedRemoval(TableUpdateV4& aTableUpdate,
                                 const ThreatEntrySet& aRemoval);
};

} // namespace safebrowsing
} // namespace mozilla

#endif
