Skip to content

Commit 02de3f3

Browse files
committed
Start with service description type and DNS data functions
0 parents  commit 02de3f3

File tree

10 files changed

+949
-0
lines changed

10 files changed

+949
-0
lines changed

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: erlef/setup-beam@v1
16+
with:
17+
otp-version: "28"
18+
gleam-version: "1.13.0"
19+
rebar3-version: "3"
20+
# elixir-version: "1"
21+
- run: gleam deps download
22+
- run: gleam test
23+
- run: gleam format --check src test

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.beam
2+
*.ez
3+
/build
4+
erl_crash.dump

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# esdee
2+
3+
[![Package Version](https://img.shields.io/hexpm/v/esdee)](https://hex.pm/packages/esdee)
4+
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/esdee/)
5+
6+
```sh
7+
gleam add esdee@1
8+
```
9+
```gleam
10+
import esdee
11+
12+
pub fn main() -> Nil {
13+
// TODO: An example of the project in use
14+
}
15+
```
16+
17+
Further documentation can be found at <https://hexdocs.pm/esdee>.
18+
19+
## Development
20+
21+
```sh
22+
gleam run # Run the project
23+
gleam test # Run the tests
24+
```

gleam.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name = "esdee"
2+
version = "1.0.0"
3+
4+
# Fill out these fields if you intend to generate HTML documentation or publish
5+
# your project to the Hex package manager.
6+
#
7+
# description = ""
8+
# licences = ["Apache-2.0"]
9+
# repository = { type = "github", user = "", repo = "" }
10+
# links = [{ title = "Website", href = "" }]
11+
#
12+
# For a full reference of all the available options, you can have a look at
13+
# https://gleam.run/writing-gleam/gleam-toml/.
14+
15+
[dependencies]
16+
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
17+
gleam_erlang = ">= 1.3.0 and < 2.0.0"
18+
19+
[dev-dependencies]
20+
gleeunit = ">= 1.0.0 and < 2.0.0"
21+

manifest.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file was generated by Gleam
2+
# You typically do not need to edit this file
3+
4+
packages = [
5+
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
6+
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
7+
{ name = "gleeunit", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "CD701726CBCE5588B375D157B4391CFD0F2F134CD12D9B6998A395484DE05C58" },
8+
]
9+
10+
[requirements]
11+
gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" }
12+
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
13+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }

src/esdee.gleam

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import gleam/option
2+
3+
pub type ServiceDescription {
4+
ServiceDescription(
5+
service_type: String,
6+
instance_name: String,
7+
target_name: String,
8+
priority: Int,
9+
weight: Int,
10+
port: Int,
11+
txt_values: List(String),
12+
ipv4: option.Option(#(Int, Int, Int, Int)),
13+
ipv6: option.Option(#(Int, Int, Int, Int, Int, Int, Int, Int)),
14+
)
15+
}

src/esdee/internal/dns.gleam

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// Minimal implementation of DNS message encoding/decoding for DNS-SD
2+
3+
/// Encodes a DNS-SD question for the given domain
4+
@external(erlang, "esdee_ffi", "encode_question")
5+
pub fn encode_question(domain: String) -> BitArray
6+
7+
/// The minimal resource record data we require
8+
pub type ResourceRecord {
9+
PtrRecord(service_type: String, instance_name: String)
10+
SrvRecord(
11+
instance_name: String,
12+
priority: Int,
13+
weight: Int,
14+
port: Int,
15+
target_name: String,
16+
)
17+
TxtRecord(instance_name: String, values: List(String))
18+
ARecord(target_name: String, ip: #(Int, Int, Int, Int))
19+
AaaaRecord(target_name: String, ip: #(Int, Int, Int, Int, Int, Int, Int, Int))
20+
}
21+
22+
/// Decode the resource records out of a binary DNS record.
23+
/// We don't currently care about the header or other entries.
24+
@external(erlang, "esdee_ffi", "decode_records")
25+
pub fn decode_records(data: BitArray) -> Result(List(ResourceRecord), Nil)

src/esdee_ffi.erl

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-module(esdee_ffi).
2+
3+
-export([encode_question/1, decode_records/1]).
4+
5+
-include_lib("kernel/src/inet_dns.hrl").
6+
7+
encode_question(Domain) ->
8+
Header = #dns_header{},
9+
Question =
10+
#dns_query{domain = unicode:characters_to_list(Domain),
11+
type = ?T_ANY,
12+
class = ?C_IN},
13+
Record = #dns_rec{header = Header, qdlist = [Question]},
14+
inet_dns:encode(Record, true).
15+
16+
decode_records(Bits) ->
17+
case inet_dns:decode(Bits) of
18+
{ok, Record} ->
19+
Header = Record#dns_rec.header,
20+
case Header#dns_header.qr of
21+
true ->
22+
Answers = lists:filtermap(fun map_answer/1, Record#dns_rec.anlist),
23+
Resources = lists:filtermap(fun map_resource/1, Record#dns_rec.arlist),
24+
{ok, Answers ++ Resources};
25+
_ ->
26+
{ok, []}
27+
end;
28+
_ ->
29+
{error, nil}
30+
end.
31+
32+
map_answer(Answer) ->
33+
case Answer#dns_rr.type of
34+
?S_PTR ->
35+
{true, map_ptr(Answer)};
36+
_ ->
37+
false
38+
end.
39+
40+
map_resource(Resource) ->
41+
case Resource#dns_rr.type of
42+
?S_TXT ->
43+
{true, map_txt(Resource)};
44+
?S_SRV ->
45+
{true, map_srv(Resource)};
46+
?S_A ->
47+
{true, map_a(Resource)};
48+
?S_AAAA ->
49+
{true, map_aaaa(Resource)};
50+
_ ->
51+
false
52+
end.
53+
54+
map_ptr(Record) ->
55+
ServiceType = unicode:characters_to_binary(Record#dns_rr.domain),
56+
InstanceName = unicode:characters_to_binary(Record#dns_rr.data),
57+
{ptr_record, ServiceType, InstanceName}.
58+
59+
map_txt(Record) ->
60+
InstanceName = unicode:characters_to_binary(Record#dns_rr.domain),
61+
Values = lists:map(fun unicode:characters_to_binary/1, Record#dns_rr.data),
62+
{txt_record, InstanceName, Values}.
63+
64+
map_srv(Record) ->
65+
InstanceName = unicode:characters_to_binary(Record#dns_rr.domain),
66+
{Priority, Weight, Port, TargetName} = Record#dns_rr.data,
67+
{srv_record,
68+
InstanceName,
69+
Priority,
70+
Weight,
71+
Port,
72+
unicode:characters_to_binary(TargetName)}.
73+
74+
map_a(Record) ->
75+
TargetName = unicode:characters_to_binary(Record#dns_rr.domain),
76+
Ip = Record#dns_rr.data,
77+
{a_record, TargetName, Ip}.
78+
79+
map_aaaa(Record) ->
80+
TargetName = unicode:characters_to_binary(Record#dns_rr.domain),
81+
Ip = Record#dns_rr.data,
82+
{aaaa_record, TargetName, Ip}.

0 commit comments

Comments
 (0)