Skip to content

Commit 37eb9ec

Browse files
committed
fixes #3
1 parent d6def01 commit 37eb9ec

File tree

10 files changed

+131
-247
lines changed

10 files changed

+131
-247
lines changed

lib/bson/decoder.ex

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ defmodule BSON.Decoder do
88
map
99
end
1010

11-
def documents(binary),
12-
do: documents(binary, [])
13-
def documents("", acc),
14-
do: Enum.reverse(acc)
11+
def documents(binary), do: documents(binary, [])
12+
def documents("", acc), do: Enum.reverse(acc)
1513
def documents(binary, acc) do
1614
{doc, rest} = document(binary)
1715
documents(rest, [doc|acc])
@@ -169,18 +167,11 @@ defmodule BSON.Decoder do
169167
{string, rest}
170168
end
171169

172-
defp subtype(0x00),
173-
do: :generic
174-
defp subtype(0x01),
175-
do: :function
176-
defp subtype(0x02),
177-
do: :binary_old
178-
defp subtype(0x03),
179-
do: :uuid_old
180-
defp subtype(0x04),
181-
do: :uuid
182-
defp subtype(0x05),
183-
do: :md5
184-
defp subtype(int) when is_integer(int) and int in 0x80..0xFF,
185-
do: int
170+
defp subtype(0x00), do: :generic
171+
defp subtype(0x01), do: :function
172+
defp subtype(0x02), do: :binary_old
173+
defp subtype(0x03), do: :uuid_old
174+
defp subtype(0x04), do: :uuid
175+
defp subtype(0x05), do: :md5
176+
defp subtype(int) when is_integer(int) and int in 0x80..0xFF, do: int
186177
end

lib/mongo/auth/cr.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
defmodule Mongo.Auth.CR do
22
@moduledoc false
3-
import Mongo.MongoDBConnection.Utils
3+
alias Mongo.MongoDBConnection.Utils
44

55
def auth({username, password}, s) do
6-
with {:ok, message} <- command(-2, [getnonce: 1], s),
6+
with {:ok, message} <- Utils.command(-2, [getnonce: 1], s),
77
do: nonce(message, username, password, s)
88
end
99

1010
defp nonce(%{"nonce" => nonce, "ok" => ok}, username, password, s)
1111
when ok == 1 # to support a response that returns 1 or 1.0
1212
do
13-
digest = digest(nonce, username, password)
13+
digest = Utils.digest(nonce, username, password)
1414
command = [authenticate: 1, user: username, nonce: nonce, key: digest]
1515

16-
case command(-3, command, s) do
16+
case Utils.command(-3, command, s) do
1717
{:ok, %{"ok" => ok}} when ok == 1 ->
1818
:ok
1919
{:ok, %{"ok" => 0.0, "errmsg" => reason, "code" => code}} ->

lib/mongo/auth/scram.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
defmodule Mongo.Auth.SCRAM do
22
@moduledoc false
33
import Mongo.BinaryUtils
4-
import Mongo.MongoDBConnection.Utils
54
import Bitwise
65

6+
alias Mongo.MongoDBConnection.Utils
7+
78
def auth({username, password}, s) do
89
# TODO: Wrap and log error
910

@@ -13,11 +14,11 @@ defmodule Mongo.Auth.SCRAM do
1314
message = [saslStart: 1, mechanism: "SCRAM-SHA-1", payload: payload]
1415

1516
result =
16-
with {:ok, %{"ok" => ok} = reply} when ok == 1 <- command(-2, message, s),
17+
with {:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-2, message, s),
1718
{message, signature} = first(reply, first_bare, username, password, nonce),
18-
{:ok, %{"ok" => ok} = reply} when ok == 1 <- command(-3, message, s),
19+
{:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-3, message, s),
1920
message = second(reply, signature),
20-
{:ok, %{"ok" => ok} = reply} when ok == 1 <- command(-4, message, s),
21+
{:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-4, message, s),
2122
do: final(reply)
2223

2324
case result do
@@ -36,7 +37,7 @@ defmodule Mongo.Auth.SCRAM do
3637
server_nonce = params["r"]
3738
salt = params["s"] |> Base.decode64!
3839
iter = params["i"] |> String.to_integer
39-
pass = digest_password(username, password)
40+
pass = Utils.digest_password(username, password)
4041
salted_password = hi(pass, salt, iter)
4142

