/
uuid16.ex
203 lines (164 loc) · 6.18 KB
/
uuid16.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
defmodule Grizzly.ZWave.SmartStart.MetaExtension.UUID16 do
@moduledoc """
This is used to advertise 16 bytes of manufactured-defined information that
is unique for a given product.
Z-Wave UUIDs are not limited to the format outlined in RFC 4122 but can also
be ASCII characters and a relevant prefix.
"""
@typedoc """
The three formats that the Z-Wave UUID can be formatted in are `:ascii`,
`:hex`, or `:rfc4122`.
Both `:ascii` and `:hex` can also have the prefix `sn:` or `UUID:`.
Valid `:hex` formatted UUIDs look like:
- `0102030405060708090A141516171819`
- `sn:0102030405060708090A141516171819`
- `UUID:0102030405060708090A141516171819`
Valid `:ascii` formatted UUIDs look like:
- `Hello Elixir!!!`
- `sn:Hello Elixir!!!`
- `UUID:Hello Elixir!!!`
Lastly `rfc4122` format looks like `58D5E212-165B-4CA0-909B-C86B9CEE0111`
where every two digits make up one hex value.
More information about RFC 4122 and the specification format can be
found [here](https://tools.ietf.org/html/rfc4122#section-4.1.2).
"""
@type format() :: :ascii | :hex | :rfc4122
@type uuid() :: binary()
@type t() :: %{uuid: uuid(), format: format()}
defguardp is_format_hex(value) when value in [0, 2, 4]
defguardp is_format_ascii(value) when value in [1, 3, 5]
defguardp is_format_rfc4122(value) when value == 6
@doc """
Make a new `UUID16.t()`
"""
@spec new(String.t(), format()) :: {:ok, t()} | {:error, :invalid_uuid_length | :invalid_format}
def new(uuid, format) do
with :ok <- validate_format(format),
uuid_no_prefix = remove_uuid_prefix(uuid),
:ok <- validate_uuid_length(uuid_no_prefix, format) do
{:ok, %{uuid: uuid, format: format}}
end
end
@doc """
Make a binary string from a `UUID16.t()`
"""
@spec encode(t()) :: binary()
def encode(uuid16) do
[format_prefix, uuid] = get_format_prefix_and_uuid(uuid16.uuid)
uuid_binary = uuid_to_binary(uuid, uuid16.format)
<<0x06, 0x11, format_to_byte(uuid16.format, format_prefix)>> <> uuid_binary
end
defp get_format_prefix_and_uuid(uuid_string) do
case String.split(uuid_string, ":") do
[uuid] -> [:none, uuid]
[prefix, _uuid] = result when prefix in ["sn", "UUID"] -> result
end
end
@doc """
Take a binary string and try to make a `UUID16.t()` from it
If the critical bit is set in teh binary this will return
`{:error, :critical_bit_set}` and the information should be ignored.
If the format in the binary is not part of the defined Z-Wave specification
this will return `{:error, :invalid_format}`
"""
@spec parse(binary) :: {:ok, t()} | {:error, any()}
def parse(<<0x03::size(7), 0::size(1), 0x11, presentation_format, uuid::binary>>) do
with {:ok, uuid_string} <- uuid_from_binary(presentation_format, uuid),
{:ok, format} <- format_from_byte(presentation_format) do
new(uuid_string, format)
end
end
def parse(<<0x03::size(7), 0x01::size(1), _rest::binary>>) do
{:error, :critical_bit_set}
end
def parse(bin) when is_binary(bin), do: {:error, :invalid_binary}
defp format_from_byte(format_byte) when is_format_hex(format_byte), do: {:ok, :hex}
defp format_from_byte(format_byte) when is_format_ascii(format_byte), do: {:ok, :ascii}
defp format_from_byte(format_byte) when is_format_rfc4122(format_byte), do: {:ok, :rfc4122}
defp format_from_byte(format_byte) when format_byte in 7..99, do: {:ok, :hex}
defp format_from_byte(_), do: {:error, :invalid_format}
defp format_to_byte(:hex, :none), do: 0
defp format_to_byte(:hex, "sn"), do: 2
defp format_to_byte(:hex, "UUID"), do: 4
defp format_to_byte(:ascii, :none), do: 1
defp format_to_byte(:ascii, "sn"), do: 3
defp format_to_byte(:ascii, "UUID"), do: 5
defp format_to_byte(:rfc4122, :none), do: 6
defp uuid_to_binary(uuid, :hex) do
hex_uuid_to_binary(uuid)
end
defp uuid_to_binary(uuid, :ascii) do
ascii_uuid_to_binary(uuid)
end
defp uuid_to_binary(uuid, :rfc4122) do
rfc4122_uuid_to_binary(uuid)
end
defp uuid_to_binary(_uuid, _format), do: {:error, :invalid_uuid_length}
defp rfc4122_uuid_to_binary(uuid) do
uuid
|> String.replace("-", "")
|> Base.decode16!(case: :mixed)
end
defp hex_uuid_to_binary(uuid) do
Base.decode16!(uuid, case: :mixed)
end
defp ascii_uuid_to_binary(uuid_string) do
uuid_string
|> String.split("", trim: true)
|> Enum.reduce(<<>>, &(&2 <> &1))
end
defp uuid_as_hex_digits(uuid) do
Base.encode16(uuid)
end
defp uuid_as_ascii(uuid) do
uuid_out_string =
uuid
|> to_charlist()
|> to_string()
uuid_out_string
end
defp uuid_from_binary(format, uuid) when is_format_hex(format) do
formatted_uuid = uuid_as_hex_digits(uuid)
case format do
0 -> {:ok, formatted_uuid}
2 -> {:ok, "sn:#{formatted_uuid}"}
4 -> {:ok, "UUID:#{formatted_uuid}"}
end
end
defp uuid_from_binary(format, uuid) when is_format_ascii(format) do
formatted_uuid = uuid_as_ascii(uuid)
case format do
1 -> {:ok, formatted_uuid}
3 -> {:ok, "sn:#{formatted_uuid}"}
5 -> {:ok, "UUID:#{formatted_uuid}"}
end
end
defp uuid_from_binary(
6,
<<time_low::binary-size(4), time_mid::binary-size(2),
time_hi_and_version::binary-size(2), clock_seq::binary-size(2), node::binary-size(6)>>
) do
formatted =
[
time_low,
time_mid,
time_hi_and_version,
clock_seq,
node
]
|> Enum.map_join("-", &Base.encode16/1)
{:ok, formatted}
end
defp uuid_from_binary(format, uuid) when format in 7..99 do
uuid_from_binary(0, uuid)
end
defp validate_format(format) when format in [:hex, :ascii, :rfc4122], do: :ok
defp validate_format(_format), do: {:error, :invalid_format}
defp remove_uuid_prefix("sn:" <> uuid), do: uuid
defp remove_uuid_prefix("UUID:" <> uuid), do: uuid
defp remove_uuid_prefix(uuid), do: uuid
defp validate_uuid_length(uuid, :hex) when byte_size(uuid) == 32, do: :ok
defp validate_uuid_length(uuid, :ascii) when byte_size(uuid) == 16, do: :ok
defp validate_uuid_length(uuid, :rfc4122) when byte_size(uuid) == 36, do: :ok
defp validate_uuid_length(_uuid, _format), do: {:error, :invalid_uuid_length}
end