Add components for zip packing.

This is the changes to add packing feature in zip unpacker.
Design doc (now writing) can be found in the following link.

https://docs.google.com/a/google.com/document/d/1eQQEkkwdEPfVANnlaJxIP12MZjaAcJ22ziIiuj0DMIw/edit?usp=sharing

BUG=359837
TEST=manually tested(test components will be added later.)

Change-Id: Ia0ad78050a58053aa09e268f25ac6f064cbb9aac
diff --git a/unpacker/Makefile.pnacl b/unpacker/Makefile.pnacl
index cacf5e8..eb2766c 100644
--- a/unpacker/Makefile.pnacl
+++ b/unpacker/Makefile.pnacl
@@ -19,6 +19,9 @@
 
 CFLAGS = -Wall
 SOURCES = \
+  cpp/compressor.cc \
+  cpp/compressor_archive_libarchive.cc \
+  cpp/compressor_io_javascript_stream.cc \
   cpp/module.cc \
   cpp/request.cc \
   cpp/volume.cc \
diff --git a/unpacker/cpp/compressor.cc b/unpacker/cpp/compressor.cc
new file mode 100644
index 0000000..1d13d78
--- /dev/null
+++ b/unpacker/cpp/compressor.cc
@@ -0,0 +1,140 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "compressor.h"
+
+#include <cstring>
+#include <ctime>
+#include <sstream>
+
+#include "request.h"
+#include "compressor_io_javascript_stream.h"
+#include "compressor_archive_libarchive.h"
+
+namespace {
+
+// An internal implementation of JavaScriptCompressorRequestorInterface.
+class JavaScriptCompressorRequestor : public JavaScriptCompressorRequestorInterface {
+ public:
+  explicit JavaScriptCompressorRequestor(Compressor* compressor) :
+      compressor_(compressor) {}
+
+  virtual void WriteChunkRequest(int64_t length,
+                                 const pp::VarArrayBuffer& buffer) {
+    compressor_->message_sender()->SendWriteChunk(
+        compressor_->compressor_id(), buffer, length);
+  }
+
+  virtual void ReadFileChunkRequest(int64_t length) {
+    compressor_->message_sender()->SendReadFileChunk(
+        compressor_->compressor_id(), length);
+  }
+
+ private:
+  Compressor* compressor_;
+};
+
+}  // namespace
+
+Compressor::Compressor(const pp::InstanceHandle& instance_handle,
+                       int compressor_id,
+                       JavaScriptMessageSenderInterface* message_sender)
+    : compressor_id_(compressor_id),
+      message_sender_(message_sender),
+      worker_(instance_handle),
+      callback_factory_(this) {
+  requestor_ = new JavaScriptCompressorRequestor(this);
+  compressor_stream_ =
+      new CompressorIOJavaScriptStream(requestor_);
+  compressor_archive_ =
+      new CompressorArchiveLibarchive(compressor_stream_);
+}
+
+Compressor::~Compressor() {
+  worker_.Join();
+  delete compressor_archive_;
+  delete compressor_stream_;
+  delete requestor_;
+}
+
+bool Compressor::Init() {
+  return worker_.Start();
+}
+
+void Compressor::CreateArchive() {
+  compressor_archive_->CreateArchive();
+  message_sender_->SendCreateArchiveDone(compressor_id_);
+}
+
+void Compressor::AddToArchive(const pp::VarDictionary& dictionary) {
+  worker_.message_loop().PostWork(callback_factory_.NewCallback(
+      &Compressor::AddToArchiveCallback, dictionary));
+}
+
+void Compressor::AddToArchiveCallback(int32_t,
+                                      const pp::VarDictionary& dictionary) {
+  PP_DCHECK(dictionary.Get(request::key::kPathname).is_string());
+  std::string pathname =
+      dictionary.Get(request::key::kPathname).AsString();
+
+  PP_DCHECK(dictionary.Get(request::key::kFileSize).is_string());
+  int64_t file_size =
+      request::GetInt64FromString(dictionary, request::key::kFileSize);
+  PP_DCHECK(file_size >= 0);
+
+  PP_DCHECK(dictionary.Get(request::key::kIsDirectory).is_bool());
+  bool is_directory =
+      dictionary.Get(request::key::kIsDirectory).AsBool();
+
+  PP_DCHECK(dictionary.Get(request::key::kModificationTime).is_string());
+  std::string strtime =
+      dictionary.Get(request::key::kModificationTime).AsString();
+  tm tm;
+  strptime(strtime.c_str(), "%m/%d/%Y %T", &tm);
+  time_t modification_time = mktime(&tm);
+
+  compressor_archive_->AddToArchive(
+      pathname, file_size, modification_time, is_directory);
+  message_sender_->SendAddToArchiveDone(compressor_id_);
+}
+
+void Compressor::ReadFileChunkDone(const pp::VarDictionary& dictionary) {
+  PP_DCHECK(dictionary.Get(request::key::kLength).is_string());
+  int64_t read_bytes =
+      request::GetInt64FromString(dictionary, request::key::kLength);
+
+  PP_DCHECK(dictionary.Get(request::key::kChunkBuffer).is_array_buffer());
+  pp::VarArrayBuffer array_buffer(dictionary.Get(request::key::kChunkBuffer));
+
+  compressor_stream_->ReadFileChunkDone(read_bytes, &array_buffer);
+}
+
+void Compressor::WriteChunkDone(const pp::VarDictionary& dictionary) {
+  PP_DCHECK(dictionary.Get(request::key::kLength).is_string());
+  int64_t written_bytes =
+      request::GetInt64FromString(dictionary, request::key::kLength);
+
+  compressor_stream_->WriteChunkDone(written_bytes);
+}
+
+void Compressor::CloseArchive(const pp::VarDictionary& dictionary) {
+  PP_DCHECK(dictionary.Get(request::key::kHasError).is_bool());
+  bool has_error =
+      dictionary.Get(request::key::kHasError).AsBool();
+
+  // If an error has occurred, no more write chunk requests are sent and
+  // CloseArchive() can be safely called in the main thread.
+  if (has_error) {
+    compressor_archive_->CloseArchive(has_error);
+    message_sender_->SendCloseArchiveDone(compressor_id_);
+  } else {
+    worker_.message_loop().PostWork(callback_factory_.NewCallback(
+        &Compressor::CloseArchiveCallback, has_error));
+  }
+}
+
+void Compressor::CloseArchiveCallback(int32_t, bool has_error) {
+  compressor_archive_->CloseArchive(has_error);
+  message_sender_->SendCloseArchiveDone(compressor_id_);
+}
diff --git a/unpacker/cpp/compressor.h b/unpacker/cpp/compressor.h
new file mode 100644
index 0000000..99236a1
--- /dev/null
+++ b/unpacker/cpp/compressor.h
@@ -0,0 +1,93 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPRESSOR_H_
+#define COMPRESSOR_H_
+
+#include <ctime>
+#include <pthread.h>
+
+#include "archive.h"
+#include "ppapi/cpp/instance_handle.h"
+#include "ppapi/cpp/var_array_buffer.h"
+#include "ppapi/cpp/var_dictionary.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi/utility/threading/simple_thread.h"
+
+#include "compressor_archive.h"
+#include "compressor_stream.h"
+#include "javascript_compressor_requestor_interface.h"
+#include "javascript_message_sender_interface.h"
+
+// Handles all packing operations like creating archive objects and writing data
+// onto the archive.
+class Compressor {
+ public:
+  Compressor(const pp::InstanceHandle& instance_handle /* Used for workers. */,
+             int compressor_id,
+             JavaScriptMessageSenderInterface* message_sender);
+
+  virtual ~Compressor();
+
+  // Initializes the compressor.
+  bool Init();
+
+  // Creates an archive object.
+  void CreateArchive();
+
+  // Adds an entry to the archive.
+  void AddToArchive(const pp::VarDictionary& dictionary);
+
+  // Processes a file chunk sent from JavaScript.
+  void ReadFileChunkDone(const pp::VarDictionary& dictionary);
+
+  // Receives a write chunk response from JavaScript.
+  void WriteChunkDone(const pp::VarDictionary& dictionary);
+
+  // Releases all resources obtained by libarchive.
+  void CloseArchive(const pp::VarDictionary& dictionary);
+
+  // A getter function for the message sender.
+  JavaScriptMessageSenderInterface* message_sender() { return message_sender_; }
+
+  // A getter function for the requestor.
+  JavaScriptCompressorRequestorInterface* requestor() { return requestor_; }
+
+  // A getter function for the compressor id.
+  int compressor_id() { return compressor_id_; }
+
+ private:
+
+  // A callback helper for AddToArchive.
+  void AddToArchiveCallback(int32_t, const pp::VarDictionary& dictionary);
+
+  // A callback helper for CloseArchive.
+  void CloseArchiveCallback(int32_t, bool has_error);
+
+  // The compressor id of this compressor.
+  int compressor_id_;
+
+  // An object that sends messages to JavaScript.
+  JavaScriptMessageSenderInterface* message_sender_;
+
+  // A worker for jobs that require blocking operations or a lot of processing
+  // time. Those shouldn't be done on the main thread. The jobs submitted to
+  // this thread are executed in order, so a new job must wait for the last job
+  // to finish.
+  pp::SimpleThread worker_;
+
+  // Callback factory used to submit jobs to worker_.
+  pp::CompletionCallbackFactory<Compressor> callback_factory_;
+
+  // A requestor for making calls to JavaScript.
+  JavaScriptCompressorRequestorInterface* requestor_;
+
+  // Libarchive wrapper instance per compressor, shared across all operations.
+  CompressorArchive* compressor_archive_;
+
+  // An instance that takes care of all IO operations.
+  CompressorStream* compressor_stream_;
+};
+
+#endif  /// COMPRESSOR_H_
diff --git a/unpacker/cpp/compressor_archive.h b/unpacker/cpp/compressor_archive.h
new file mode 100644
index 0000000..3c9ff88
--- /dev/null
+++ b/unpacker/cpp/compressor_archive.h
@@ -0,0 +1,54 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPRESSSOR_ARCHIVE_H_
+#define COMPRESSSOR_ARCHIVE_H_
+
+#include "compressor_io_javascript_stream.h"
+
+// Defines a wrapper for packing operations executed on an archive. API is not
+// meant to be thread safe and its methods shouldn't be called in parallel.
+class CompressorArchive {
+ public:
+  explicit CompressorArchive(CompressorStream* compressor_stream)
+    : compressor_stream_(compressor_stream) {}
+
+  virtual ~CompressorArchive() {}
+
+  // Creates an archive object. This method does not call CustomArchiveWrite, so
+  // this is synchronous.
+  virtual void CreateArchive() = 0;
+
+  // Releases all resources obtained by libarchive.
+  // This method also writes metadata about the archive itself onto the end of
+  // the archive file before releasing resources if hasError is false. Since
+  // writing data onto the archive is asynchronous, this function must not be
+  // called in the main thread if hasError is false.
+  virtual void CloseArchive(bool has_error) = 0;
+
+  // Adds an entry to the archive. It writes the header of the entry onto the
+  // archive first, and then if it is a file(not a directory), requests
+  // JavaScript for file chunks, compresses and writes them onto the archive
+  // until all chunks of the entry are written onto the archive. This method
+  // calls IO operations, so this function must not be called in the main thread.
+  virtual void AddToArchive(const std::string& filename,
+                            int64_t file_size,
+                            time_t modification_time,
+                            bool is_directory) = 0;
+
+  // A getter function for archive_.
+  struct archive* archive() const { return archive_; }
+
+  // A getter function for compressor_stream_.
+  CompressorStream* compressor_stream() const { return compressor_stream_; }
+
+ private:
+  // The libarchive correspondent archive object.
+  struct archive* archive_;
+
+  // An instance that takes care of all IO operations.
+  CompressorStream* compressor_stream_;
+};
+
+#endif  // COMPRESSSOR_ARCHIVE_H_
diff --git a/unpacker/cpp/compressor_archive_libarchive.cc b/unpacker/cpp/compressor_archive_libarchive.cc
new file mode 100644
index 0000000..92ac415
--- /dev/null
+++ b/unpacker/cpp/compressor_archive_libarchive.cc
@@ -0,0 +1,147 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "compressor_archive_libarchive.h"
+
+#include <cerrno>
+#include <cstring>
+
+#include "archive_entry.h"
+#include "ppapi/cpp/logging.h"
+
+namespace {
+  // Nothing to do here because JavaScript takes care of file open operations.
+  int CustomArchiveOpen(archive* archive_object, void* client_data) {
+    return ARCHIVE_OK;
+  }
+
+  // Called when any data chunk must be written on the archive. It copies data
+  // from the given buffer processed by libarchive to an array buffer and passes
+  // it to compressor_stream.
+  ssize_t CustomArchiveWrite(archive* archive_object, void* client_data,
+      const void* buffer, size_t length) {
+    CompressorArchiveLibarchive* compressor_libarchive =
+        static_cast<CompressorArchiveLibarchive*>(client_data);
+
+    const char* char_buffer = static_cast<const char*>(buffer);
+
+    // Copy the data in buffer to array_buffer.
+    PP_DCHECK(length > 0);
+    pp::VarArrayBuffer array_buffer(length);
+    char* array_buffer_data = static_cast<char*>(array_buffer.Map());
+    memcpy(array_buffer_data, char_buffer, length);
+    array_buffer.Unmap();
+
+    ssize_t written_bytes =
+        compressor_libarchive->compressor_stream()->Write(length, array_buffer);
+
+    // Negative written_bytes represents an error.
+    if (written_bytes < 0) {
+      // When writing fails, archive_set_error() should be called and -1 should
+      // be returned.
+      archive_set_error(
+          compressor_libarchive->archive(), EIO, "Failed to write a chunk.");
+      return -1;
+    }
+    return written_bytes;
+  }
+
+  // Nothing to do here because JavaScript takes care of file close operations.
+  int CustomArchiveClose(archive* archive_object, void* client_data) {
+    return ARCHIVE_OK;
+  }
+}
+
+CompressorArchiveLibarchive::CompressorArchiveLibarchive(
+    CompressorStream* compressor_stream)
+    : CompressorArchive(compressor_stream),
+      compressor_stream_(compressor_stream) {
+  destination_buffer_ =
+      new char[compressor_archive_constants::kMaximumDataChunkSize];
+}
+
+CompressorArchiveLibarchive::~CompressorArchiveLibarchive() {
+  delete destination_buffer_;
+}
+
+void CompressorArchiveLibarchive::CreateArchive() {
+  archive_ = archive_write_new();
+  archive_write_set_format_zip(archive_);
+
+  // Passing 1 as the second argument causes the final chunk not to be padded.
+  archive_write_set_bytes_in_last_block(archive_, 1);
+  archive_write_set_bytes_per_block(
+     archive_, compressor_archive_constants::kMaximumDataChunkSize);
+  archive_write_open(archive_, this, CustomArchiveOpen,
+                     CustomArchiveWrite, CustomArchiveClose);
+}
+
+void CompressorArchiveLibarchive::AddToArchive(
+    const std::string& filename,
+    int64_t file_size,
+    time_t modification_time,
+    bool is_directory) {
+  entry = archive_entry_new();
+
+  archive_entry_set_pathname(entry, filename.c_str());
+  archive_entry_set_size(entry, file_size);
+  archive_entry_set_mtime(entry, modification_time, 0 /* millisecond */);
+
+  if (is_directory) {
+    archive_entry_set_filetype(entry, AE_IFDIR);
+    archive_entry_set_perm(
+        entry, compressor_archive_constants::kDirectoryPermission);
+  } else {
+    archive_entry_set_filetype(entry, AE_IFREG);
+    archive_entry_set_perm(
+        entry, compressor_archive_constants::kFilePermission);
+  }
+  archive_write_header(archive_, entry);
+  // If archive_errno() returns 0, the header was written correctly.
+  if (archive_errno(archive_) != 0) {
+    CloseArchive(true /* hasError */);
+    return;
+  }
+
+  if (!is_directory) {
+    int64_t remaining_size = file_size;
+    while (remaining_size > 0) {
+      int64_t chunk_size = std::min(remaining_size,
+          compressor_archive_constants::kMaximumDataChunkSize);
+      PP_DCHECK(chunk_size > 0);
+
+      int64_t read_bytes =
+          compressor_stream_->Read(chunk_size, destination_buffer_);
+      // Negative read_bytes indicates an error occurred when reading chunks.
+      if (read_bytes < 0) {
+        CloseArchive(true /* hasError */);
+        break;
+      }
+
+      int64_t written_bytes =
+          archive_write_data(archive_, destination_buffer_, read_bytes);
+      // If archive_errno() returns 0, the buffer was written correctly.
+      if (archive_errno(archive_) != 0) {
+        CloseArchive(true /* hasError */);
+        break;
+      }
+      PP_DCHECK(written_bytes > 0);
+
+      remaining_size -= written_bytes;
+    }
+  }
+
+  archive_entry_free(entry);
+}
+
+void CompressorArchiveLibarchive::CloseArchive(bool has_error) {
+  // If has_error is true, mark the archive object as being unusable and
+  // release resources without writing no more data on the archive.
+  if (has_error)
+    archive_write_fail(archive_);
+  if (archive_) {
+    archive_write_free(archive_);
+    archive_ = NULL;
+  }
+}
diff --git a/unpacker/cpp/compressor_archive_libarchive.h b/unpacker/cpp/compressor_archive_libarchive.h
new file mode 100644
index 0000000..b0a299d
--- /dev/null
+++ b/unpacker/cpp/compressor_archive_libarchive.h
@@ -0,0 +1,61 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPRESSOR_ARCHIVE_LIBARCHIVE_H_
+#define COMPRESSOR_ARCHIVE_LIBARCHIVE_H_
+
+#include <string>
+
+#include "archive.h"
+
+#include "compressor_archive.h"
+#include "compressor_stream.h"
+
+// A namespace with constants used by CompressorArchiveLibarchive.
+namespace compressor_archive_constants {
+const int64_t kMaximumDataChunkSize = 512 * 1024;
+const int kFilePermission = 640;
+const int kDirectoryPermission = 760;
+}  // namespace compressor_archive_constants
+
+class CompressorArchiveLibarchive : public CompressorArchive {
+ public:
+  explicit CompressorArchiveLibarchive(CompressorStream* compressor_stream);
+
+  virtual ~CompressorArchiveLibarchive();
+
+  // Creates an archive object.
+  virtual void CreateArchive();
+
+  // Releases all resources obtained by libarchive.
+  virtual void CloseArchive(bool has_error);
+
+  // Adds an entry to the archive.
+  virtual void AddToArchive(const std::string& filename,
+                            int64_t file_size,
+                            time_t modification_time,
+                            bool is_directory);
+
+  // A getter function for archive_.
+  struct archive* archive() const { return archive_; }
+
+  // A getter function for compressor_stream.
+  CompressorStream* compressor_stream() const { return compressor_stream_; }
+
+ private:
+  // An instance that takes care of all IO operations.
+  CompressorStream* compressor_stream_;
+
+  // The libarchive correspondent archive object.
+  struct archive* archive_;
+
+  // The libarchive correspondent archive entry object that is currently
+  // processed.
+  struct archive_entry* entry;
+
+  // The buffer used to store the data read from JavaScript.
+  char* destination_buffer_;
+};
+
+#endif  // COMPRESSOR_ARCHIVE_LIBARCHIVE_H_
diff --git a/unpacker/cpp/compressor_io_javascript_stream.cc b/unpacker/cpp/compressor_io_javascript_stream.cc
new file mode 100644
index 0000000..5fdb081
--- /dev/null
+++ b/unpacker/cpp/compressor_io_javascript_stream.cc
@@ -0,0 +1,84 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "compressor_io_javascript_stream.h"
+
+#include <limits>
+#include <thread>
+
+#include "archive.h"
+#include "ppapi/cpp/logging.h"
+
+CompressorIOJavaScriptStream::CompressorIOJavaScriptStream(
+    JavaScriptCompressorRequestorInterface* requestor)
+    : requestor_(requestor) {
+  pthread_mutex_init(&shared_state_lock_, NULL);
+  pthread_cond_init(&available_data_cond_, NULL);
+  pthread_cond_init(&data_written_cond_, NULL);
+
+  pthread_mutex_lock(&shared_state_lock_);
+  available_data_ = false;
+  pthread_mutex_unlock(&shared_state_lock_);
+}
+
+CompressorIOJavaScriptStream::~CompressorIOJavaScriptStream() {
+  pthread_cond_destroy(&data_written_cond_);
+  pthread_cond_destroy(&available_data_cond_);
+  pthread_mutex_destroy(&shared_state_lock_);
+};
+
+int64_t CompressorIOJavaScriptStream::Write(int64_t byte_to_write,
+    const pp::VarArrayBuffer& buffer) {
+  pthread_mutex_lock(&shared_state_lock_);
+  requestor_->WriteChunkRequest(byte_to_write, buffer);
+
+  pthread_cond_wait(&data_written_cond_, &shared_state_lock_);
+
+  int64_t written_bytes = written_bytes_;
+  pthread_mutex_unlock(&shared_state_lock_);
+
+  return written_bytes;
+}
+
+void CompressorIOJavaScriptStream::WriteChunkDone(int64_t written_bytes) {
+  pthread_mutex_lock(&shared_state_lock_);
+  written_bytes_ = written_bytes;
+  pthread_cond_signal(&data_written_cond_);
+  pthread_mutex_unlock(&shared_state_lock_);
+}
+
+int64_t CompressorIOJavaScriptStream::Read(int64_t bytes_to_read,
+                                           char* destination_buffer) {
+  pthread_mutex_lock(&shared_state_lock_);
+
+  destination_buffer_ = destination_buffer;
+  requestor_->ReadFileChunkRequest(bytes_to_read);
+
+  while (!available_data_) {
+    pthread_cond_wait(&available_data_cond_, &shared_state_lock_);
+  }
+
+  int64_t read_bytes = read_bytes_;
+  available_data_ = false;
+  pthread_mutex_unlock(&shared_state_lock_);
+  return read_bytes;
+}
+
+void CompressorIOJavaScriptStream::ReadFileChunkDone(int64_t read_bytes,
+      pp::VarArrayBuffer* array_buffer) {
+  pthread_mutex_lock(&shared_state_lock_);
+
+  // JavaScript sets a negative value in read_bytes if an error occurred while
+  // reading a chunk.
+  if (read_bytes >= 0) {
+    char* array_buffer_data = static_cast<char*>(array_buffer->Map());
+    memcpy(destination_buffer_, array_buffer_data, read_bytes);
+    array_buffer->Unmap();
+  }
+
+  read_bytes_ = read_bytes;
+  available_data_ = true;
+  pthread_cond_signal(&available_data_cond_);
+  pthread_mutex_unlock(&shared_state_lock_);
+}
diff --git a/unpacker/cpp/compressor_io_javascript_stream.h b/unpacker/cpp/compressor_io_javascript_stream.h
new file mode 100644
index 0000000..d0a22d8
--- /dev/null
+++ b/unpacker/cpp/compressor_io_javascript_stream.h
@@ -0,0 +1,63 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPRESSOR_IO_JAVSCRIPT_STREAM_H_
+#define COMPRESSOR_IO_JAVSCRIPT_STREAM_H_
+
+#include <pthread.h>
+#include <string>
+
+#include "archive.h"
+#include "ppapi/cpp/instance_handle.h"
+#include "ppapi/cpp/var_array_buffer.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi/utility/threading/lock.h"
+#include "ppapi/utility/threading/simple_thread.h"
+
+#include "compressor_stream.h"
+#include "javascript_compressor_requestor_interface.h"
+
+class CompressorIOJavaScriptStream : public CompressorStream {
+ public:
+  CompressorIOJavaScriptStream(
+      JavaScriptCompressorRequestorInterface* requestor);
+
+  virtual ~CompressorIOJavaScriptStream();
+
+  virtual int64_t Write(int64_t bytes_to_read,
+                        const pp::VarArrayBuffer& buffer);
+
+  virtual void WriteChunkDone(int64_t write_bytes);
+
+  virtual int64_t Read(int64_t bytes_to_read, char* destination_buffer);
+
+  virtual void ReadFileChunkDone(int64_t read_bytes,
+                                 pp::VarArrayBuffer* buffer);
+
+private:
+  // A requestor that makes calls to JavaScript to read and write chunks.
+  JavaScriptCompressorRequestorInterface* requestor_;
+
+  pthread_mutex_t shared_state_lock_;
+  pthread_cond_t available_data_cond_;
+  pthread_cond_t data_written_cond_;
+
+  // The bytelength of the data written onto the archive for the last write
+  // chunk request. If this value is negative, some error occurred when writing
+  // a chunk in JavaScript.
+  int64_t written_bytes_;
+
+  // The bytelength of the data read from the entry for the last read file chunk
+  // request. If this value is negative, some error occurred when reading a
+  // chunk in JavaScript.
+  int64_t read_bytes_;
+
+  // True if destination_buffer_ is available.
+  bool available_data_;
+
+  // Stores the data read from JavaScript.
+  char* destination_buffer_;
+};
+
+#endif  // COMPRESSOR_IO_JAVSCRIPT_STREAM_H_
diff --git a/unpacker/cpp/compressor_stream.h b/unpacker/cpp/compressor_stream.h
new file mode 100644
index 0000000..de1f420
--- /dev/null
+++ b/unpacker/cpp/compressor_stream.h
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPRESSOR_STREAM_H_
+#define COMPRESSOR_STREAM_H_
+
+#include <string>
+
+// A IO class that reads and writes data from and to files through JavaScript.
+// Currently, Read() and Write() methods are not called at the same time.
+// To improve the performance, these should be called in parallel by preparing
+// two buffers and calling them with these buffers alternatively.
+class CompressorStream {
+ public:
+  virtual ~CompressorStream() {}
+
+  // Writes the given buffer onto the archive. After sending a write chunk
+  // request to JavaScript, it waits until WriteChunkDone() is called in the
+  // main thread. Thus, This method must not be called in the main thread.
+  virtual int64_t Write(int64_t bytes_to_write,
+                        const pp::VarArrayBuffer& buffer) = 0;
+
+  // Called when write chunk done response arrives from JavaScript. Sends a
+  // signal to invoke Write function in another thread again.
+  virtual void WriteChunkDone(int64_t write_bytes) = 0;
+
+  // Reads a file chunk from the entry that is currently being processed. After
+  // sending a read file chunk request to JavaScript, it waits until
+  // ReadFileChunkDone() is called in the main thread. Thus, This method must
+  // not be called in the main thread.
+  virtual int64_t Read(int64_t bytes_to_read, char* destination_buffer) = 0;
+
+  // Called when read file chunk done response arrives from JavaScript. Copies
+  // the binary data in the given buffer to destination_buffer_ and Sends a
+  // signal to invoke Read function in another thread again. buffer must not be
+  // const because buffer.Map() and buffer.Unmap() can not be called with const.
+  virtual void ReadFileChunkDone(int64_t read_bytes,
+                                 pp::VarArrayBuffer* buffer) = 0;
+};
+
+#endif  // COMPRESSOR_STREAM_H_
diff --git a/unpacker/cpp/javascript_compressor_requestor_interface.h b/unpacker/cpp/javascript_compressor_requestor_interface.h
new file mode 100644
index 0000000..367c021
--- /dev/null
+++ b/unpacker/cpp/javascript_compressor_requestor_interface.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef JAVASCRIPT_COMPRESSOR_REQUESTOR_INTERFACE_H_
+#define JAVASCRIPT_COMPRESSOR_REQUESTOR_INTERFACE_H_
+
+#include <string>
+
+// Makes requests from Compressor to JavaScript.
+class JavaScriptCompressorRequestorInterface {
+ public:
+  virtual ~JavaScriptCompressorRequestorInterface() {}
+
+  virtual void WriteChunkRequest(int64_t length,
+                                 const pp::VarArrayBuffer& buffer) = 0;
+
+  virtual void ReadFileChunkRequest(int64_t length) = 0;
+};
+
+#endif  // JAVASCRIPT_COMPRESSOR_REQUESTOR_INTERFACE_H_
diff --git a/unpacker/cpp/javascript_message_sender_interface.h b/unpacker/cpp/javascript_message_sender_interface.h
index 282356b..17c9f9e 100644
--- a/unpacker/cpp/javascript_message_sender_interface.h
+++ b/unpacker/cpp/javascript_message_sender_interface.h
@@ -16,6 +16,9 @@
                                    const std::string& request_id,
                                    const std::string& message) = 0;
 
