Different take on pad() SFINAE, with overloads and only one enable_if<>.
[cppcodec.git] / cppcodec / detail / base64.hpp
1 /**
2  *  Copyright (C) 2015 Topology LP
3  *  Copyright (C) 2013 Adam Rudd (bit calculations)
4  *  All rights reserved.
5  *
6  *  Permission is hereby granted, free of charge, to any person obtaining a copy
7  *  of this software and associated documentation files (the "Software"), to
8  *  deal in the Software without restriction, including without limitation the
9  *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  *  sell copies of the Software, and to permit persons to whom the Software is
11  *  furnished to do so, subject to the following conditions:
12  *
13  *  The above copyright notice and this permission notice shall be included in
14  *  all copies or substantial portions of the Software.
15  *
16  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  *  IN THE SOFTWARE.
23  *
24  *  Bit calculations adapted from https://github.com/adamvr/arduino-base64,
25  *  commit 999595783185a0afcba156d7276dfeaa9cb5382f.
26  */
27
28 #ifndef CPPCODEC_DETAIL_BASE64
29 #define CPPCODEC_DETAIL_BASE64
30
31 #include <stdint.h>
32 #include <type_traits>
33
34 #include "../data/access.hpp"
35 #include "../parse_error.hpp"
36 #include "stream_codec.hpp"
37
38 namespace cppcodec {
39 namespace detail {
40
41 template <typename CodecVariant>
42 class base64 : public CodecVariant::template codec_impl<base64<CodecVariant>>
43 {
44 public:
45     static inline constexpr uint8_t binary_block_size() { return 3; }
46     static inline constexpr uint8_t encoded_block_size() { return 4; }
47
48     template <typename Result, typename ResultState>
49     static void encode_block(Result& encoded, ResultState&, const uint8_t* src);
50
51     template <typename Result, typename ResultState>
52     static void encode_tail(Result& encoded, ResultState&, const uint8_t* src, size_t src_len);
53
54     template <typename Result, typename ResultState>
55     static void pad(Result&, ResultState&, ...) { } // lower priority overload
56
57     template <typename Result, typename ResultState, typename V = CodecVariant,
58             typename std::enable_if<V::generates_padding()>::type* = nullptr>
59     static void pad(Result& encoded, ResultState&, size_t remaining_src_len);
60
61     template <typename Result, typename ResultState>
62     static void decode_block(Result& decoded, ResultState&, const uint8_t* idx);
63
64     template <typename Result, typename ResultState>
65     static void decode_tail(Result& decoded, ResultState&, const uint8_t* idx, size_t idx_len);
66 };
67
68
69 template <typename CodecVariant>
70 template <typename Result, typename ResultState>
71 inline void base64<CodecVariant>::encode_block(
72     Result& encoded, ResultState& state, const uint8_t* src)
73 {
74     using V = CodecVariant;
75     data::put(encoded, state, V::symbol(src[0] >> 2)); // first 6 bits
76     data::put(encoded, state, V::symbol(((src[0] & 0x3) << 4) + (src[1] >> 4))); // last 2 + next 4
77     data::put(encoded, state, V::symbol(((src[1] & 0xF) << 2) + (src[2] >> 6))); // last 4 + next 2
78     data::put(encoded, state, V::symbol(src[2] & 0x3F)); // last 6 bits
79 }
80
81 template <typename CodecVariant>
82 template <typename Result, typename ResultState>
83 inline void base64<CodecVariant>::encode_tail(
84         Result& encoded, ResultState& state, const uint8_t* src, size_t remaining_src_len)
85 {
86     using V = CodecVariant;
87
88     data::put(encoded, state, V::symbol(src[0] >> 2)); // encoded size 1
89     if (remaining_src_len == 1) {
90         data::put(encoded, state, V::symbol((src[0] & 0x03) << 4)); // size 2
91         return;
92     }
93     data::put(encoded, state, V::symbol(((src[0] & 0x03) << 4) + (src[1] >> 4))); // size 2
94     if (remaining_src_len == 2) {
95         data::put(encoded, state, V::symbol((src[1] & 0x0f) << 2)); // size 3
96         return;
97     }
98     abort(); // not reached: encode_block() should be called if remaining_src_len > 2, not this function
99 }
100
101 template <typename CodecVariant>
102 template <typename Result, typename ResultState, typename V,
103         typename std::enable_if<V::generates_padding()>::type*>
104 inline void base64<CodecVariant>::pad(
105         Result& encoded, ResultState& state, size_t remaining_src_len)
106 {
107     switch (remaining_src_len) {
108     case 1: // 2 symbols, 2 padding characters
109         data::put(encoded, state, CodecVariant::padding_symbol());
110     case 2: // 3 symbols, 1 padding character
111         data::put(encoded, state, CodecVariant::padding_symbol());
112     }
113 }
114
115 template <typename CodecVariant>
116 template <typename Result, typename ResultState>
117 inline void base64<CodecVariant>::decode_block(
118         Result& decoded, ResultState& state, const uint8_t* idx)
119 {
120     data::put(decoded, state, (uint8_t)((idx[0] << 2) + ((idx[1] & 0x30) >> 4)));
121     data::put(decoded, state, (uint8_t)(((idx[1] & 0xF) << 4) + ((idx[2] & 0x3C) >> 2)));
122     data::put(decoded, state, (uint8_t)(((idx[2] & 0x3) << 6) + idx[3]));
123 }
124
125 template <typename CodecVariant>
126 template <typename Result, typename ResultState>
127 inline void base64<CodecVariant>::decode_tail(
128         Result& decoded, ResultState& state, const uint8_t* idx, size_t idx_len)
129 {
130     if (idx_len == 1) {
131         throw invalid_input_length(
132                 "invalid number of symbols in last base64 block: found 1, expected 2 or 3");
133     }
134
135     // idx_len == 2: decoded size 1
136     data::put(decoded, state, (uint8_t)((idx[0] << 2) + ((idx[1] & 0x30) >> 4)));
137     if (idx_len == 2) {
138         return;
139     }
140
141     // idx_len == 3: decoded size 2
142     data::put(decoded, state, (uint8_t)(((idx[1] & 0xF) << 4) + ((idx[2] & 0x3C) >> 2)));
143 }
144
145 } // namespace detail
146 } // namespace cppcodec
147
148 #endif // CPPCODEC_DETAIL_BASE64