diff --git a/lib/cacophony/message.ex b/lib/cacophony/message.ex
index bd621d9..8548902 100644
--- a/lib/cacophony/message.ex
+++ b/lib/cacophony/message.ex
@@ -1,324 +1,339 @@
 defmodule Cacophony.Message do
   @moduledoc """
   Encoding and decoding of ASN.1 LDAP messages to high-level types.
 
   ## Known Limitations
 
   * Cannot interpret ExtendedResponse objects.  Extended response objects
     are encapsulated as an ExtendedResponse type.  Matching up OIDs is up
     to you.
   """
 
   require Logger
 
   defmodule BindRequest do
     @moduledoc "A type representing a BindRequest."
 
     defstruct [:id, :version, :dn, :credentials]
   end
 
   defmodule BindResponse do
     @moduledoc "A type representing a BindResponse."
 
     defstruct [
       :id,
       :result_code,
       :matched_dn,
       {:diagnostic_message, ""},
       {:referral, :asn1_NOVALUE},
       {:server_sasl_creds, :asn1_NOVALUE}
     ]
   end
 
   defmodule UnbindRequest do
     @moduledoc "A type representing an UnbindRequest."
 
     defstruct [:id]
   end
 
   @whoami_oid "1.3.6.1.4.1.4203.1.11.3"
 
   defmodule WhoAmIRequest do
     @moduledoc "A type representing a Who Am I request (RFC 4532)."
 
     defstruct [:id]
   end
 
   defmodule WhoAmIResponse do
     @moduledoc "A type representing a Who Am I response (RFC 4532)."
 
     defstruct [
       :id,
       {:result_code, :success},
       {:matched_dn, ""},
       {:diag_msg, ""},
       {:referral, :asn1_NOVALUE},
       :authzid
     ]
   end
 
   defmodule ExtendedRequest do
     @moduledoc "A catch-all type for any extended request."
 
     defstruct [:id, :request_name, :request_value]
   end
 
   defmodule ExtendedResponse do
     @moduledoc "A catch-all type for any extended responses."
 
     defstruct [
       :id,
       {:result_code, :success},
       {:matched_dn, ""},
       {:diag_msg, ""},
       {:referral, :asn1_NOVALUE},
       :response_name,
       :response_value
     ]
   end
 
   defmodule SearchRequest do
     @moduledoc "A type representing a Search request."
 
     defstruct [
       :id,
       :base,
       {:scope, :baseObject},
       {:deref_aliases, :neverDerefAliases},
       :size_limit,
       :time_limit,
       :types_only,
       :filter,
       :attributes
     ]
   end
 
   defmodule SearchResultDone do
     @moduledoc "A type representing the end of a set of SearchResultEntry items."
 
     defstruct [
       :id,
       {:result_code, :success},
       {:matched_dn, ""},
       {:diag_msg, ""},
       {:referral, :asn1_NOVALUE}
     ]
   end
 
   defmodule PartialAttribute do
     @moduledoc "A type representing a PartialAttribute: key to 0 or more values."
 
     defstruct [
       :name,
       {:values, []}
     ]
   end
 
   defmodule SearchResultEntry do
     @moduledoc "A type representing a SearchResultEntry: DN to 0 or more PartialAttribute items."
 
     defstruct [
       :id,
       :object_name,
       {:attributes, []}
     ]
   end
 