+  virtual void SendCompressorError(int compressor_id,
+                                   const std::string& message) = 0;
+
   virtual void SendFileChunkRequest(const std::string& file_system_id,
                                     const std::string& request_id,
                                     int64_t offset,
@@ -46,6 +49,19 @@
                               int src_line,
                               const std::string& src_func,
                               const std::string& message) = 0;
+
+  virtual void SendCreateArchiveDone(int compressor_id) = 0;
+
+  virtual void SendReadFileChunk(int compressor_id_,
+                                 int64_t file_size) = 0;
+
+  virtual void SendWriteChunk(int compressor_id,
+                              const pp::VarArrayBuffer& array_buffer,
+                              int64_t length) = 0;
+
+  virtual void SendAddToArchiveDone(int compressor_id) = 0;
+
+  virtual void SendCloseArchiveDone(int compressor_id) = 0;
 };
 
 #define CONSOLE_LOG(fsid, rid, msg) \
diff --git a/unpacker/cpp/module.cc b/unpacker/cpp/module.cc
index c5fae8c..24ae6c0 100644
--- a/unpacker/cpp/module.cc
+++ b/unpacker/cpp/module.cc
@@ -13,12 +13,14 @@
 #include "ppapi/utility/threading/lock.h"
 #include "ppapi/utility/threading/simple_thread.h"
 
