1/*
2 * Copyright (c) 2014 IBM Corp.
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 *    Seth Hoenig
11 *    Allan Stockdill-Mander
12 *    Mike Robertson
13 */
14
15package mqtt
16
17import (
18	"errors"
19	"strings"
20)
21
22//ErrInvalidQos is the error returned when an packet is to be sent
23//with an invalid Qos value
24var ErrInvalidQos = errors.New("Invalid QoS")
25
26//ErrInvalidTopicEmptyString is the error returned when a topic string
27//is passed in that is 0 length
28var ErrInvalidTopicEmptyString = errors.New("Invalid Topic; empty string")
29
30//ErrInvalidTopicMultilevel is the error returned when a topic string
31//is passed in that has the multi level wildcard in any position but
32//the last
33var ErrInvalidTopicMultilevel = errors.New("Invalid Topic; multi-level wildcard must be last level")
34
35// Topic Names and Topic Filters
36// The MQTT v3.1.1 spec clarifies a number of ambiguities with regard
37// to the validity of Topic strings.
38// - A Topic must be between 1 and 65535 bytes.
39// - A Topic is case sensitive.
40// - A Topic may contain whitespace.
41// - A Topic containing a leading forward slash is different than a Topic without.
42// - A Topic may be "/" (two levels, both empty string).
43// - A Topic must be UTF-8 encoded.
44// - A Topic may contain any number of levels.
45// - A Topic may contain an empty level (two forward slashes in a row).
46// - A TopicName may not contain a wildcard.
47// - A TopicFilter may only have a # (multi-level) wildcard as the last level.
48// - A TopicFilter may contain any number of + (single-level) wildcards.
49// - A TopicFilter with a # will match the absense of a level
50//     Example:  a subscription to "foo/#" will match messages published to "foo".
51
52func validateSubscribeMap(subs map[string]byte) ([]string, []byte, error) {
53	var topics []string
54	var qoss []byte
55	for topic, qos := range subs {
56		if err := validateTopicAndQos(topic, qos); err != nil {
57			return nil, nil, err
58		}
59		topics = append(topics, topic)
60		qoss = append(qoss, qos)
61	}
62
63	return topics, qoss, nil
64}
65
66func validateTopicAndQos(topic string, qos byte) error {
67	if len(topic) == 0 {
68		return ErrInvalidTopicEmptyString
69	}
70
71	levels := strings.Split(topic, "/")
72	for i, level := range levels {
73		if level == "#" && i != len(levels)-1 {
74			return ErrInvalidTopicMultilevel
75		}
76	}
77
78	if qos < 0 || qos > 2 {
79		return ErrInvalidQos
80	}
81	return nil
82}
83