191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia/*
291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * Copyright (C) 2015 The Android Open Source Project
391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia *
491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * Licensed under the Apache License, Version 2.0 (the "License");
591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * you may not use this file except in compliance with the License.
691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * You may obtain a copy of the License at
791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia *
891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia *      http://www.apache.org/licenses/LICENSE-2.0
991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia *
1091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * Unless required by applicable law or agreed to in writing, software
1191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * distributed under the License is distributed on an "AS IS" BASIS,
1291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * See the License for the specific language governing permissions and
1491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia * limitations under the License.
1591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia */
1691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
1791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviapackage com.android.server.accounts;
1891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
19c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdiviaimport android.accounts.Account;
20c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdiviaimport android.util.LruCache;
21c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdiviaimport android.util.Pair;
22c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
23c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdiviaimport com.android.internal.util.Preconditions;
24c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
2591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviaimport java.util.ArrayList;
2691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviaimport java.util.Arrays;
2791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviaimport java.util.HashMap;
2891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviaimport java.util.List;
2991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdiviaimport java.util.Objects;
3091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
3191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia/**
32c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia * TokenCaches manage time limited authentication tokens in memory.
3391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia */
3491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia/* default */ class TokenCache {
3591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
36c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    private static final int MAX_CACHE_CHARS = 64000;
37c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
3891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    private static class Value {
3991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public final String token;
4091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public final long expiryEpochMillis;
4191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
4291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public Value(String token, long expiryEpochMillis) {
4391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            this.token = token;
4491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            this.expiryEpochMillis = expiryEpochMillis;
4591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
4691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
4791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
4891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    private static class Key {
49c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public final Account account;
5091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public final String packageName;
5191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public final String tokenType;
5291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public final byte[] sigDigest;
5391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
54c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public Key(Account account, String tokenType, String packageName, byte[] sigDigest) {
55c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            this.account = account;
5691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            this.tokenType = tokenType;
5791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            this.packageName = packageName;
5891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            this.sigDigest = sigDigest;
5991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
6091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
6191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        @Override
6291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public boolean equals(Object o) {
6391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            if (o != null && o instanceof Key) {
6491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia                Key cacheKey = (Key) o;
65c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                return Objects.equals(account, cacheKey.account)
66c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                        && Objects.equals(packageName, cacheKey.packageName)
6791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia                        && Objects.equals(tokenType, cacheKey.tokenType)
6891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia                        && Arrays.equals(sigDigest, cacheKey.sigDigest);
6991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            } else {
7091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia                return false;
7191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            }
7291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
7391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
7491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        @Override
7591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        public int hashCode() {
76c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            return account.hashCode()
77c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                    ^ packageName.hashCode()
78c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                    ^ tokenType.hashCode()
79c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                    ^ Arrays.hashCode(sigDigest);
8091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
8191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
8291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
83c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    private static class TokenLruCache extends LruCache<Key, Value> {
8491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
85c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        private class Evictor {
86c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            private final List<Key> mKeys;
87c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
88c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            public Evictor() {
89c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                mKeys = new ArrayList<>();
90c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
91c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
92c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            public void add(Key k) {
93c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                mKeys.add(k);
94c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
95c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
96c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            public void evict() {
97c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                for (Key k : mKeys) {
98c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                    TokenLruCache.this.remove(k);
99c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                }
100c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
101c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        }
102c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
103c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        /**
104c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia         * Map associated tokens with an Evictor that will manage evicting the token from the
105c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia         * cache. This reverse lookup is needed because very little information is given at token
106c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia         * invalidation time.
107c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia         */
108c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        private HashMap<Pair<String, String>, Evictor> mTokenEvictors = new HashMap<>();
109c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        private HashMap<Account, Evictor> mAccountEvictors = new HashMap<>();
110c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
111c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public TokenLruCache() {
112c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            super(MAX_CACHE_CHARS);
113c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        }
114c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
115c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        @Override
116c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        protected int sizeOf(Key k, Value v) {
117c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            return v.token.length();
118c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        }
119c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
120c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        @Override
121c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        protected void entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal) {
122c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            // When a token has been removed, clean up the associated Evictor.
123c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            if (oldVal != null && newVal == null) {
124c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                /*
125c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                 * This is recursive, but it won't spiral out of control because LruCache is
126c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                 * thread safe and the Evictor can only be removed once.
127c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                 */
128c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                Evictor evictor = mTokenEvictors.remove(oldVal.token);
129c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                if (evictor != null) {
130c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                    evictor.evict();
131c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                }
132c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
133c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        }
13491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
135c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public void putToken(Key k, Value v) {
136c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            // Prepare for removal by token string.
137c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            Evictor tokenEvictor = mTokenEvictors.get(v.token);
138c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            if (tokenEvictor == null) {
139c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                tokenEvictor = new Evictor();
140c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
141c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            tokenEvictor.add(k);
142c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            mTokenEvictors.put(new Pair<>(k.account.type, v.token), tokenEvictor);
14391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
144c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            // Prepare for removal by associated account.
145c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            Evictor accountEvictor = mAccountEvictors.get(k.account);
146c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            if (accountEvictor == null) {
147c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                accountEvictor = new Evictor();
148c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
149c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            accountEvictor.add(k);
150c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            mAccountEvictors.put(k.account, tokenEvictor);
151c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
152c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            // Only cache the token once we can remove it directly or by account.
153c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            put(k, v);
15491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
15591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
156c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public void evict(String accountType, String token) {
157c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            Evictor evictor = mTokenEvictors.get(new Pair<>(accountType, token));
158c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            if (evictor != null) {
159c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                evictor.evict();
160c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            }
161c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
16291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
16391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
164c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        public void evict(Account account) {
165c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            Evictor evictor = mAccountEvictors.get(account);
166c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            if (evictor != null) {
167c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia                evictor.evict();
16891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            }
16991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
17091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
17191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
17291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    /**
173c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia     * Map associating basic token lookup information with with actual tokens (and optionally their
174c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia     * expiration times).
175c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia     */
176c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    private TokenLruCache mCachedTokens = new TokenLruCache();
177c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
178c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    /**
17991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * Caches the specified token until the specified expiryMillis. The token will be associated
18091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * with the given token type, package name, and digest of signatures.
18191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     *
18291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * @param token
18391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * @param tokenType
18491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * @param packageName
18591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * @param sigDigest
18691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * @param expiryMillis
18791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     */
18891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    public void put(
189c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            Account account,
19091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            String token,
19191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            String tokenType,
19291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            String packageName,
19391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            byte[] sigDigest,
19491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            long expiryMillis) {
195c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        Preconditions.checkNotNull(account);
19691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        if (token == null || System.currentTimeMillis() > expiryMillis) {
19791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            return;
19891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
199c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        Key k = new Key(account, tokenType, packageName, sigDigest);
20091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        Value v = new Value(token, expiryMillis);
201c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        mCachedTokens.putToken(k, v);
20291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
20391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
20491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    /**
20591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * Evicts the specified token from the cache. This should be called as part of a token
20691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * invalidation workflow.
20791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     */
208c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    public void remove(String accountType, String token) {
209c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        mCachedTokens.evict(accountType, token);
210c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    }
211c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia
212c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    public void remove(Account account) {
213c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        mCachedTokens.evict(account);
21491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
21591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia
21691979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    /**
21791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     * Gets a token from the cache if possible.
21891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia     */
219c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia    public String get(Account account, String tokenType, String packageName, byte[] sigDigest) {
220c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia        Key k = new Key(account, tokenType, packageName, sigDigest);
22191979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        Value v = mCachedTokens.get(k);
22291979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        long currentTime = System.currentTimeMillis();
22391979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        if (v != null && currentTime < v.expiryEpochMillis) {
22491979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia            return v.token;
22591979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        } else if (v != null) {
226c37ee22714ddec1104ba3a2189cf77924ac27812Carlos Valdivia            remove(account.type, v.token);
22791979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        }
22891979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia        return null;
22991979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia    }
23091979be8804232a04da2bf36cdd857ee7da04479Carlos Valdivia}
231