+#include "compressor.h"
 #include "request.h"
 #include "volume.h"
 
 namespace {
 
 typedef std::map<std::string, Volume*>::const_iterator volume_iterator;
+typedef std::map<int, Compressor*>::const_iterator compressor_iterator;
 
 // An internal implementation of JavaScriptMessageSenderInterface. This class
 // handles all communication from the module to the JavaScript code. Thread
@@ -37,6 +39,12 @@
         request::CreateFileSystemError(file_system_id, request_id, message));
   }
 
+  virtual void SendCompressorError(int compressor_id,
+                                   const std::string& message) {
+    JavaScriptPostMessage(
+        request::CreateCompressorError(compressor_id, message));
+  }
+
   virtual void SendFileChunkRequest(const std::string& file_system_id,
                                     const std::string& request_id,
                                     int64_t offset,
@@ -91,6 +99,33 @@
         file_system_id, request_id, src_file, src_line, src_func, message));
   }
 
+  virtual void SendCreateArchiveDone(int compressor_id) {
+    JavaScriptPostMessage(request::CreateCreateArchiveDoneResponse(
+        compressor_id));
+  }
+
+  virtual void SendReadFileChunk(int compressor_id, int64_t length) {
+    JavaScriptPostMessage(request::CreateReadFileChunkRequest(
+        compressor_id, length));
+  }
+
+  virtual void SendWriteChunk(int compressor_id,
+      const pp::VarArrayBuffer& array_buffer,
+      int64_t length) {
+    JavaScriptPostMessage(request::CreateWriteChunkRequest(
+        compressor_id, array_buffer, length));
+  }
+
+  virtual void SendAddToArchiveDone(int compressor_id) {
+    JavaScriptPostMessage(request::CreateAddToArchiveDoneResponse(
+        compressor_id));
+  }
+
+  virtual void SendCloseArchiveDone(int compressor_id) {
+    JavaScriptPostMessage(request::CreateCloseArchiveDoneResponse(
+        compressor_id));
+  }
+
  private:
   // Posts a message to JavaScript. This is prone to races in case of using
   // NaCl instead of PNaCl. See crbug.com/413513.
