1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file. 4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "components/gcm_driver/gcm_driver.h" 6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include <algorithm> 8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/logging.h" 10116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "base/metrics/field_trial.h" 11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "components/gcm_driver/gcm_app_handler.h" 12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)namespace gcm { 14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 15116680a4aac90f2aa7413d9095a592090648e557Ben Murdochnamespace { 16116680a4aac90f2aa7413d9095a592090648e557Ben Murdochconst char kGCMFieldTrialName[] = "GCM"; 17116680a4aac90f2aa7413d9095a592090648e557Ben Murdochconst char kGCMFieldTrialEnabledGroupName[] = "Enabled"; 18116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch} // namespace 19116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 20116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch// static 21116680a4aac90f2aa7413d9095a592090648e557Ben Murdochbool GCMDriver::IsAllowedForAllUsers() { 22116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch std::string group_name = 23116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch base::FieldTrialList::FindFullName(kGCMFieldTrialName); 24116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return group_name == kGCMFieldTrialEnabledGroupName; 25116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch} 26116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 2746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)GCMDriver::GCMDriver() { 28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)GCMDriver::~GCMDriver() { 31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::Register(const std::string& app_id, 34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const std::vector<std::string>& sender_ids, 35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const RegisterCallback& callback) { 36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!app_id.empty()); 37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!sender_ids.empty()); 38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!callback.is_null()); 39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result = EnsureStarted(); 41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (result != GCMClient::SUCCESS) { 42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(std::string(), result); 43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // If previous un/register operation is still in progress, bail out. 47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (IsAsyncOperationPending(app_id)) { 48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING); 49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // Normalize the sender IDs by making them sorted. 53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::vector<std::string> normalized_sender_ids = sender_ids; 54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end()); 55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 5646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) register_callbacks_[app_id] = callback; 5746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) 5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) RegisterImpl(app_id, normalized_sender_ids); 59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::Unregister(const std::string& app_id, 62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const UnregisterCallback& callback) { 63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!app_id.empty()); 64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!callback.is_null()); 65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result = EnsureStarted(); 67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (result != GCMClient::SUCCESS) { 68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(result); 69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // If previous un/register operation is still in progress, bail out. 73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (IsAsyncOperationPending(app_id)) { 74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(GCMClient::ASYNC_OPERATION_PENDING); 75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unregister_callbacks_[app_id] = callback; 79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 8046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) UnregisterImpl(app_id); 81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::Send(const std::string& app_id, 84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const std::string& receiver_id, 85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const GCMClient::OutgoingMessage& message, 86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const SendCallback& callback) { 87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!app_id.empty()); 88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!receiver_id.empty()); 89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) DCHECK(!callback.is_null()); 90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result = EnsureStarted(); 92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (result != GCMClient::SUCCESS) { 93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(std::string(), result); 94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // If the message with send ID is still in progress, bail out. 98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::pair<std::string, std::string> key(app_id, message.id); 99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (send_callbacks_.find(key) != send_callbacks_.end()) { 100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(message.id, GCMClient::INVALID_PARAMETER); 101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) send_callbacks_[key] = callback; 105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 10646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) SendImpl(app_id, receiver_id, message); 107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::RegisterFinished(const std::string& app_id, 110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const std::string& registration_id, 111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result) { 112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::map<std::string, RegisterCallback>::iterator callback_iter = 113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) register_callbacks_.find(app_id); 114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (callback_iter == register_callbacks_.end()) { 115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // The callback could have been removed when the app is uninstalled. 116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) RegisterCallback callback = callback_iter->second; 120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) register_callbacks_.erase(callback_iter); 121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(registration_id, result); 122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::UnregisterFinished(const std::string& app_id, 125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result) { 126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::map<std::string, UnregisterCallback>::iterator callback_iter = 127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unregister_callbacks_.find(app_id); 128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (callback_iter == unregister_callbacks_.end()) 129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) UnregisterCallback callback = callback_iter->second; 132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unregister_callbacks_.erase(callback_iter); 133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(result); 134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void GCMDriver::SendFinished(const std::string& app_id, 137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) const std::string& message_id, 138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) GCMClient::Result result) { 139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::map<std::pair<std::string, std::string>, SendCallback>::iterator 140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback_iter = send_callbacks_.find( 141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) std::pair<std::string, std::string>(app_id, message_id)); 142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if (callback_iter == send_callbacks_.end()) { 143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) // The callback could have been removed when the app is uninstalled. 144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return; 145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) } 146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) SendCallback callback = callback_iter->second; 148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) send_callbacks_.erase(callback_iter); 149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) callback.Run(message_id, result); 150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 15246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void GCMDriver::Shutdown() { 15346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin(); 15446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) iter != app_handlers_.end(); ++iter) { 15546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) iter->second->ShutdownHandler(); 15646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) } 15746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) app_handlers_.clear(); 158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void GCMDriver::AddAppHandler(const std::string& app_id, 16146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) GCMAppHandler* handler) { 16246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) DCHECK(!app_id.empty()); 16346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) DCHECK(handler); 164f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(app_handlers_.count(app_id), 0u); 16546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) app_handlers_[app_id] = handler; 166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 16846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void GCMDriver::RemoveAppHandler(const std::string& app_id) { 16946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) DCHECK(!app_id.empty()); 17046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) app_handlers_.erase(app_id); 171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)GCMAppHandler* GCMDriver::GetAppHandler(const std::string& app_id) { 174f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Look for exact match. 17546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) GCMAppHandlerMap::const_iterator iter = app_handlers_.find(app_id); 176f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (iter != app_handlers_.end()) 177f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return iter->second; 178f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 179f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Ask the handlers whether they know how to handle it. 180f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) for (iter = app_handlers_.begin(); iter != app_handlers_.end(); ++iter) { 181f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (iter->second->CanHandle(app_id)) 182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return iter->second; 183f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 184f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 185f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return &default_app_handler_; 186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 18846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)bool GCMDriver::HasRegisterCallback(const std::string& app_id) { 18946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) return register_callbacks_.find(app_id) != register_callbacks_.end(); 19046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)} 191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 19246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void GCMDriver::ClearCallbacks() { 19346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) register_callbacks_.clear(); 19446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) unregister_callbacks_.clear(); 19546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) send_callbacks_.clear(); 196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 19846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)bool GCMDriver::IsAsyncOperationPending(const std::string& app_id) const { 19946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) return register_callbacks_.find(app_id) != register_callbacks_.end() || 20046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) unregister_callbacks_.find(app_id) != unregister_callbacks_.end(); 201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} 202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)} // namespace gcm 204