-
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
// Copyright 2025 Shota FUJI
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
using GLib;
namespace Sood {
public errordomain ParseError {
SIGNATURE_MISMATCH,
INVALID_HEADER_SIZE,
INVALID_PROPERTY_KEY,
INVALID_PROPERTY_VALUE,
DUPLICATED_PROPERTY_KEY,
}
public enum Kind {
UNKNOWN = 0,
QUERY = 1,
RESPONSE = 2;
internal static Kind from_byte(uint8 byte) {
switch (byte) {
case 'Q':
return QUERY;
case 'R':
return RESPONSE;
default:
return UNKNOWN;
}
}
}
public const uint8[] discovery_query = {
'S', 'O', 'O', 'D', 2, 'Q',
16,
// query_service_id
'q', 'u', 'e', 'r', 'y', '_', 's', 'e', 'r', 'v', 'i', 'c', 'e', '_', 'i', 'd',
0, 36,
// 00720724-5143-4a9b-abac-0e50cba674bb
'0', '0', '7', '2', '0', '7', '2', '4', '-',
'5', '1', '4', '3', '-',
'4', 'a', '9', 'b', '-',
'a', 'b', 'a', 'c', '-',
'0', 'e', '5', '0', 'c', 'b', 'a', '6', '7', '4', 'b', 'b'
};
private const uint8[] signature = { 'S', 'O', 'O', 'D', 2 };
public class Message : Object {
public Kind kind { get; set construct; }
public Gee.HashMap<string, string> properties { get; construct; }
public Message.from_bytes(Bytes bytes) throws ParseError {
if (bytes.length < signature.length + 1) {
throw new ParseError.INVALID_HEADER_SIZE("Too small header size, unlikely SOOD message.");
}
var properties = new Gee.HashMap<string, string>();
int i = 0;
for (; i < signature.length; i++) {
if (bytes[i] != signature[i]) {
throw new ParseError.SIGNATURE_MISMATCH("Message does not have valid SOOD signature.");
}
}
var kind = Kind.from_byte(bytes[i]);
i += 1;
while (i < bytes.length) {
var key = parse_property_key(bytes, ref i);
if (i >= bytes.length) {
throw new ParseError.INVALID_PROPERTY_VALUE("Property value is missing after key field.");
}
if (properties.has_key(key)) {
throw new ParseError.DUPLICATED_PROPERTY_KEY("Found duplicated property.");
}
var value = parse_property_value(bytes, ref i);
properties[key] = value;
}
Object(kind: kind, properties: properties);
}
private static string parse_property_key(Bytes bytes, ref int position) throws ParseError {
var size = bytes[position];
position += 1;
if (size == 0) {
throw new ParseError.INVALID_PROPERTY_KEY("Found property key of zero bytes.");
}
if ((position + size) > bytes.length) {
throw new ParseError.INVALID_PROPERTY_KEY("Declared key size overflows byte array.");
}
var key = bytes.slice(position, position + size);
for (int i = 0; i < key.length; i++) {
if (key[i] == 0) {
throw new ParseError.INVALID_PROPERTY_KEY("Property key contains NUL character.");
}
}
var key_data = Bytes.unref_to_data(key);
position += size;
return "%.*s".printf(key_data.length, key_data);
}
private static string parse_property_value(Bytes bytes, ref int position) throws ParseError {
// Size field is 16bit and value can be empty, so the minimum byte size is 2.
if (position + 2 >= bytes.length) {
throw new ParseError.INVALID_PROPERTY_VALUE("Property value size is corrupted.");
}
var size = uint16.from_big_endian(((uint16) bytes[position]) | ((uint16) (bytes[position + 1] << 8)));
position += 2;
if ((position + size) > bytes.length) {
throw new ParseError.INVALID_PROPERTY_VALUE("Declared value size overflows byte array.");
}
var value = bytes.slice(position, position + size);
for (int i = 0; i < value.length; i++) {
if (value[i] == 0) {
throw new ParseError.INVALID_PROPERTY_VALUE("Property value contains NUL character.");
}
}
var value_data = Bytes.unref_to_data(value);
position += size;
return "%.*s".printf(value_data.length, value_data);
}
}
}