@@ -128,6 +163,18 @@
     PP_DCHECK(var_dict.Get(request::key::kOperation).is_int());
     int operation = var_dict.Get(request::key::kOperation).AsInt();
 
+    if (request::IsPackRequest(operation))
+      HandlePackMessage(var_dict, operation);
+    else
+      HandleUnpackMessage(var_dict, operation);
+  }
+
+ private:
+
+  // Processes unpack messages.
+  void HandleUnpackMessage(const pp::VarDictionary& var_dict,
+      const int operation) {
+
     PP_DCHECK(var_dict.Get(request::key::kFileSystemId).is_string());
     std::string file_system_id =
         var_dict.Get(request::key::kFileSystemId).AsString();
@@ -135,7 +182,7 @@
     PP_DCHECK(var_dict.Get(request::key::kRequestId).is_string());
     std::string request_id = var_dict.Get(request::key::kRequestId).AsString();
 
-    // Process operation.
+    // Processes operation.
     switch (operation) {
       case request::READ_METADATA: {
         ReadMetadata(var_dict, file_system_id, request_id);
@@ -183,7 +230,44 @@
     }
   }
 
- private:
+  // Processes pack messages.
+  void HandlePackMessage(const pp::VarDictionary& var_dict,
+      const int operation) {
+    PP_DCHECK(var_dict.Get(request::key::kCompressorId).is_int());
+    int compressor_id =
+        var_dict.Get(request::key::kCompressorId).AsInt();
+
+    switch (operation) {
+      case request::CREATE_ARCHIVE: {
+        CreateArchive(compressor_id);
+        break;
+      }
+
+      case request::ADD_TO_ARCHIVE: {
+        AddToArchive(var_dict, compressor_id);
+        break;
+      }
+
+      case request::READ_FILE_CHUNK_DONE: {
+        ReadFileChunkDone(var_dict, compressor_id);
+        break;
+      }
+
+      case request::WRITE_CHUNK_DONE: {
+        WriteChunkDone(var_dict, compressor_id);
+        break;
+      }
+
+      case request::CLOSE_ARCHIVE: {
+        CloseArchive(var_dict, compressor_id);
+        break;
+      }
+
+      default:
+        PP_NOTREACHED();
+    }
+  }
+
   // Reads the metadata for the corresponding volume for file_system_id. This
   // should be called only once and before any other operation like OpenFile,
   // ReadFile, etc.
@@ -320,10 +404,63 @@
     iterator->second->ReadFile(request_id, var_dict);
   }
 
+  // Requests libarchive to create an archive object for the given compressor_id.
+  void CreateArchive(int compressor_id) {
+    Compressor* compressor =
+        new Compressor(instance_handle_, compressor_id, &message_sender_);
+    if (!compressor->Init()) {
+      std::stringstream ss;
+      ss << compressor_id;
+      message_sender_.SendCompressorError(
+          compressor_id,
+          "Could not create a compressor for compressor id: " + ss.str() + ".");
+      delete compressor;
+      return;
+    }
+    compressors_[compressor_id] = compressor;
+
+    compressor->CreateArchive();
+  }
+
+  void AddToArchive(const pp::VarDictionary& var_dict,
+                    int compressor_id) {
+    compressor_iterator iterator = compressors_.find(compressor_id);
+    PP_DCHECK(iterator != compressors_.end());
+
+    iterator->second->AddToArchive(var_dict);
+  }
+
+  void ReadFileChunkDone(const pp::VarDictionary& var_dict,
+                         const int compressor_id) {
+    compressor_iterator iterator = compressors_.find(compressor_id);
+    PP_DCHECK(iterator != compressors_.end());
+
+    iterator->second->ReadFileChunkDone(var_dict);
+  }
+
+  void WriteChunkDone(const pp::VarDictionary& var_dict,
+                      int compressor_id) {
+    compressor_iterator iterator = compressors_.find(compressor_id);
+    PP_DCHECK(iterator != compressors_.end());
+
+    iterator->second->WriteChunkDone(var_dict);
+  }
+
+  void CloseArchive(const pp::VarDictionary& var_dict,
+                    int compressor_id) {
+    compressor_iterator iterator = compressors_.find(compressor_id);
+
+    if (iterator != compressors_.end())
+      iterator->second->CloseArchive(var_dict);
+  }
+
   // A map that holds for every opened archive its instance. The key is the file
   // system id of the archive.
   std::map<std::string, Volume*> volumes_;
 
+  // A map from compressor ids to compressors.
+  std::map<int, Compressor*> compressors_;
+
   // An pp::InstanceHandle used to create pp::SimpleThread in Volume.
   pp::InstanceHandle instance_handle_;
 
diff --git a/unpacker/cpp/request.cc b/unpacker/cpp/request.cc
index 443e9bf..db8528b 100644
--- a/unpacker/cpp/request.cc
+++ b/unpacker/cpp/request.cc
@@ -21,6 +21,12 @@
 
 }  // namespace
 
+// Return true if the given operation is related to packing.
+bool request::IsPackRequest(int operation) {
+  return request::MINIMUM_PACK_REQUEST_VALUE <= operation ||
+         operation == request::COMPRESSOR_ERROR;
+}
+
 pp::VarDictionary request::CreateReadMetadataDoneResponse(
     const std::string& file_system_id,
     const std::string& request_id,
@@ -85,6 +91,56 @@
   return response;
 }
 
+pp::VarDictionary request::CreateCreateArchiveDoneResponse(
+    const int compressor_id) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, CREATE_ARCHIVE_DONE);
+  request.Set(request::key::kCompressorId, compressor_id);
+  return request;
+}
+
+pp::VarDictionary request::CreateReadFileChunkRequest(
+    const int compressor_id,
+    const int64_t length) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, READ_FILE_CHUNK);
+  request.Set(request::key::kCompressorId, compressor_id);
+
+  std::stringstream ss_length;
+  ss_length << length;
+  request.Set(request::key::kLength, ss_length.str());
+  return request;
+}
+
+pp::VarDictionary request::CreateWriteChunkRequest(
+    int compressor_id,
+    const pp::VarArrayBuffer& array_buffer,
+    int64_t length) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, WRITE_CHUNK);
+  request.Set(request::key::kCompressorId, compressor_id);
+
+  request.Set(request::key::kChunkBuffer, array_buffer);
+  std::stringstream ss_length;
+  ss_length << length;
+  request.Set(request::key::kLength, ss_length.str());
+  return request;
+}
+
+pp::VarDictionary request::CreateAddToArchiveDoneResponse(int compressor_id) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, ADD_TO_ARCHIVE_DONE);
+  request.Set(request::key::kCompressorId, compressor_id);
+  return request;
+}
+
+pp::VarDictionary request::CreateCloseArchiveDoneResponse(int compressor_id) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, CLOSE_ARCHIVE_DONE);
+  request.Set(request::key::kCompressorId, compressor_id);
+  return request;
+}
+
 pp::VarDictionary request::CreateFileSystemError(
     const std::string& file_system_id,
     const std::string& request_id,
@@ -111,6 +167,16 @@
   return request;
 }
 
+pp::VarDictionary request::CreateCompressorError(
+    int compressor_id,
+    const std::string& error) {
+  pp::VarDictionary request;
+  request.Set(request::key::kOperation, COMPRESSOR_ERROR);
+  request.Set(request::key::kCompressorId, compressor_id);
+  request.Set(request::key::kError, error);
+  return request;
+}
+
 int64_t request::GetInt64FromString(const pp::VarDictionary& dictionary,
                                     const std::string& request_key) {
   std::stringstream ss_int64(dictionary.Get(request_key).AsString());
diff --git a/unpacker/cpp/request.h b/unpacker/cpp/request.h
index e24225c..197866c 100644
--- a/unpacker/cpp/request.h
+++ b/unpacker/cpp/request.h
@@ -16,21 +16,15 @@
 // on the JS side.
 namespace key {
 
-// Mandatory keys for all requests.
+// Mandatory keys for all unpacking requests.
 const char kOperation[] = "operation";  // Should be a request::Operation.
 const char kFileSystemId[] = "file_system_id";  // Should be a string.
 const char kRequestId[] = "request_id";         // Should be a string.
 
-// Optional keys depending on request operation.
-const char kError[] = "error";        // Should be a string.
+// Optional keys unique to unpacking operations.
 const char kMetadata[] = "metadata";  // Should be a pp:VarDictionary.
 const char kArchiveSize[] =
     "archive_size";  // Should be a string as int64_t is not support by pp::Var.
-const char kChunkBuffer[] = "chunk_buffer";  // Should be a pp::VarArrayBuffer.
-const char kOffset[] = "offset";       // Should be a string as int64_t is not
-                                       // supported by pp::Var.
-const char kLength[] = "length";       // Should be a string as int64_t is not
-                                       // supported by pp::Var.
 const char kIndex[] = "index";         // Should be a string as int64_t is not
                                        // supported by pp::Var.
 const char kEncoding[] = "encoding";   // Should be a string.
@@ -40,11 +34,30 @@
                                                   // pp::VarArrayBuffer.
 const char kHasMoreData[] = "has_more_data";      // Should be a bool.
 const char kPassphrase[] = "passphrase";          // Should be a string.
+
+// Mandatory keys for all packing requests.
+const char kCompressorId[] = "compressor_id";         // Should be an int.
+
+// Optional keys unique to packing operations.
+const char kEntryId[] = "entry_id";                   // Should be an int.
+const char kPathname[] = "pathname";                  // Should be a string.
+const char kFileSize[] = "file_size";                 // Should be a string.
+const char kIsDirectory[] = "is_directory";           // Should be a bool.
+const char kModificationTime[] = "modification_time"; // Should be a string
+                                                      // (mm/dd/yy h:m:s).
+const char kHasError[] = "has_error";                 // Should be a bool.
+
+// Optional keys used for both packing and unpacking operations.
+const char kError[] = "error";        // Should be a string.
+const char kChunkBuffer[] = "chunk_buffer";  // Should be a pp::VarArrayBuffer.
+const char kOffset[] = "offset";       // Should be a string as int64_t is not
+                                       // supported by pp::Var.
+const char kLength[] = "length";       // Should be a string as int64_t is not
+                                       // supported by pp::Var.
 const char kSrcFile[] = "src_file";               // Should be a string.
 const char kSrcLine[] = "src_line";               // Should be a string.
 const char kSrcFunc[] = "src_func";               // Should be a string.
 const char kMessage[] = "message";                // Should be a string.
-
 }  // namespace key
 
 // Defines request operations. These operations should be the same as the
@@ -67,9 +80,26 @@
   READ_FILE_DONE = 14,
   CONSOLE_LOG = 15,
   CONSOLE_DEBUG = 16,
+  CREATE_ARCHIVE = 17,
+  CREATE_ARCHIVE_DONE = 18,
+  ADD_TO_ARCHIVE = 19,
+  ADD_TO_ARCHIVE_DONE = 20,
+  READ_FILE_CHUNK = 21,
+  READ_FILE_CHUNK_DONE = 22,
+  WRITE_CHUNK = 23,
+  WRITE_CHUNK_DONE = 24,
+  CLOSE_ARCHIVE = 25,
+  CLOSE_ARCHIVE_DONE = 26,
   FILE_SYSTEM_ERROR = -1,  // Errors specific to a file system.
+  COMPRESSOR_ERROR = -2    // Errors specific to a compressor.
 };
 