4243
<<^client_nonce::binary(24), _::binary>> = server_nonce

lib/mongo/auth/x509.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
defmodule Mongo.Auth.X509 do
22
@moduledoc false
3-
import Mongo.MongoDBConnection.Utils
3+
alias Mongo.MongoDBConnection.Utils
44

55
def auth({username, _password}, s) do
66
cmd = [authenticate: 1, user: username, mechanism: "MONGODB-X509"]
7-
with {:ok, _message} <- command(-2, cmd, s) do
7+
with {:ok, _message} <- Utils.command(-2, cmd, s) do
88
:ok
99
else
1010
_error -> {:error, "X509 auth failed"}

lib/mongo/messages.ex

Lines changed: 45 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
defmodule Mongo.Messages do
2-
@moduledoc false
2+
@moduledoc """
3+
This module encodes and decodes the data from and to the mongodb server.
4+
We only support MongoDB >= 3.2 and use op_query with the hack collection "$cmd"
5+
Other op codes are deprecated. Therefore only op_reply and op_query are supported.
6+
"""
37

48
defmacro __using__(_opts) do
59
quote do
@@ -14,21 +18,8 @@ defmodule Mongo.Messages do
1418
import Record
1519
import Mongo.BinaryUtils
1620

17-
@op_update 2001
18-
@op_insert 2002
19-
@op_query 2004
20-
@op_get_more 2005
21-
@op_delete 2006
22-
@op_kill_cursors 2007
23-
24-
@update_flags [
25-
upsert: 0x1,
26-
multi: 0x2
27-
]
28-
29-
@insert_flags [
30-
continue_on_error: 0x1
31-
]
21+
@op_reply 1
22+
@op_query 2004
3223

3324
@query_flags [
3425
tailable_cursor: 0x2,
@@ -40,116 +31,71 @@ defmodule Mongo.Messages do
4031
partial: 0x80
4132
]
4233

43-
@delete_flags [
44-
single: 0x1
45-
]
46-
4734
@header_size 4 * 4
4835

4936
defrecordp :msg_header, [:length, :request_id, :response_to, :op_code]
50-
defrecord :op_update, [:coll, :flags, :query, :update]
51-
defrecord :op_insert, [:flags, :coll, :docs]
5237
defrecord :op_query, [:flags, :coll, :num_skip, :num_return, :query, :select]
53-
defrecord :op_get_more, [:coll, :num_return, :cursor_id]
54-
defrecord :op_delete, [:coll, :flags, :query]
55-
defrecord :op_kill_cursors, [:cursor_ids]
5638
defrecord :op_reply, [:flags, :cursor_id, :from, :num, :docs]
5739

58-
def encode(request_id, op) do
59-
iodata = encode_op(op)
60-
header = msg_header(length: IO.iodata_length(iodata) + @header_size,
61-
request_id: request_id, response_to: 0,
62-
op_code: op_to_code(op))
63-
64-
[encode_header(header)|iodata]
40+
@doc """
41+
Decodes the header from response of a request sent by the mongodb server
42+
"""
43+
def decode_header(iolist) when is_list(iolist) do
44+
case IO.iodata_length(iolist) >= @header_size do
45+
true -> iolist |> IO.iodata_to_binary() |> decode_header()
46+
false -> :error
47+
end
6548
end
49+
def decode_header(<<length::int32, request_id::int32, response_to::int32, op_code::int32, rest::binary>>) do
50+
header = msg_header(length: length - @header_size, request_id: request_id, response_to: response_to, op_code: op_code) ## todo don't subtract header-size here
51+
{:ok, header, rest}
52+
end
53+
def decode_header(_binary), do: :error
6654

55+
@doc """
56+
Decodes the response body of a request sent by the mongodb server
57+
"""
6758
def decode_response(msg_header(length: length) = header, iolist) when is_list(iolist) do
68-
if IO.iodata_length(iolist) >= length,
69-
do: decode_response(header, IO.iodata_to_binary(iolist)),
70-
else: :error
59+
case IO.iodata_length(iolist) >= length do
60+
true -> decode_response(header, IO.iodata_to_binary(iolist))
61+
false -> :error
62+
end
7163
end
72-
def decode_response(msg_header(length: length, response_to: response_to), binary) when byte_size(binary) >= length do
64+
def decode_response(msg_header(length: length, response_to: response_to, op_code: op_code), binary) when byte_size(binary) >= length do
7365
<<response::binary(length), rest::binary>> = binary
74-
{:ok, response_to, decode_reply(response), rest}
75-
end
76-
def decode_response(_header, _binary) do
77-
:error
66+
case op_code do
67+
@op_reply -> {:ok, response_to, decode_reply(response), rest}
68+
_ -> :error
69+
end
7870
end
71+
def decode_response(_header, _binary), do: :error
7972