+  defmodule AbandonRequest do
+    @moduledoc "A type representing an AbandonRequest: an ID of a request to be abandoned."
+
+    defstruct [
+      :id,
+      :abandon_id
+    ]
+  end
+
+  def interpret(:abandonRequest, abandon_id, message_id),
+    do: {:ok, %AbandonRequest{id: message_id, abandon_id: abandon_id}}
+
   def interpret(:bindRequest, {:BindRequest, vsn, dn, creds}, message_id),
     do: {:ok, %BindRequest{id: message_id, version: vsn, dn: dn, credentials: creds}}
 
   def interpret(
         :bindResponse,
         {:BindResponse, result, matched_dn, diag_msg, referral, sasl_creds},
         message_id
       ),
       do:
         {:ok,
          %BindResponse{
            id: message_id,
            result_code: result,
            matched_dn: matched_dn,
            diagnostic_message: diag_msg,
            referral: referral,
            server_sasl_creds: sasl_creds
          }}
 
   def interpret(:unbindRequest, _, message_id), do: {:ok, %UnbindRequest{id: message_id}}
 
   def interpret(:extendedReq, {:ExtendedRequest, @whoami_oid, :asn1_NOVALUE}, message_id),
     do: {:ok, %WhoAmIRequest{id: message_id}}
 
   def interpret(:extendedReq, {:ExtendedRequest, request_name, request_value}, message_id),
     do:
       {:ok,
        %ExtendedRequest{id: message_id, request_name: request_name, request_value: request_value}}
 
   def interpret(
         :extendedResp,
         {:ExtendedResponse, result, matched_dn, diag_msg, referral, response_name,
          response_value},
         message_id
       ),
       do:
         {:ok,
          %ExtendedResponse{
            id: message_id,
            result_code: result,
            matched_dn: matched_dn,
            diag_msg: diag_msg,
            referral: referral,
            response_name: response_name,
            response_value: response_value
          }}
 
   def interpret(
         :searchRequest,
         {:SearchRequest, base, scope, deref_aliases, size_limit, time_limit, types_only, filter,
          attributes},
         message_id
       ),
       do:
         {:ok,
          %SearchRequest{
            id: message_id,
            base: base,
            scope: scope,
            deref_aliases: deref_aliases,
            size_limit: size_limit,
            time_limit: time_limit,
            types_only: types_only,
            filter: filter,
            attributes: attributes
          }}
 
   def interpret(
         :searchResDone,
         {:LDAPResult, result, matched_dn, diag_msg, referral},
         message_id
       ),
       do:
         {:ok,
          %SearchResultDone{
            id: message_id,
            result_code: result,
            matched_dn: matched_dn,
            diag_msg: diag_msg,
            referral: referral
          }}
 
   def interpret(:searchResEntry, {:SearchResultEntry, matched_dn, attributes}, message_id),
     do:
       {:ok,
        %SearchResultEntry{
          id: message_id,
          object_name: matched_dn,
          attributes:
            Enum.map(
              attributes,
              fn {:PartialAttribute, key, values} ->
                %PartialAttribute{name: key, values: values}
              end
            )
        }}
 
   def decode({:LDAPMessage, message_id, {type, payload}, _}),
     do: interpret(type, payload, message_id)
 
   def decode({:LDAPMessage, message_id, {type, payload}}),
     do: interpret(type, payload, message_id)
 
   def decode(message) when is_binary(message) do
     with {:ok, data} <- :LDAP.decode(:LDAPMessage, message),
          {:ok, struct} <- decode(data) do
       {:ok, struct}
     else
       e -> {:error, e}
     end
   end
 
   def decode(_), do: {:error, :badmatch}
 
   def encode({:asn1, message}),
     do: :LDAP.encode(:LDAPMessage, message)
 
   def encode({:envelope, message_id, payload}),
     do: encode({:asn1, {:LDAPMessage, message_id, payload, :asn1_NOVALUE}})
 
   def encode(%BindRequest{} = msg) do
     payload = {:BindRequest, msg.version, msg.dn, msg.credentials}
     encode({:envelope, msg.id, {:bindRequest, payload}})
   end
 
   def encode(%BindResponse{} = msg) do
     payload =
       {:BindResponse, msg.result_code, msg.matched_dn, msg.diagnostic_message, msg.referral,
        msg.server_sasl_creds}
 
     encode({:envelope, msg.id, {:bindResponse, payload}})
   end
 
   def encode(%UnbindRequest{} = msg),
     do: encode({:envelope, msg.id, {:unbindRequest, :asn1_NOVALUE}})
 
   def encode(%WhoAmIRequest{} = msg),
     do:
       encode({:envelope, msg.id, {:extendedReq, {:ExtendedRequest, @whoami_oid, :asn1_NOVALUE}}})
 
   def encode(%WhoAmIResponse{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:extendedResp,
           {:ExtendedResponse, msg.result_code, msg.matched_dn, msg.diag_msg, msg.referral,
            :asn1_NOVALUE, msg.authzid}}}
       )
 
   def encode(%ExtendedRequest{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:extendedReq, {:ExtendedRequest, msg.request_name, msg.request_value}}}
       )
 
   def encode(%ExtendedResponse{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:extendedResp,
           {:ExtendedResponse, msg.result_code, msg.matched_dn, msg.diag_msg, msg.referral,
            msg.response_name, msg.response_value}}}
       )
 
   def encode(%SearchRequest{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:searchRequest,
           {:SearchRequest, msg.base, msg.scope, msg.deref_aliases, msg.size_limit, msg.time_limit,
            msg.types_only, msg.filter, msg.attributes}}}
       )
 
   def encode(%SearchResultDone{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:searchResDone,
           {:LDAPResult, msg.result_code, msg.matched_dn, msg.diag_msg, msg.referral}}}
       )
 
   def encode(%SearchResultEntry{} = msg),
     do:
       encode(
         {:envelope, msg.id,
          {:searchResEntry,
           {:SearchResultEntry, msg.object_name,
            Enum.map(
              msg.attributes,
              fn %PartialAttribute{name: name, values: values} ->
                {:PartialAttribute, name, values}
              end
            )}}}
       )
 