+// Operations greater than or equal to this value are for packing.
+const int MINIMUM_PACK_REQUEST_VALUE = 17;
+
+// Return true if the given operation is related to packing.
+bool IsPackRequest(int operation);
+
 // Creates a response to READ_METADATA request.
 pp::VarDictionary CreateReadMetadataDoneResponse(
     const std::string& file_system_id,
@@ -103,6 +133,19 @@
     const pp::VarArrayBuffer& array_buffer,
     bool has_more_data);
 
+pp::VarDictionary CreateCreateArchiveDoneResponse(int compressor_id);
+
+pp::VarDictionary CreateReadFileChunkRequest(int compressor_id,
+                                             int64_t length);
+
+pp::VarDictionary CreateWriteChunkRequest(int compressor_id,
+                                          const pp::VarArrayBuffer& array_buffer,
+                                          int64_t length);
+
+pp::VarDictionary CreateAddToArchiveDoneResponse(int compressor_id);
+
+pp::VarDictionary CreateCloseArchiveDoneResponse(int compressor_id);
+
 // Creates a file system error.
 pp::VarDictionary CreateFileSystemError(const std::string& file_system_id,
                                         const std::string& request_id,
@@ -116,6 +159,10 @@
     const std::string& src_func,
     const std::string& message);
 
+// Creates a compressor error.
+pp::VarDictionary CreateCompressorError(int compressor_id,
+                                        const std::string& error);
+
 // Obtains a int64_t from a string value inside dictionary based on a
 // request::Key.
 int64_t GetInt64FromString(const pp::VarDictionary& dictionary,
diff --git a/unpacker/cpp/volume.cc b/unpacker/cpp/volume.cc
index 8d5a3f5..588698f 100644
--- a/unpacker/cpp/volume.cc
+++ b/unpacker/cpp/volume.cc
@@ -391,7 +391,7 @@
     return;
   }
 