80-
def decode_header(iolist) when is_list(iolist) do
81-
if IO.iodata_length(iolist) >= @header_size,
82-
do: IO.iodata_to_binary(iolist) |> decode_header,
83-
else: :error
84-
end
85-
def decode_header(<<length::int32, request_id::int32, response_to::int32,
86-
op_code::int32, rest::binary>>) do
87-
header = msg_header(length: length-@header_size, request_id: request_id, response_to: response_to, op_code: op_code)
88-
{:ok, header, rest}
89-
end
90-
def decode_header(_binary) do
91-
:error
73+
@doc """
74+
Decodes a reply message from the response
75+
"""
76+
def decode_reply(<<flags::int32, cursor_id::int64, from::int32, num::int32, rest::binary>>) do
77+
op_reply(flags: flags, cursor_id: cursor_id, from: from, num: num, docs: BSON.Decoder.documents(rest))
9278
end
9379

94-
defp encode_op(op_update(coll: coll, flags: flags, query: query, update: update)) do
95-
[<<0x00::int32>>,
96-
coll,
97-
<<0x00, blit_flags(:update, flags)::int32>>,
98-
query,
99-
update]
80+
def encode(request_id, op_query() = op) do
81+
iodata = encode_op(op)
82+
header = msg_header(length: IO.iodata_length(iodata) + @header_size, request_id: request_id, response_to: 0, op_code: @op_query)
83+
[encode_header(header)|iodata]
10084
end
10185

102-
defp encode_op(op_insert(flags: flags, coll: coll, docs: docs)) do
103-
[<<blit_flags(:insert, flags)::int32>>,
104-
coll,
105-
0x00,
106-
docs]
86+
defp encode_header(msg_header(length: length, request_id: request_id, response_to: response_to, op_code: op_code)) do
87+
<<length::int32, request_id::int32, response_to::int32, op_code::int32>>
10788
end
10889

10990
defp encode_op(op_query(flags: flags, coll: coll, num_skip: num_skip,
11091
num_return: num_return, query: query, select: select)) do
11192
[<<blit_flags(:query, flags)::int32>>,
11293
coll,
11394
<<0x00, num_skip::int32, num_return::int32>>,
114-
query,
95+
BSON.Encoder.document(query),
11596
select]
11697
end
11798

118-
defp encode_op(op_get_more(coll: coll, num_return: num_return, cursor_id: cursor_id)) do
119-
[<<0x00::int32>>,
120-
coll,
121-
<<0x00, num_return::int32, cursor_id::int64>>]
122-
end
123-
124-
defp encode_op(op_delete(coll: coll, flags: flags, query: query)) do
125-
[<<0x00::int32>>,
126-
coll,
127-
<<0x00, blit_flags(:delete, flags)::int32>> |
128-
query]
129-
end
130-
131-
defp encode_op(op_kill_cursors(cursor_ids: ids)) do
132-
binary_ids = for id <- ids, into: "", do: <<id::int64>>
133-
num = div byte_size(binary_ids), 8
134-
[<<0x00::int32, num::int32>>, binary_ids]
135-
end
136-
137-
defp op_to_code(op_update()), do: @op_update
138-
defp op_to_code(op_insert()), do: @op_insert
139-
defp op_to_code(op_query()), do: @op_query
140-
defp op_to_code(op_get_more()), do: @op_get_more
141-
defp op_to_code(op_delete()), do: @op_delete
142-
defp op_to_code(op_kill_cursors()), do: @op_kill_cursors
143-
144-
defp decode_reply(<<flags::int32, cursor_id::int64, from::int32, num::int32, rest::binary>>) do
145-
op_reply(flags: flags, cursor_id: cursor_id, from: from, num: num, docs: rest)
146-
end
147-
148-
defp encode_header(msg_header(length: length, request_id: request_id,
149-
response_to: response_to, op_code: op_code)) do
150-
<<length::int32, request_id::int32, response_to::int32, op_code::int32>>
151-
end
152-
15399
defp blit_flags(op, flags) when is_list(flags) do
154100
import Bitwise
155101
Enum.reduce(flags, 0x0, &(flag_to_bit(op, &1) ||| &2))
@@ -158,21 +104,9 @@ defmodule Mongo.Messages do
158104
flags
159105
end
160106