+  def encode(%AbandonRequest{} = msg),
+    do: encode({:envelope, msg.id, {:abandonRequest, msg.abandon_id}})
+
   def encode(_), do: {:error, :badmatch}
 end
diff --git a/test/cacophony/message_test.exs b/test/cacophony/message_test.exs
index 1e530a4..2fb0b2e 100644
--- a/test/cacophony/message_test.exs
+++ b/test/cacophony/message_test.exs
@@ -1,231 +1,254 @@
 defmodule Cacophony.MessageTest do
   use ExUnit.Case
 
   @whoami_oid "1.3.6.1.4.1.4203.1.11.3"
   @cirno_oid "9.99.999.9999.99999.999999"
 
   describe "decode/1" do
     test "successfully decodes a bindRequest" do
       bindreq = {:BindRequest, 3, "cn=foo", {:simple, "bar"}}
       message = {:LDAPMessage, 1, {:bindRequest, bindreq}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.BindRequest{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
       assert msg.dn == "cn=foo"
       assert msg.credentials == {:simple, "bar"}
     end
 
     test "successfully decodes a bindResponse" do
       bindresp = {:BindResponse, :success, "cn=foo", "", :asn1_NOVALUE, :asn1_NOVALUE}
       message = {:LDAPMessage, 1, {:bindResponse, bindresp}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.BindResponse{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
       assert msg.matched_dn == "cn=foo"
       assert msg.result_code == :success
     end
 
     test "successfully decodes an unbindRequest" do
       message = {:LDAPMessage, 1, {:unbindRequest, :asn1_NOVALUE}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.UnbindRequest{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
     end
 
     test "successfully decodes an extended whoAmIRequest" do
       message =
         {:LDAPMessage, 1, {:extendedReq, {:ExtendedRequest, @whoami_oid, :asn1_NOVALUE}},
          :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.WhoAmIRequest{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
     end
 
     test "successfully decodes an unknown extendedRequest" do
       message =
         {:LDAPMessage, 1, {:extendedReq, {:ExtendedRequest, @cirno_oid, :asn1_NOVALUE}},
          :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.ExtendedRequest{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
     end
 
     test "successfully decodes a searchRequest" do
       searchreq =
         {:SearchRequest, "", :baseObject, :neverDerefAliases, 0, 0, false,
          {:present, "objectclass"}, ["supportedSASLMechanisms"]}
 
       message = {:LDAPMessage, 1, {:searchRequest, searchreq}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchRequest{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
       assert msg.attributes == ["supportedSASLMechanisms"]
       assert msg.filter == {:present, "objectclass"}
       assert msg.base == ""
       assert msg.scope == :baseObject
       assert msg.deref_aliases == :neverDerefAliases
       assert msg.time_limit == 0
       assert msg.size_limit == 0
       refute msg.types_only
     end
 
     test "successfully decodes a searchResDone" do
       searchresdone = {:SearchResultDone, :success, "", "", :asn1_NOVALUE}
       message = {:LDAPMessage, 1, {:searchResDone, searchresdone}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchResultDone{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
     end
 
     test "successfully decodes a searchResEntry" do
       searchresentry =
         {:SearchResultEntry, "cn=test", [{:PartialAttribute, "key", ["value1", "value2"]}]}
 
       message = {:LDAPMessage, 1, {:searchResEntry, searchresentry}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchResultEntry{} = msg} = Cacophony.Message.decode(bin)
 
       assert msg.id == 1
       assert msg.object_name == "cn=test"
 
       assert msg.attributes == [
                %Cacophony.Message.PartialAttribute{name: "key", values: ["value1", "value2"]}
              ]
     end
+
+    test "successfully decodes an abandonRequest" do
+      message = {:LDAPMessage, 2, {:abandonRequest, 1}, :asn1_NOVALUE}
+
+      {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
+
+      {:ok, %Cacophony.Message.AbandonRequest{} = msg} = Cacophony.Message.decode(bin)
+
+      assert msg.id == 2
+      assert msg.abandon_id == 1
+    end
   end
 
   describe "encode/1" do
     test "successfully encodes a bindRequest" do
       bindreq = {:BindRequest, 3, "cn=foo", {:simple, "bar"}}
       message = {:LDAPMessage, 1, {:bindRequest, bindreq}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.BindRequest{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes a bindResponse" do
       bindresp = {:BindResponse, :success, "cn=foo", "", :asn1_NOVALUE, :asn1_NOVALUE}
       message = {:LDAPMessage, 1, {:bindResponse, bindresp}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.BindResponse{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes an unbindRequest" do
       message = {:LDAPMessage, 1, {:unbindRequest, :asn1_NOVALUE}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.UnbindRequest{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes an extended whoAmIRequest" do
       message =
         {:LDAPMessage, 1, {:extendedReq, {:ExtendedRequest, @whoami_oid, :asn1_NOVALUE}},
          :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.WhoAmIRequest{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes an unknown extendedRequest" do
       message =
         {:LDAPMessage, 1, {:extendedReq, {:ExtendedRequest, @cirno_oid, :asn1_NOVALUE}},
          :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.ExtendedRequest{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes a searchRequest" do
       searchreq =
         {:SearchRequest, "", :baseObject, :neverDerefAliases, 0, 0, false,
          {:present, "objectclass"}, ["supportedSASLMechanisms"]}
 
       message = {:LDAPMessage, 1, {:searchRequest, searchreq}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchRequest{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes a searchResDone" do
       searchresdone = {:SearchResultDone, :success, "", "", :asn1_NOVALUE}
       message = {:LDAPMessage, 1, {:searchResDone, searchresdone}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchResultDone{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
 
     test "successfully encodes a searchResEntry" do
       searchresentry =
         {:SearchResultEntry, "cn=test", [{:PartialAttribute, "key", ["value1", "value2"]}]}
 
       message = {:LDAPMessage, 1, {:searchResEntry, searchresentry}, :asn1_NOVALUE}
 
       {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
 
       {:ok, %Cacophony.Message.SearchResultEntry{} = msg} = Cacophony.Message.decode(bin)
 
       {:ok, other_bin} = Cacophony.Message.encode(msg)
 
       assert bin == other_bin
     end
+
+    test "successfully encodes an abandonRequest" do
+      message = {:LDAPMessage, 2, {:abandonRequest, 1}, :asn1_NOVALUE}
+
+      {:ok, bin} = :LDAP.encode(:LDAPMessage, message)
+
+      {:ok, %Cacophony.Message.AbandonRequest{} = msg} = Cacophony.Message.decode(bin)
+
+      {:ok, other_bin} = Cacophony.Message.encode(msg)
+
+      assert bin == other_bin
+    end
   end
 end