-  if (!volume_archive_->GetNextHeader()) {
+  if (volume_archive_->GetNextHeader() == VolumeArchive::RESULT_FAIL) {
     message_sender_->SendFileSystemError(
         file_system_id_, args.request_id, volume_archive_->error_message());
     ClearJob();
diff --git a/unpacker/cpp/volume.h b/unpacker/cpp/volume.h
index 212b336..dc8efb4 100644
--- a/unpacker/cpp/volume.h
+++ b/unpacker/cpp/volume.h
@@ -11,9 +11,9 @@
 #include "ppapi/cpp/instance_handle.h"
 #include "ppapi/cpp/var_array_buffer.h"
 #include "ppapi/cpp/var_dictionary.h"
+#include "ppapi/utility/completion_callback_factory.h"
 #include "ppapi/utility/threading/lock.h"
 #include "ppapi/utility/threading/simple_thread.h"
-#include "ppapi/utility/completion_callback_factory.h"
 
 #include "javascript_requestor_interface.h"
 #include "javascript_message_sender_interface.h"
diff --git a/unpacker/html/compressor.html b/unpacker/html/compressor.html
new file mode 100644
index 0000000..f245e0d
--- /dev/null
+++ b/unpacker/html/compressor.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+  <head>
+    <script src="../js/compressor-foreground.js"></script>
+  </head>
+</html>
diff --git a/unpacker/js/app.js b/unpacker/js/app.js
index f1ea470..21182a1 100644
--- a/unpacker/js/app.js
+++ b/unpacker/js/app.js
@@ -47,6 +47,13 @@
   volumes: {},
 
   /**
+   * Each "Pack with" request from Files app creates a new compressor.
+   * Thus, multiple compressors can exist at the same time.
+   * @type {!Object<!unpacker.types.CompressorId, !unpacker.Compressor>}
+   */
+  compressors: {},
+
+  /**
    * A map with promises of loading a volume's metadata from NaCl.
    * Any call from fileSystemProvider API should work only on valid metadata.
    * These promises ensure that the fileSystemProvider API calls wait for the
@@ -79,15 +86,30 @@
   /**
    * Function called on receiving a message from NaCl module. Registered by
    * common.js.
+   * Process pack message by getting compressor and passing the message to it.
    * @param {!Object} message The message received from NaCl module.
+   * @param {!unpacker.request.Operation} operation
    * @private
    */
-  handleMessage_: function(message) {
-    // Get mandatory fields in a message.
-    var operation = message.data[unpacker.request.Key.OPERATION];
-    console.assert(operation != undefined,  // Operation can be 0.
-                   'No NaCl operation: ' + operation + '.');
+  handlePackMessage_: function(message, operation) {
+    var compressorId = message.data[unpacker.request.Key.COMPRESSOR_ID];
+    console.assert(compressorId, 'No NaCl compressor id.');
 
+    var compressor = unpacker.app.compressors[compressorId];
+    if (!compressor) {
+      console.error('No compressor for compressor id: ' + compressorId + '.');
+      return;
+    }
+    compressor.processMessage(message.data, operation);
+  },
+
+  /**
+   * Process unpack message by getting volume and passing the message to it.
+   * @param {!Object} message The message received from NaCl module.
+   * @param {!unpacker.request.Operation} operation
+   * @private
+   */
+  handleUnpackMessage_: function(message, operation) {
     var fileSystemId = message.data[unpacker.request.Key.FILE_SYSTEM_ID];
     console.assert(fileSystemId, 'No NaCl file system id.');
 
@@ -106,6 +128,25 @@
   },
 
   /**
+   * Function called on receiving a message from NaCl module. Registered by
+   * common.js.
+   * @param {!Object} message The message received from NaCl module.
+   * @private
+   */
+  handleMessage_: function(message) {
+    // Get mandatory fields in a message.
+    var operation = message.data[unpacker.request.Key.OPERATION];
+    console.assert(operation != undefined,  // Operation can be 0.
+                   'No NaCl operation: ' + operation + '.');
+
+    // Assign the message to either module.
+    if (unpacker.request.isPackRequest(operation))
+      unpacker.app.handlePackMessage_(message, operation);
+    else
+      unpacker.app.handleUnpackMessage_(message, operation);
+  },
+
+  /**
    * Saves state in case of restarts, event page suspend, crashes, etc.
    * @param {!Array<!unpacker.types.FileSystemId>} fileSystemIdsArray
    * @private
@@ -421,6 +462,36 @@
   },
 
   /**
+   * Cleans up the resources for a compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @param {boolean} hasError
+   */
+  cleanupCompressor: function(compressorId, hasError) {
+    var compressor = unpacker.app.compressors[compressorId];
+    if (!compressor) {
+      console.error('No compressor for: compressor id' + compressorId + '.');
+      return;
+    }
+
+    unpacker.app.mountProcessCounter--;
+    if (Object.keys(unpacker.app.volumes).length === 0 &&
+        unpacker.app.mountProcessCounter === 0) {
+      unpacker.app.unloadNaclModule();
+    } else {
+      // Request libarchive to abort any ongoing process and release resources.
+      // The argument indicates whether an error occurred or not.
+      if (hasError)
+        compressor.sendCloseArchiveRequest(hasError);
+    }
+
+    // Delete the archive file if it exists.
+    if (compressor.archiveFileEntry)
+      compressor.archiveFileEntry.remove();
+
+    delete unpacker.app.compressors[compressorId];
+  },
+
+  /**
    * Unmounts a volume and removes any resources related to the volume from both
    * the extension and the local storage state.
    * @param {!unpacker.types.FileSystemId} fileSystemId
@@ -567,6 +638,66 @@
   },
 
   /**
+   * Creates a new compressor and compresses entries.
+   * @param {!Object} launchData
+   */
+  onLaunchedWithPack: function(launchData) {
+    unpacker.app.mountProcessCounter++;
+
+    // Create a promise to load the NaCL module.
+    if (!unpacker.app.moduleLoadedPromise) {
+      unpacker.app.loadNaclModule(unpacker.app.DEFAULT_MODULE_NMF,
+                                  unpacker.app.DEFAULT_MODULE_TYPE);
+    }
+
+    unpacker.app.moduleLoadedPromise
+        .then(function() {
+          var compressor = new unpacker.Compressor(
+              /** @type {!Object} */ (unpacker.app.naclModule),
+             launchData.items);
+
+          var compressorId = compressor.getCompressorId();
+
+          unpacker.app.compressors[compressorId] = compressor;
+
+          // TODO(takise): Error messages have not been prepared yet for timer
+          // and error processing.
+
+          // If packing takes significant amount of time, then show a
+          // notification about packing in progress.
+          // var deferredNotificationTimer = setTimeout(function() {
+          //   chrome.notifications.create(compressorId.toString(), {
+          //     type: 'basic',
+          //     iconUrl: chrome.runtime.getManifest().icons[128],
+          //     title: entry.name,
+          //     message: chrome.i18n.getMessage('packingMessage'),
+          //   }, function() {});
+          // }, unpacker.app.PACKING_NOTIFICATION_DELAY);
+
+          var onError = function(compressorId) {
+            // clearTimeout(deferredNotificationTimer);
+            // console.error('Packing error: ' + error.message + '.');
+            // chrome.notifications.create(compressorId.toString(), {
+            //   type: 'basic',
+            //   iconUrl: chrome.runtime.getManifest().icons[128],
+            //   title: entry.name,
+            //   message: chrome.i18n.getMessage('packingErrorMessage')
+            // }, function() {});
+            unpacker.app.cleanupCompressor(compressorId, true /* hasError */);
+          };
+
+          var onSuccess = function(compressorId) {
+            // clearTimeout(deferredNotificationTimer);
+            // chrome.notifications.clear(compressorId.toString(),
+            //     function() {});
+            unpacker.app.cleanupCompressor(compressorId, false /* hasError */);
+          };
+
+          compressor.compress(onSuccess, onError);
+        });
+  },
+
+  /**
    * Creates a volume for every opened file with the extension or mime type
    * declared in the manifest file.
    * @param {!Object} launchData
@@ -579,13 +710,7 @@
    *     system id of the volume that failed to load. Can be called multiple
    *     times, depending on how many volumes must be loaded.
    */
-  onLaunched: function(launchData, opt_onSuccess, opt_onError) {
-    if (launchData.items == null) {
-      // The user tried to launch us directly.
-      console.log('Ignoring launch request w/out items field', {launchData});
-      return;
-    }
-
+  onLaunchedWithUnpack: function(launchData, opt_onSuccess, opt_onError) {
     // Increment the counter that indicates the number of ongoing mouot process.
     unpacker.app.mountProcessCounter++;
 
@@ -684,6 +809,28 @@
   },
 
   /**
+   * Fired when this extension is launched.
+   * Calls a module designated by launchData.id.
+   * Currently, Verbs API does not support "unpack" option. Thus, any launchData
+   * that does not have "pack" as id is regarded as unpack for now.
+   * @param {!Object} launchData
+   * @param {function(string)=} opt_onSuccess
+   * @param {function(string)=} opt_onError
+   */
+  onLaunched: function(launchData, opt_onSuccess, opt_onError) {
+    if (launchData.items == null) {
+      // The user tried to launch us directly.
+      console.log('Ignoring launch request w/out items field', {launchData});
+      return;
+    }
+
+    if (launchData.id === "pack")
+      unpacker.app.onLaunchedWithPack(launchData);
+    else
+      unpacker.app.onLaunchedWithUnpack(launchData, opt_onSuccess, opt_onError);
+  },
+
+  /**
    * Saves the state before suspending the event page, so we can resume it
    * once new events arrive.
    */
diff --git a/unpacker/js/compressor-foreground.js b/unpacker/js/compressor-foreground.js
new file mode 100644
index 0000000..bf482e3
--- /dev/null
+++ b/unpacker/js/compressor-foreground.js
@@ -0,0 +1,39 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+window.onload = function() {
+  chrome.runtime.getBackgroundPage(function(backgroundPage) {
+
+    // Called from the background page. We need to place this function here
+    // because chrome.fileSystem.chooseEntry only works on forground page.
+    backgroundPage.unpacker.Compressor.prototype.createArchiveFileForeground_ =
+        function(compressorId) {
+          var compressor =
+              backgroundPage.unpacker.app.compressors[compressorId];
+          var suggestedName = compressor.archiveName_;
+          // Create an archive file.
+          chrome.fileSystem.chooseEntry(
+              {type: 'saveFile', suggestedName: suggestedName},
+              function(entry, fileEntries) {
+                if (!entry) {
+                  console.error('Failed to create an archive file.');
+                  compressor.onError_(compressor.compressorId_);
+                  return;
+                }
+
+                compressor.archiveFileEntry_ = entry;
+
+                compressor.sendCreateArchiveRequest_();
+              });
+        };
+
+    // Some compressors are waiting for this foreground page to be loaded.
+    backgroundPage.unpacker.Compressor.CompressorIdQueue.forEach(
+        function(compressorId) {
+          var compressor =
+              backgroundPage.unpacker.app.compressors[compressorId];
+          compressor.createArchiveFileForeground_(compressorId);
+        });
+  });
+};
diff --git a/unpacker/js/compressor.js b/unpacker/js/compressor.js
new file mode 100644
index 0000000..a96fd4d
--- /dev/null
+++ b/unpacker/js/compressor.js
@@ -0,0 +1,516 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * A class that takes care of communication with NaCL and creates an archive.
+ * One instance of this class is created for each pack request. Since multiple
+ * compression requests can be in progress at the same time, each instance has
+ * a unique compressor id of positive integer. Every communication with NaCL
+ * must be done with compressor id.
+ * @constructor
+ * @param {!Object} naclModule The nacl module.
+ * @param {!Array} items The items to be packed.
+ */
+unpacker.Compressor = function(naclModule, items) {
+  /**
+   * @private {!Object}
+   * @const
+   */
+  this.naclModule_ = naclModule;
+
+  /**
+   * @private {!Array}
+   * @const
+   */
+  this.items_ = items;
+
+  /**
+   * @private {!unpacker.types.CompressorId}
+   * @const
+   */
+  this.compressorId_ = unpacker.Compressor.compressorIdCounter++;
+
+  /**
+   * @private {string}
+   * @const
+   */
+  this.archiveName_ = this.getArchiveName_();
+
+  /**
+   * The counter used to assign a unique id to each entry.
+   * @type {number}
+   */
+  this.entryIdCounter_ = 1;
+
+  /**
+   * The set of entry ids waiting for metadata from FileSystem API.
+   * These requests needs to be tracked here to tell whether all pack process
+   * has finished or not.
+   * @type {!Set}
+   */
+  this.metadataRequestsInProgress_ = new Set();
+
+  /**
+   * The queue containing entry ids that have already obtained metadata from
+   * FileSystem API and are waiting to be added into archive.
+   * @type {!Array}
+   */
+  this.pendingAddToArchiveRequests_ = [];
+
+  /**
+   * The id of the entry that is being compressed and written into archive.
+   * Note that packing of each entry should be done one by one unlike
+   * unpacking. Thus, at most one entry is processed at once.
+   * @type {!unpacker.types.EntryId}
+   */
+  this.entryIdInProgress_ = 0;
+
+  /**
+   * Map from entry ids to entries.
+   * @const {!Object<!unpacker.types.EntryId, !FileEntry|!DirectoryEntry>}
+   */
+  this.entries_ = {};
+
+  /**
+   * Map from entry ids to its metadata.
+   * @const {!Object<!unpacker.types.EntryId, !Metadata>}
+   */
+  this.metadata_ = {};
+
+  /**
+   * The offset from which the entry in progress should be read.
+   * @type {number}
+   */
+  this.offset_ = 0;
+};
+
+/**
+ * The counter which is assigned and incremented every time a new compressor
+ * instance is created.
+ * @type {number}
+ */
+unpacker.Compressor.compressorIdCounter = 1;
+
+/**
+ * The queue containing compressor ids that wait for foreground page to be
+ * loaded. Once this extension becomes a component extension, we don't need to
+ * create an archive file on the foreground page and this also gets unnecessary.
+ * @type {!Array}
+ */
+unpacker.Compressor.CompressorIdQueue = [];
+
+/**
+ * The default archive name.
+ * @type {string}
+ */
+unpacker.Compressor.DEFAULT_ARCHIVE_NAME = 'Archive.zip';
+
+/**
+ * The getter function for compressor id.
+ * @return {!unpacker.types.CompressorId}
+ */
+unpacker.Compressor.prototype.getCompressorId = function() {
+  return this.compressorId_;
+};
+
+/**
+ * Returns the archive file name.
+ * @private
+ * @return {string}
+ */
+unpacker.Compressor.prototype.getArchiveName_ = function() {
+  // When multiple entries are selected.
+  if (this.items_.length !== 1)
+    return unpacker.Compressor.DEFAULT_ARCHIVE_NAME;
+
+  var name = this.items_[0].entry.name
+  var idx = name.lastIndexOf('.');
+  // When the name does not have extension.
+  // TODO(takise): This converts file.tar.gz to file.tar.zip.
+  if (idx === -1)
+    return name + '.zip';
+  // When the name has extension.
+  return name.substring(0, idx) + '.zip';
+};
+
+/**
+ * Starts actual compressing process.
+ * Creates an archive file and requests libarchive to create an archive object.
+ * @param {function(!unpacker.types.CompressorId)} onSuccess
+ * @param {function(!unpacker.types.CompressorId)} onError
+ */
+unpacker.Compressor.prototype.compress = function(onSuccess, onError) {
+  this.onSuccess_ = onSuccess;
+  this.onError_ = onError;
+
+  this.getArchiveFile_();
+};
+
+/**
+ * Gets an archive file with write permission. Currently, this extension does
+ * not have permission to create files from the background page. Thus, this
+ * function first creates a foreground page and then creates an archive file in
+ * it. Once this extension becomes a component extension, this process will be
+ * simpler.
+ * @private
+ */
+unpacker.Compressor.prototype.getArchiveFile_ = function() {
+  // If the foreground page already exists, create an archive file.
+  if (this.createArchiveFileForeground_) {
+    this.createArchiveFileForeground_(this.compressorId_);
+  } else {
+    // If the foreground page does not exist, push the id of this compressor to
+    // the queue so that we can resume later and create the foreground page.
+    // We need this queue because multiple compressors can wait for the
+    // foreground page to be loaded.
+    var queue = unpacker.Compressor.CompressorIdQueue;
+    queue.push(this.compressorId_);
+    if (queue.length === 1) {
+      chrome.app.window.create('../html/compressor.html', {hidden: true});
+    }
+  }
+};
+
+/**
+ * Sends an create archive request to NaCL.
+ * @private
+ */
+unpacker.Compressor.prototype.sendCreateArchiveRequest_ = function() {
+  var request = unpacker.request.createCreateArchiveRequest(
+      this.compressorId_);
+  this.naclModule_.postMessage(request);
+}
+
+/**
+ * A handler of create archive done response.
+ * Enumerates entries and requests FileSystem API for their metadata.
+ * @private
+ */
+unpacker.Compressor.prototype.createArchiveDone_ = function() {
+  this.items_.forEach(function(item) {
+    this.getEntryMetadata_(item.entry);
+  }.bind(this));
+}
+
+/**
+ * Gets metadata of a file or directory.
+ * @param {!FileEntry|!DirectoryEntry} entry FileEntry or DirectoryEntry.
+ * @private
+ */
+unpacker.Compressor.prototype.getEntryMetadata_ = function(entry) {
+  if (entry.isFile)
+    this.getSingleMetadata_(entry);
+  else
+    this.getDirectoryEntryMetadata_(/** @type {!DirectoryEntry} */ (entry));
+}
+
+/**
+ * Requests metadata of an entry non-recursively.
+ * @param {!FileEntry|!DirectoryEntry} entry FileEntry or DirectoryEntry.
+ * @private
+ */
+unpacker.Compressor.prototype.getSingleMetadata_ = function(entry) {
+  var entryId = this.entryIdCounter_++;
+  this.metadataRequestsInProgress_.add(entryId);
+  this.entries_[entryId] = entry;
+
+  entry.getMetadata(function(metadata) {
+    this.metadataRequestsInProgress_.delete(entryId);
+    this.pendingAddToArchiveRequests_.push(entryId);
+    this.metadata_[entryId] = metadata;
+    this.sendAddToArchiveRequest_();
+  }.bind(this), function(error) {
+    console.error('Failed to get metadata: ' +
+        error.message + '.');
+    this.onError_(this.compressorId_);
+  }.bind(this));
+}
+
+/**
+ * Requests metadata of an entry recursively.
+ * @param {!DirectoryEntry} dir DirectoryEntry.
+ * @private
+ */
+unpacker.Compressor.prototype.getDirectoryEntryMetadata_ = function(dir) {
+
+  // Read entries in dir and call getEntryMetadata_ for them recursively.
+  var dirReader = dir.createReader();
+
+  // Recursive function
+  var getEntries = function() {
+    dirReader.readEntries(function(results) {
+      // ReadEntries must be called until it returns nothing, because
+      // it does not necessarily return all entries in the directory.
+      if (results.length) {
+        results.forEach(this.getEntryMetadata_.bind(this));
+        getEntries();
+      }
+    }.bind(this), function(error) {
+      console.error('Failed to get directory entries: ' +
+          error.message + '.');
+      this.onError_(this.compressorId_);
+    }.bind(this));
+  }.bind(this);
+
+  getEntries();
+
+  // Get the metadata of this dir itself.
+  this.getSingleMetadata_(dir);
+}
+
+/**
+ * Pops an entry from the queue and adds it to the archive.
+ * If another entry is in progress, this function does nothing. If there is no
+ * entry in the queue, it shifts to close archive process. Otherwise, this sends
+ * an add to archive request for a popped entry with its metadata to libarchive.
+ * @private
+ */
+unpacker.Compressor.prototype.sendAddToArchiveRequest_ = function() {
+  // Another process is in progress.
+  if (this.entryIdInProgress_ != 0)
+    return;
+
+  // All entries have already been archived.
+  if (this.pendingAddToArchiveRequests_.length === 0) {
+    if (this.metadataRequestsInProgress_.size === 0)
+      this.sendCloseArchiveRequest(false /* hasError */);
+    return;
+  }
+
+  var entryId = this.pendingAddToArchiveRequests_.shift();
+  this.entryIdInProgress_ = entryId;
+
+  // Convert the absolute path on the virtual filesystem to a relative path from
+  // the archive root by removing the leading '/' if exists.
+  var fullPath = this.entries_[entryId].fullPath;
+  if (fullPath.length && fullPath[0] == '/')
+    fullPath = fullPath.substring(1);
+
+  // Modification time is sent as string in a format: 'mm/dd/yy hh:mm:ss'.
+  var mt = this.metadata_[entryId].modificationTime;
+  var formattedTime = (mt.getMonth() + 1) + '/' + mt.getDate() + '/' +
+                      mt.getFullYear() + ' ' + mt.getHours() + ':' +
+                      mt.getMinutes() + ':' + mt.getSeconds();
+
+  var request = unpacker.request.createAddToArchiveRequest(
+      this.compressorId_, entryId, fullPath,
+      this.metadata_[entryId].size, formattedTime,
+      this.entries_[entryId].isDirectory);
+  this.naclModule_.postMessage(request);
+}
+
+/**
+ * Sends a close archive request to libarchive. libarchive writes metadata of
+ * the archive itself on the archive and releases objects obtainted in the
+ * packing process.
+ */
+unpacker.Compressor.prototype.sendCloseArchiveRequest = function(hasError) {
+  var request = unpacker.request.createCloseArchiveRequest(
+      this.compressorId_, hasError);
+  this.naclModule_.postMessage(request);
+}
+
+/**
+ * Sends a read file chunk done response.
+ * @param {number} length The number of bytes read from the entry.
+ * @param {!ArrayBuffer} buffer A buffer containing the data that was read.
+ * @private
+ */
+unpacker.Compressor.prototype.sendReadFileChunkDone_ =
+    function(length, buffer) {
+  var request = unpacker.request.createReadFileChunkDoneResponse(
+      this.compressorId_, length, buffer);
+  this.naclModule_.postMessage(request);
+}
+
+/**
+ * A handler of read file chunk messages.
+ * Reads 'length' bytes from the entry currently in process.
+ * @param {!Object} data
+ * @private
+ */
+unpacker.Compressor.prototype.onReadFileChunk_ = function(data) {
+  var entryId = this.entryIdInProgress_;
+  var entry = this.entries_[entryId];
+  var length = Number(data[unpacker.request.Key.LENGTH]);
+
+  // A function to create a reader and read bytes.
+  var readFileChunk = function() {
+    var file = this.file_.slice(this.offset_, this.offset_ + length);
+    var reader = new FileReader();
+
+    reader.onloadend = function(event) {
+      var buffer = event.target.result;
+
+      // The buffer must have 'length' bytes because the byte length which can
+      // be read from the file is already calculated on NaCL side.
+      if (buffer.byteLength !== length) {
+        console.error('Tried to read chunk with length ' + length +
+            ', but byte with length ' + buffer.byteLength + ' was returned.');
+
+        // If the first argument(length) is negative, it means that an error
+        // occurred in reading a chunk.
+        this.sendReadFileChunkDone_(-1, buffer);
+        this.onError_(this.compressorId_);
+        return;
+      }
+
+      this.offset_ += length;
+      this.sendReadFileChunkDone_(length, buffer);
+    }.bind(this);
+
+    reader.onerror = function(event) {
+      console.error('Failed to read file chunk. Name: ' + file.name +
+          ', offset: ' + this.offset_ + ', length: ' + length + '.');
+
+      // If the first argument(length) is negative, it means that an error
+      // occurred in reading a chunk.
+      this.sendReadFileChunkDone_(-1, new ArrayBuffer(0));
+      this.onError_(this.compressorId_);
+    }
+
+    reader.readAsArrayBuffer(file);
+  }.bind(this);
+
+  // When the entry is read for the first time.
+  if (!this.file_) {
+    entry.file(function(file) {
+      this.file_ = file;
+      readFileChunk();
+    }.bind(this));
+    return;
+  }
+
+  // From the second time onward.
+  readFileChunk();
+}
+
+/**
+ * A handler of write chunk requests.
+ * Writes the data in the given buffer onto the archive file.
+ * @param {!Object} data
+ * @private
+ */
+unpacker.Compressor.prototype.onWriteChunk_ = function(data) {
+  var length = Number(data[unpacker.request.Key.LENGTH]);
+  var buffer = data[unpacker.request.Key.CHUNK_BUFFER];
+  this.writeChunk_(length, buffer, this.sendWriteChunkDone_.bind(this));
+}
+
+/**
+ * Writes buffer into the archive file (window.archiveFileEntry).
+ * @param {number} length The number of bytes in the buffer to write.
+ * @param {!ArrayBuffer} buffer The buffer to write in the archive.
+ * @param {function(number)} callback Callback to execute at the end of the
+ *     function. This function has one parameter: length, which represents the
+ *     length of bytes written on to the archive. If writing a chunk fails,
+ *     a negative value must be assigned to this argument.
+ * @private
+ */
+unpacker.Compressor.prototype.writeChunk_ = function(length, buffer,
+    callback) {
+  // TODO(takise): Use the same instance of FileWriter over multiple calls of
+  // this function instead of creating new ones.
+  this.archiveFileEntry_.createWriter(function(fileWriter) {
+    fileWriter.onwriteend = function(event) {
+      callback(length);
+    };
+
+    fileWriter.onerror = function(event) {
+      console.error('Failed to write chunk to ' + this.archiveFileEntry_ + '.');
+
+      // If the first argument(length) is negative, it means that an error
+      // occurred in writing a chunk.
+      callback(-1 /* length */);
+      this.onError_(this.compressorId_);
+    };
+
+    // Create a new Blob and append it to the archive file.
+    var blob = new Blob([buffer], {});
+    fileWriter.seek(fileWriter.length);
+    fileWriter.write(blob);
+  }, function(event) {
+    console.error('Failed to create writer for ' + this.archiveFileEntry_ +
+        '.');
+    this.onError_(this.compressorId_);
+  });
+};
+
+/**
+ * Sends a write chunk done response.
+ * @param {number} length The number of bytes written onto the entry.
+ * @private
+ */
+unpacker.Compressor.prototype.sendWriteChunkDone_ = function(length) {
+  var request = unpacker.request.createWriteChunkDoneResponse(
+      this.compressorId_, length);
+  this.naclModule_.postMessage(request);
+}
+
+/**
+ * A handler of add to archive done responses.
+ * Resets information on the current entry and starts processing another entry.
+ * @private
+ */
+unpacker.Compressor.prototype.onAddToArchiveDone_ = function() {
+  // Reset information on the current entry.
+  this.entryIdInProgress_ = 0;
+  this.file_ = null;
+  this.offset_ = 0;
+
+  // Start processing another entry.
+  this.sendAddToArchiveRequest_();
+}
+
+/**
+ * A handler of close archive responses.
+ * Receiving this response means the entire packing process has finished.
+ * @private
+ */
+unpacker.Compressor.prototype.onCloseArchiveDone_ = function() {
+  this.onSuccess_(this.compressorId_);
+}
+
+/**
+ * Processes messages from NaCl module.
+ * @param {!Object} data The data contained in the message from NaCl. Its
+ *     types depend on the operation of the request.
+ * @param {!unpacker.request.Operation} operation An operation from request.js.
+ */
+unpacker.Compressor.prototype.processMessage = function(data, operation) {
+  switch (operation) {
+    case unpacker.request.Operation.CREATE_ARCHIVE_DONE:
+      this.createArchiveDone_();
+      break;
+
+    case unpacker.request.Operation.READ_FILE_CHUNK:
+      this.onReadFileChunk_(data);
+      break;
+
+    case unpacker.request.Operation.WRITE_CHUNK:
+      this.onWriteChunk_(data);
+      break;
+
+    case unpacker.request.Operation.ADD_TO_ARCHIVE_DONE:
+      this.onAddToArchiveDone_();
+      break;
+
+    case unpacker.request.Operation.CLOSE_ARCHIVE_DONE:
+      this.onCloseArchiveDone_();
+      break;
+
+    case unpacker.request.Operation.COMPRESSOR_ERROR:
+      console.error('Compressor error for compressor id ' + this.compressorId_ +
+       ': ' + data[unpacker.request.Key.ERROR]);  // The error contains
+                                                  // the '.' at the end.
+      this.onError_(this.compressorId_);
+      break;
+
+    default:
+      console.error('Invalid NaCl operation: ' + operation + '.');
+      this.onError_(this.compressorId_);
+  }
+};
diff --git a/unpacker/js/request.js b/unpacker/js/request.js
index 969987f..b0dcb00 100644
--- a/unpacker/js/request.js
+++ b/unpacker/js/request.js
@@ -16,26 +16,42 @@
    * @enum {string}
    */
   Key: {
-    // Mandatory keys for all requests.
+    // Mandatory keys for all unpacking operations.
     OPERATION: 'operation',  // Should be a unpacker.request.Operation.
     FILE_SYSTEM_ID: 'file_system_id',  // Should be a string.
     REQUEST_ID: 'request_id',          // Should be a string.
 
-    // Optional keys depending on request operation.
-    ERROR: 'error',                // Should be a string.
+    // Optional keys unique to unpacking operations.
     METADATA: 'metadata',          // Should be a dictionary.
     ARCHIVE_SIZE: 'archive_size',  // Should be a string as only int is
                                    // supported by pp::Var on C++.
+    INDEX: 'index',        // Should be a string. Same reason as ARCHIVE_SIZE.
+    ENCODING: 'encoding',  // Should be a string.
+    OPEN_REQUEST_ID: 'open_request_id',     // Should be a string, just like
+                                            // REQUEST_ID.
+    READ_FILE_DATA: 'read_file_data',       // Should be an ArrayBuffer.
+    HAS_MORE_DATA: 'has_more_data',         // Should be a boolean.
+    PASSPHRASE: 'passphrase',               // Should be a string.
+
+    // Mandatory keys for all packing operations.
+    COMPRESSOR_ID: 'compressor_id',         // Should be an int.
+
+    // Optional keys unique to packing operations.
+    ENTRY_ID: 'entry_id',                   // Should be an int.
+    PATHNAME: 'pathname',                   // should be a string.
+    FILE_SIZE: 'file_size',                 // should be a string. Same reason
+                                            // as ARCHIVE_SIZE.
+    IS_DIRECTORY: 'is_directory',           // should be a boolean.
+    MODIFICATION_TIME: 'modification_time', // should be a string.
+                                            // (mm/dd/yy h:m:s)
+    HAS_ERROR: 'has_error',                 // Should be a boolean Sent from JS
+                                            // to NaCL.
+
+    // Optional keys used for both packing and unpacking operations.
+    ERROR: 'error',                // Should be a string.
     CHUNK_BUFFER: 'chunk_buffer',  // Should be an ArrayBuffer.
     OFFSET: 'offset',      // Should be a string. Same reason as ARCHIVE_SIZE.
     LENGTH: 'length',      // Should be a string. Same reason as ARCHIVE_SIZE.
-    INDEX: 'index',        // Should be a string. Same reason as ARCHIVE_SIZE.
-    ENCODING: 'encoding',  // Should be a string.
-    OPEN_REQUEST_ID: 'open_request_id',  // Should be a string, just like
-                                         // REQUEST_ID.
-    READ_FILE_DATA: 'read_file_data',    // Should be an ArrayBuffer.
-    HAS_MORE_DATA: 'has_more_data',      // Should be a boolean.
-    PASSPHRASE: 'passphrase',            // Should be a string.
     SRC_FILE: 'src_file',                // Should be a string.
     SRC_LINE: 'src_line',                // Should be a int.
     SRC_FUNC: 'src_func',                // Should be a string.
@@ -45,7 +61,9 @@
   /**
    * Defines request operations. These operation should be the same as the
    * operations on the NaCL side. FILE_SYSTEM_ID and REQUEST_ID are mandatory
-   * for all requests.
+   * for all unpack requests, while COMPRESSOR_ID is required for all pack
+   * requests. All the values of unpacking operations must be smaller than any
+   * packing operation (except errors).
    * @enum {number}
    */
   Operation: {
@@ -66,7 +84,34 @@
     READ_FILE_DONE: 14,
     CONSOLE_LOG: 15,
     CONSOLE_DEBUG: 16,
-    FILE_SYSTEM_ERROR: -1
+    CREATE_ARCHIVE: 17,
+    CREATE_ARCHIVE_DONE: 18,
+    ADD_TO_ARCHIVE: 19,
+    ADD_TO_ARCHIVE_DONE: 20,
+    READ_FILE_CHUNK: 21,
+    READ_FILE_CHUNK_DONE: 22,
+    WRITE_CHUNK: 23,
+    WRITE_CHUNK_DONE: 24,
+    CLOSE_ARCHIVE: 25,
+    CLOSE_ARCHIVE_DONE: 26,
+    FILE_SYSTEM_ERROR: -1,
+    COMPRESSOR_ERROR: -2
+  },
+
+  /**
+  * Operations greater than or equal to this value are for packing.
+  * @const {number}
+  */
+  MINIMUM_PACK_REQUEST_VALUE: 17,
+
+  /**
+  * Return true if the given operation is related to packing.
+  * @param {!unpacker.request.Operation} operation
+  * @return {boolean}
+  */
+  isPackRequest: function(operation) {
+    return unpacker.request.MINIMUM_PACK_REQUEST_VALUE <= operation ||
+           operation == unpacker.request.Operation.COMPRESSOR_ERROR;
   },
 
   /**
@@ -230,5 +275,90 @@
     readFileRequest[unpacker.request.Key.OFFSET] = offset.toString();
     readFileRequest[unpacker.request.Key.LENGTH] = length.toString();
     return readFileRequest;
+  },
+
+  /**
+   * Creates a create archive request for compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @return {!Object} A create archive request.
+   */
+  createCreateArchiveRequest: function(compressorId) {
+    var request = {};
+    request[unpacker.request.Key.OPERATION] =
+        unpacker.request.Operation.CREATE_ARCHIVE;
+    request[unpacker.request.Key.COMPRESSOR_ID] = compressorId;
+    return request;
+  },
+
+  /**
+   * Creates an add to archive request for compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @param {!unpacker.types.EntryId} entryId
+   * @param {string} pathname The relative path of the entry.
+   * @param {number} fileSize The size of the entry.
+   * @param {string} modificationTime The modification time of the entry.
+   * @param {boolean} isDirectory Whether the entry is a directory or not.
+   * @return {!Object} An add to archive request.
+   */
+  createAddToArchiveRequest: function(compressorId, entryId, pathname,
+                                      fileSize, modificationTime, isDirectory) {
+    var request = {};
+    request[unpacker.request.Key.OPERATION] =
+        unpacker.request.Operation.ADD_TO_ARCHIVE;
+    request[unpacker.request.Key.COMPRESSOR_ID] = compressorId;
+    request[unpacker.request.Key.ENTRY_ID] = entryId;
+    request[unpacker.request.Key.PATHNAME] = pathname.toString();
+    request[unpacker.request.Key.FILE_SIZE] = fileSize.toString();
+    request[unpacker.request.Key.MODIFICATION_TIME] =
+        modificationTime.toString();
+    request[unpacker.request.Key.IS_DIRECTORY] = isDirectory;
+    return request;
+  },
+
+  /**
+   * Creates a read file chunk response for compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @param {number} length The number of bytes read from the entry.
+   * @param {!ArrayBuffer} buffer A buffer containing the data that was read.
+   * @return {!Object} A read file chunk done response.
+   */
+  createReadFileChunkDoneResponse: function(compressorId, length, buffer) {
+    var response = {};
+    response[unpacker.request.Key.OPERATION] =
+        unpacker.request.Operation.READ_FILE_CHUNK_DONE;
+    response[unpacker.request.Key.COMPRESSOR_ID] = compressorId;
+    response[unpacker.request.Key.LENGTH] = length.toString();
+    response[unpacker.request.Key.CHUNK_BUFFER] = buffer;
+    return response;
+  },
+
+  /**
+   * Creates a write chunk done response for compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @param {number} length The number of bytes written onto the archive file.
+   * @return {!Object} A write chunk done response.
+   */
+  createWriteChunkDoneResponse: function(compressorId, length) {
+    var response = {};
+    response[unpacker.request.Key.OPERATION] =
+        unpacker.request.Operation.WRITE_CHUNK_DONE;
+    response[unpacker.request.Key.COMPRESSOR_ID] = compressorId;
+    response[unpacker.request.Key.LENGTH] = length.toString();
+    return response;
+  },
+
+  /**
+   * Creates a close archive request for compressor.
+   * @param {!unpacker.types.CompressorId} compressorId
+   * @param {boolean} hasError True if some error occurred.
+   * @return {!Object} A close archive request.
+   */
+  createCloseArchiveRequest: function(compressorId, hasError) {
+    var request = {};
+    request[unpacker.request.Key.OPERATION] =
+        unpacker.request.Operation.CLOSE_ARCHIVE;
+    request[unpacker.request.Key.COMPRESSOR_ID] = compressorId;
+    request[unpacker.request.Key.HAS_ERROR] = hasError;
+    return request;
   }
 };
diff --git a/unpacker/js/types.js b/unpacker/js/types.js
index e899311..360cc0d 100644
--- a/unpacker/js/types.js
+++ b/unpacker/js/types.js
@@ -16,6 +16,12 @@
 /** @typedef {number} */
 unpacker.types.RequestId;
 
+/** @typedef {number} */
+unpacker.types.CompressorId;
+
+/** @typedef {number} */
+unpacker.types.EntryId;
+
 /**
  * @see
  * https://developer.chrome.com/apps/fileSystemProvider#event-onUnmountRequested
diff --git a/unpacker/manifest.json b/unpacker/manifest.json
index 1dcb042..b97e392 100644
--- a/unpacker/manifest.json
+++ b/unpacker/manifest.json
@@ -7,20 +7,26 @@
   "default_locale": "en",
   "display_in_launcher": false,
   "permissions": [
+    "alwaysOnTopWindows",
     "fileSystemProvider",
-    {"fileSystem": ["retainEntries"]},
+    {"fileSystem": ["retainEntries", "write", "directory"]},
     "notifications",
-    "storage",
-    "alwaysOnTopWindows"
+    "storage"
   ],
   "file_system_provider_capabilities": {
     "multipleMounts": true,
     "source": "file"
   },
   "file_handlers": {
-    "zip": {
-      "types": ["application/zip"],
-      "extensions": ["zip"]
+    "add": {
+      "types":["application/zip"],
+      "extensions": ["zip"],
+      "verb": "add_to"
+    },
+    "pack": {
+      "types": ["*"],
+      "include_directories": true,
+      "verb": "pack_with"
     }
   },
   "icons": {
@@ -36,6 +42,7 @@
         "js/unpacker.js",
         "js/app.js",
         "js/background.js",
+        "js/compressor.js",
         "js/decompressor.js",
         "js/passphrase-manager.js",
         "js/request.js",