161-
Enum.each(@update_flags, fn {flag, bit} ->
162-
defp flag_to_bit(:update, unquote(flag)), do: unquote(bit)
163-
end)
164-
165-
Enum.each(@insert_flags, fn {flag, bit} ->
166-
defp flag_to_bit(:insert, unquote(flag)), do: unquote(bit)
167-
end)
168-
169107
Enum.each(@query_flags, fn {flag, bit} ->
170108
defp flag_to_bit(:query, unquote(flag)), do: unquote(bit)
171109
end)
172110

173-
Enum.each(@delete_flags, fn {flag, bit} ->
174-
defp flag_to_bit(:delete, unquote(flag)), do: unquote(bit)
175-
end)
176-
177111
defp flag_to_bit(_op, _flag), do: 0x0
178112
end

lib/mongo/mongo_db_connection.ex

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,24 +162,20 @@ defmodule Mongo.MongoDBConnection do
162162
end
163163

164164
@impl true
165-
def handle_execute(%Mongo.Query{action: action, extra: extra} = query, params, opts, original_state) do
165+
def handle_execute(%Mongo.Query{action: action} = query, params, opts, original_state) do
166166
tmp_state = %{original_state | database: Keyword.get(opts, :database, original_state.database)}
167-
with {:ok, reply, tmp_state} <- handle_execute(action, extra, params, opts, tmp_state) do
167+
with {:ok, reply, tmp_state} <- execute_action(action, params, opts, tmp_state) do
168168
{:ok, query, reply, Map.put(tmp_state, :database, original_state.database)}
169169
end
170170
end
171171

172-
defp handle_execute(:wire_version, _, _, _, state) do
172+
defp execute_action(:wire_version, _, _, state) do
173173
{:ok, state.wire_version, state}
174174
end
175175

176-
defp handle_execute(:command, nil, [query], opts, s) do
176+
defp execute_action(:command, [query], opts, state) do
177177
flags = Keyword.take(opts, @find_one_flags)
178-
op_query(coll: Utils.namespace("$cmd", s, opts[:database]), query: query, select: "", num_skip: 0, num_return: 1, flags: flags(flags))
179-
|> get_response(s)
180-
end
181-
182-
defp get_response(op, state) do
178+
op = op_query(coll: Utils.namespace("$cmd", state, opts[:database]), query: query, select: "", num_skip: 0, num_return: 1, flags: flags(flags))
183179
with {:ok, response} <- Utils.post_request(state.request_id, op, state),
184180
state = %{state | request_id: state.request_id + 1},
185181
do: {:ok, response, state}

lib/mongo/query.ex

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
11
defmodule Mongo.Query do
2-
@moduledoc false
2+
@moduledoc """
3+
This is the query implementation for the Query Protocoll
34
4-
defstruct action: nil, extra: nil, encoded?: false
5+
The action attribute describes as atom the desired action. There are currently two
6+
  * :command
7+
  * :wire_version
8+
    
9+
  Encoding and decoding does not take place at this point, but is directly performed
10+
into the functions of Mongo.MongoDBConnection.Utils.
11+
"""
12+
defstruct action: nil
513
end
614

715
defimpl DBConnection.Query, for: Mongo.Query do
8-
import Mongo.Messages, only: [op_reply: 1, op_reply: 2]
9-
1016
def parse(query, _opts), do: query
1117
def describe(query, _opts), do: query
12-
13-
def encode(query, params, _opts) do
14-
if query.encoded? do
15-
params
16-
else
17-
Enum.map(params, fn
18-
nil -> ""
19-
doc -> BSON.Encoder.document(doc)
20-
end)
21-
end
22-
end
23-
18+
def encode(query, params, _opts), do: params
2419
def decode(_query, :ok, _opts), do: :ok
2520
def decode(_query, wire_version, _opts) when is_integer(wire_version), do: wire_version
26-
def decode(_query, op_reply(docs: docs) = reply, _opts), do: op_reply(reply, docs: BSON.Decoder.documents(docs))
21+
def decode(_query, reply, _opts), do: reply
2722
end

0 commit comments

Comments
 (0)